amir20/dozzle · error

matches multiple containers: . To act on the right one

Error message

%q matches multiple containers: %s. To act on the right one, %s

What it means

ambiguousError is produced by resolveContainerRef when a container name/id prefix matches multiple containers. It lists each candidate (host-qualified) and adds a context-specific hint telling the LLM caller exactly how to disambiguate, since acting on the wrong container would be destructive.

Solutions

  1. Re-issue the call passing host_id to scope to a single host (per the hint when candidates span hosts)
  2. Pass the full container id or the exact full container name instead of a prefix/short name
  3. Clean up stale stopped containers so only one live match remains

Example fix

// before
resolveContainerRef(ctx, deps, "web")
// after
resolveContainerRef(ctx, deps, "a1b2c3d4e5f6") // full id, or pass host_id: "abc123" alongside the name
Defensive patterns

Strategy: validation

Validate before calling

matches := findContainersByName(ref)
if len(matches) > 1 { resolve with host_id or full id before acting }

Type guard

func isFullContainerID(ref string) bool {
  return len(ref) >= 32 && !strings.Contains(ref, "/")
}

Prevention

When it happens

Trigger: resolveContainerRef's name lookup returns several containers and the runningContainers tie-breaker did not reduce them to one; raised in the final return of ambiguousError. Triggered by tool calls using a short name or id prefix shared by multiple containers (possibly across hosts).

Common situations: Swarm redeploy leaves old and new replicas of the same service name; duplicate container names across hosts in a multi-host setup; id-prefix that is too short to be unique.

Related errors


AI-assisted analysis of amir20/dozzle@d9463cbe21 (2026-09-07). Data as JSON: /api/errors/02ab1a3346e10b1f. Report an issue: GitHub.

Appendix: source

Thrown at internal/cloud/tools_resolve.go:259

	parts := make([]string, len(candidates))
	sameHost := true
	for i, c := range candidates {
		parts[i] = fmt.Sprintf("%s (id %s, %s, on host %s)", c.Name, shortID(c.ID), describeState(c), resolveHostName(c.Host, hostNames))
		if c.Host != candidates[0].Host {
			sameHost = false
		}
	}

	var hint string
	switch {
	case hostRef != "" || sameHost:
		// A host was already supplied, or every candidate is on the same host —
		// scoping by host_id cannot narrow it further.
		hint = "pass the exact container id or the full container name to disambiguate"
	default:
		hint = "pass host_id to scope to one host, or pass the exact container id"
	}
	return fmt.Errorf("%q matches multiple containers: %s. To act on the right one, %s", containerRef, strings.Join(parts, "; "), hint)
}

// runningContainers returns only the candidates in the running state. It breaks
// a multi-match tie down to the single live container when every other candidate
// is a stopped corpse (the typical Swarm-redeploy / restart-churn case), which
// is the one situation where picking among matches is unambiguous and safe.
func runningContainers(cs []container.Container) []container.Container {
	live := make([]container.Container, 0, len(cs))
	for _, c := range cs {
		if strings.EqualFold(c.State, "running") {
			live = append(live, c)
		}
	}
	return live
}

// bestCandidate picks the single most-relevant container from an ambiguous name
// match for the read-only path. Ordering: running beats stopped, then newest

View on GitHub (pinned to d9463cbe21)