multica-ai/multica · warning
repository is busy with another operation; retry later
Error message
repository is busy with another operation; retry later
What it means
HTTP 503 returned when the underlying repocache reports ErrRepoBusy (another operation holds the repository's lock — e.g. a concurrent clone/fetch/worktree operation on the same repo) and the request opted in with retry_busy=true. The response includes a retry header and a Retry-After value so callers can back off and retry the same request.
Source
Thrown at server/internal/daemon/health.go:301
IsolatedGitMetadata: req.CheckoutMode == repoCheckoutModeIsolated,
}
if req.RetryBusy {
params.LockWaitTimeout = repoCheckoutLockWaitTimeout
}
var result *repocache.WorktreeResult
var err error
if cache, ok := d.repoCache.(interface {
CreateWorktreeContext(context.Context, repocache.WorktreeParams) (*repocache.WorktreeResult, error)
}); ok {
result, err = cache.CreateWorktreeContext(r.Context(), params)
} else {
result, err = d.repoCache.CreateWorktree(params)
}
if err != nil {
if errors.Is(err, repocache.ErrRepoBusy) && req.RetryBusy {
w.Header().Set(repoCheckoutRetryHeader, repoCheckoutRetryValueBusy)
w.Header().Set("Retry-After", fmt.Sprintf("%.0f", repoCheckoutRetryAfter.Seconds()))
http.Error(w, "repository is busy with another operation; retry later", http.StatusServiceUnavailable)
return
}
if r.Context().Err() != nil {
d.logger.Debug("repo checkout cancelled", "url", req.URL, "error", err)
return
}
d.logger.Error("repo checkout failed", "url", req.URL, "error", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(result)
}
}
View on GitHub (pinned to 2c0912b6ec)
Solutions
- Honor the Retry-After header and resend the identical request after the indicated delay.
- Reduce concurrency: queue checkouts per repository instead of firing them in parallel.
- If checkouts of the same repo are frequent, pre-warm the cache (trigger one clone) so later checkouts are short worktree operations less likely to collide.
- If busy errors persist far beyond Retry-After, inspect daemon logs for a stuck operation holding the repo lock.
Example fix
// before: fire-and-forget concurrent checkouts
for _, t := range tasks { go checkout(t) } // some get 503 busy
// after: honor Retry-After and retry
resp, err := client.Do(req)
if resp.StatusCode == http.StatusServiceUnavailable {
ra, _ := strconv.Atoi(resp.Header.Get("Retry-After"))
time.Sleep(time.Duration(ra) * time.Second)
resp, err = client.Do(req)
} Defensive patterns
Strategy: retry
Try / catch
for attempt := 0; attempt < max; attempt++ {
resp, _ := client.Do(req.Clone(ctx))
if resp.StatusCode != http.StatusServiceUnavailable { break }
ra, _ := strconv.Atoi(resp.Header.Get("Retry-After"))
select {
case <-time.After(time.Duration(ra) * time.Second):
case <-ctx.Done(): return ctx.Err()
}
} Prevention
- Always send retry_busy=true when you can tolerate a retry.
- Serialize checkouts per repository client-side with a keyed mutex/queue.
- Pre-warm the repo cache after clone so later operations are short.
When it happens
Trigger: POST repo checkout with retry_busy=true while another checkout, fetch, or clone is in flight for the same repository; the repo cache serializes per-repo operations and the second caller gets ErrRepoBusy.
Common situations: Multiple agents/tasks checking out the same repo concurrently; a long initial clone blocking subsequent checkouts; CI-style parallel runs against one repository.
Related errors
- ErrRepoBusy
- checkout failed: %s
- daemon has %d active %s; setup saved the new configuration b
- repo is configured but not synced: %s
- repo cache not initialized
AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15).
Data as JSON: /api/errors/cd6bae942bba22ac.
Report an issue: GitHub.