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

  1. Honor the Retry-After header and resend the identical request after the indicated delay.
  2. Reduce concurrency: queue checkouts per repository instead of firing them in parallel.
  3. 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.
  4. 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

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


AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15). Data as JSON: /api/errors/cd6bae942bba22ac. Report an issue: GitHub.