multica-ai/multica · error

connect to daemon: %w

Error message

connect to daemon: %w

What it means

The first "connect to daemon" error site: client.Do(req) failed while POSTing to http://127.0.0.1:$MULTICA_DAEMON_PORT/repo/checkout. This is a transport-level failure — connection refused, no route, or the context deadline (5 minutes) expiring during the request itself. The retry loop only re-issues on HTTP 503 with X-Multica-Retryable: repo-busy, so transport errors abort immediately.

Source

Thrown at server/cmd/multica/cmd_repo.go:386

	parentCtx := cmd.Context()
	if parentCtx == nil {
		parentCtx = context.Background()
	}
	ctx, cancel := context.WithTimeout(parentCtx, 5*time.Minute)
	defer cancel()
	client := &http.Client{}
	checkoutURL := fmt.Sprintf("http://127.0.0.1:%s/repo/checkout", daemonPort)
	var body []byte
	for {
		req, err := http.NewRequestWithContext(ctx, http.MethodPost, checkoutURL, bytes.NewReader(data))
		if err != nil {
			return fmt.Errorf("create daemon checkout request: %w", err)
		}
		req.Header.Set("Content-Type", "application/json")
		resp, err := client.Do(req)
		if err != nil {
			return fmt.Errorf("connect to daemon: %w", err)
		}
		body, err = io.ReadAll(resp.Body)
		closeErr := resp.Body.Close()
		if err != nil {
			return fmt.Errorf("read daemon checkout response: %w", err)
		}
		if closeErr != nil {
			return fmt.Errorf("close daemon checkout response: %w", closeErr)
		}
		if resp.StatusCode == http.StatusServiceUnavailable && resp.Header.Get("X-Multica-Retryable") == "repo-busy" {
			delay := repoCheckoutRetryDelay(resp.Header.Get("Retry-After"), time.Now())
			timer := time.NewTimer(delay)
			select {
			case <-ctx.Done():
				timer.Stop()
				return fmt.Errorf("connect to daemon: %w", context.Cause(ctx))
			case <-timer.C:
				continue

View on GitHub (pinned to 2c0912b6ec)

Solutions

  1. Confirm the daemon is up and listening on the expected port: ss -ltnp | grep <port> or curl http://127.0.0.1:<port>/health.
  2. If the daemon restarted with a new port, re-run the command in a fresh daemon task so MULTICA_DAEMON_PORT is re-injected.
  3. If checkouts legitimately take >5 min, raise the context timeout in the CLI or reduce the checkout scope (smaller repo/ref) — note the timeout is hardcoded to 5*time.Minute.
  4. Check the message chain: "connection refused" means no listener; "context deadline exceeded" means timeout.

Example fix

# before: stale port from an old daemon
MULTICA_DAEMON_PORT=8123 multica repo checkout URL   # connection refused

# after: verify listener, re-run inside a live daemon task
ss -ltnp | grep 8123   # nothing? restart daemon / rerun task
Defensive patterns

Strategy: retry

Validate before calling

# fail fast if nothing listens on the daemon port before the long command
nc -z 127.0.0.1 "$MULTICA_DAEMON_PORT" || { echo 'daemon not listening' >&2; exit 1; }

Try / catch

resp, err := client.Do(req)
if err != nil {
	if errors.Is(err, context.DeadlineExceeded) {
		// 5-min budget exhausted mid-request: retry with a fresh context or bail
	}
	return fmt.Errorf("connect to daemon: %w", err)
}

Prevention

When it happens

Trigger: Nothing listening on 127.0.0.1:$MULTICA_DAEMON_PORT (daemon not running or different port); daemon crashed between env injection and the call; wrong port value; the 5-minute ctx timeout elapsing while the request is in flight.

Common situations: Agent outlives its daemon (daemon restarted, old port captured in env); port mismatch after daemon reconfig; localhost unreachable in a network-namespace-restricted sandbox; very slow checkouts exceeding the 5-minute budget.

Related errors


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