{"record":{"id":"c8387533b3ecbff6","repo":"multica-ai/multica","slug":"errrepobusy","errorCode":"ErrRepoBusy","errorMessage":"repository is busy","messagePattern":"repository is busy","errorType":"error_code","errorClass":"ErrRepoBusy","httpStatus":503,"severity":"warning","filePath":"server/internal/daemon/repocache/cache.go","lineNumber":176,"sourceCode":"}\n\n// Cache manages bare git clones for workspace repositories.\ntype Cache struct {\n\troot   string // base directory for all caches (e.g. ~/multica_workspaces/.repos)\n\tlogger *slog.Logger\n\t// repoLocks maps bare repo path → dedicated mutex. Any mutating operation\n\t// on a given bare repo (clone, fetch, worktree add, ref update) must\n\t// hold its lock — git's own lockfiles (packed-refs.lock, config.lock,\n\t// worktree admin dirs) don't tolerate parallel mutations on the same\n\t// repo. Separate repos are independent and run concurrently.\n\trepoLocks sync.Map // barePath -> *repoLock\n}\n\n// ErrRepoBusy means a foreground checkout could not acquire its repository\n// within the caller's bounded wait. Callers that advertised retry support can\n// turn this into a retryable HTTP response instead of waiting until their\n// transport deadline expires.\nvar ErrRepoBusy = errors.New(\"repository is busy\")\n\n// Activity is the path-free repository coordination state exposed through the\n// daemon health endpoint. It is diagnostic only.\ntype Activity struct {\n\tMaintenanceActive int\n\tForegroundWaiters int\n}\n\n// repoLock is a foreground-priority mutex. Ordinary cache mutations serialize\n// exactly as they did with sync.Mutex. Low-priority maintenance is different:\n// it only starts on an idle repository and receives a context that is cancelled\n// as soon as a foreground operation queues. The maintenance holder remains\n// responsible for stopping its Git process tree before unlocking.\ntype repoLock struct {\n\tmu                sync.Mutex\n\theld              bool\n\tmaintenance       bool\n\tmaintenanceCancel context.CancelCauseFunc","sourceCodeStart":158,"sourceCodeEnd":194,"githubUrl":"https://github.com/multica-ai/multica/blob/2c0912b6ec764b373d44eeea1e80f0d9f11ab417/server/internal/daemon/repocache/cache.go#L158-L194","documentation":"Sentinel error ErrRepoBusy in server/internal/daemon/repocache/cache.go: a foreground checkout requested a repository whose dedicated repoLock was held (by another clone/fetch/worktree-add/ref-update, or by low-priority maintenance), and the caller's bounded wait expired before the lock became free. git's own lockfiles cannot tolerate parallel mutations on one repo, so the cache serializes them; callers that advertised retry support are expected to convert this into a retryable response instead of blocking to the transport deadline.","triggerScenarios":"Two tasks targeting the same repo starting near-simultaneously; a background maintenance pass (GC/fetch) holding the lock when a foreground checkout queues; a large clone starving subsequent waiters past their bounded wait.","commonSituations":"Parallel agent runs on the same repository; scheduled repo maintenance overlapping task start; big monorepo clones taking longer than the foreground wait budget.","solutions":["Retry the operation after a backoff — the lock holder will usually finish and the retry acquires the lock.","Stagger task starts that target the same repo to avoid lock contention.","If it recurs constantly, inspect the daemon health Activity (ForegroundWaiters/MaintenanceActive) to see whether maintenance is starving foreground ops.","Callers: map ErrRepoBusy to a retryable HTTP response (e.g. 429/503 with Retry-After) as the doc comment instructs, not to a hard failure."],"exampleFix":"// go — caller-side retryable mapping\nif errors.Is(err, repocache.ErrRepoBusy) {\n    w.Header().Set(\"Retry-After\", \"2\")\n    http.Error(w, \"repository is busy, retry shortly\", http.StatusTooManyRequests)\n    return\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"if errors.Is(err, repocache.ErrRepoBusy) {\n    w.Header().Set(\"Retry-After\", \"2\")\n    http.Error(w, \"repository is busy, retry shortly\", http.StatusTooManyRequests)\n    return\n}\n// client side: honor Retry-After and re-issue the request","preventionTips":["Map ErrRepoBusy to a retryable HTTP status with Retry-After — never let it consume the transport deadline.","Stagger task starts that target the same repository.","Watch the daemon health Activity counters to detect maintenance starving foreground waiters."],"tags":["repocache","locking","concurrency","retryable","daemon","go"],"backgroundTag":null,"analyzedSha":"2c0912b6ec764b373d44eeea1e80f0d9f11ab417","analyzedAt":"2026-08-15T13:25:18.241Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}