{"record":{"id":"71104fcf29e5542f","repo":"amir20/dozzle","slug":"container-id-is-required","errorCode":null,"errorMessage":"container_id is required","messagePattern":"container_id is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/cloud/tools_resolve.go","lineNumber":142,"sourceCode":"\t\t// replicas (pick the newest live one).\n\t\tif live := runningContainers(tier); len(live) == 1 {\n\t\t\treturn live[0].Host, live[0].ID, \"\", nil\n\t\t}\n\t\tbest := bestCandidate(tier)\n\t\tnote = resolutionNote(strings.TrimSpace(containerRef), best, tier, hostNames)\n\t\treturn best.Host, best.ID, note, nil\n\t}\n}\n\n// matchContainerTier runs the shared scoping + tiered matching used by both\n// resolvers and returns the winning tier (the first non-empty of exact-id,\n// exact-name, substring). It also returns the trimmed hostRef, the resolved\n// scoped host id (empty when no host was supplied), and the host-name map for\n// building human-readable notes/errors. An empty returned tier means no match.\nfunc matchContainerTier(containerRef, hostRef string, deps ToolDeps) (tier []container.Container, trimmedHostRef, scopedHostID string, hostNames map[string]string, err error) {\n\tcontainerRef = strings.TrimSpace(containerRef)\n\tif containerRef == \"\" {\n\t\treturn nil, \"\", \"\", nil, fmt.Errorf(\"container_id is required\")\n\t}\n\n\tcontainers, errs := deps.HostService.ListAllContainers(deps.Labels)\n\tlogHostErrors(errs)\n\thostNames = buildHostNameMap(deps.HostService)\n\n\t// Scope to a host if one was supplied. The host reference may be an id or a\n\t// name; an unknown host is an explicit error rather than a silent no-match.\n\ttrimmedHostRef = strings.TrimSpace(hostRef)\n\tif trimmedHostRef != \"\" {\n\t\tscopedHostID, err = resolveHostRef(trimmedHostRef, deps)\n\t\tif err != nil {\n\t\t\treturn nil, trimmedHostRef, \"\", hostNames, err\n\t\t}\n\t\tfiltered := containers[:0:0]\n\t\tfor _, c := range containers {\n\t\t\tif c.Host == scopedHostID {\n\t\t\t\tfiltered = append(filtered, c)","sourceCodeStart":124,"sourceCodeEnd":160,"githubUrl":"https://github.com/amir20/dozzle/blob/d9463cbe21874e44ab79db6fa63e746ca7d22928/internal/cloud/tools_resolve.go#L124-L160","documentation":"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.","triggerScenarios":"Any container tool (actions, update, inspect, logs) invoked with container_id omitted, null, or an empty/whitespace-only string.","commonSituations":"Tool-call argument assembly dropping an empty variable; LLM omitting the parameter; scripts interpolating an unset env var so the field arrives as ''.","solutions":["Always pass a non-empty container_id string","Resolve the id first with find_containers if unknown","Check argument assembly for variables that may expand to empty strings"],"exampleFix":"// before\nconst id = process.env.CONTAINER_ID || \"\"\nfetchLogs({container_id: id})\n// after\nif (!process.env.CONTAINER_ID) throw new Error(\"CONTAINER_ID must be set\")\nfetchLogs({container_id: process.env.CONTAINER_ID})","handlingStrategy":"validation","validationCode":"if (typeof containerId !== 'string' || containerId.trim() === '') throw new Error('container_id must be a non-empty string');","typeGuard":"function isNonEmptyString(v) { return typeof v === 'string' && v.trim().length > 0; }\n// guard: isNonEmptyString(args.container_id)","tryCatchPattern":null,"preventionTips":["Validate arguments before every container tool call","Guard against env vars / form inputs expanding to empty strings","Resolve unknown ids via find_containers first"],"tags":["cloud-tools","validation","missing-argument"],"backgroundTag":"missing-required-argument","analyzedSha":"d9463cbe21874e44ab79db6fa63e746ca7d22928","analyzedAt":"2026-09-07T10:08:55.855Z","contentChangedAt":"2026-09-07T10:08:55.855Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}