amir20/dozzle · error

container_id is required

Error message

container_id is required

What it means

matchContainerTier trims the containerRef and rejects an empty string up front with 'container_id is required', before any host listing occurs. Both the strict and read resolvers route through this guard.

Solutions

  1. Always pass a non-empty container_id string
  2. Resolve the id first with find_containers if unknown
  3. Check argument assembly for variables that may expand to empty strings

Example fix

// before
const id = process.env.CONTAINER_ID || ""
fetchLogs({container_id: id})
// after
if (!process.env.CONTAINER_ID) throw new Error("CONTAINER_ID must be set")
fetchLogs({container_id: process.env.CONTAINER_ID})
Defensive patterns

Strategy: validation

Validate before calling

if (typeof containerId !== 'string' || containerId.trim() === '') throw new Error('container_id must be a non-empty string');

Type guard

function isNonEmptyString(v) { return typeof v === 'string' && v.trim().length > 0; }
// guard: isNonEmptyString(args.container_id)

Prevention

When it happens

Trigger: Any container tool (actions, update, inspect, logs) invoked with container_id omitted, null, or an empty/whitespace-only string.

Common situations: Tool-call argument assembly dropping an empty variable; LLM omitting the parameter; scripts interpolating an unset env var so the field arrives as ''.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


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

Appendix: source

Thrown at internal/cloud/tools_resolve.go:142

		// replicas (pick the newest live one).
		if live := runningContainers(tier); len(live) == 1 {
			return live[0].Host, live[0].ID, "", nil
		}
		best := bestCandidate(tier)
		note = resolutionNote(strings.TrimSpace(containerRef), best, tier, hostNames)
		return best.Host, best.ID, note, nil
	}
}

// matchContainerTier runs the shared scoping + tiered matching used by both
// resolvers and returns the winning tier (the first non-empty of exact-id,
// exact-name, substring). It also returns the trimmed hostRef, the resolved
// scoped host id (empty when no host was supplied), and the host-name map for
// building human-readable notes/errors. An empty returned tier means no match.
func matchContainerTier(containerRef, hostRef string, deps ToolDeps) (tier []container.Container, trimmedHostRef, scopedHostID string, hostNames map[string]string, err error) {
	containerRef = strings.TrimSpace(containerRef)
	if containerRef == "" {
		return nil, "", "", nil, fmt.Errorf("container_id is required")
	}

	containers, errs := deps.HostService.ListAllContainers(deps.Labels)
	logHostErrors(errs)
	hostNames = buildHostNameMap(deps.HostService)

	// Scope to a host if one was supplied. The host reference may be an id or a
	// name; an unknown host is an explicit error rather than a silent no-match.
	trimmedHostRef = strings.TrimSpace(hostRef)
	if trimmedHostRef != "" {
		scopedHostID, err = resolveHostRef(trimmedHostRef, deps)
		if err != nil {
			return nil, trimmedHostRef, "", hostNames, err
		}
		filtered := containers[:0:0]
		for _, c := range containers {
			if c.Host == scopedHostID {
				filtered = append(filtered, c)

View on GitHub (pinned to d9463cbe21)