{"record":{"id":"8ee2ca27cbca550e","repo":"juanfont/headscale","slug":"removing-test-container-w","errorCode":null,"errorMessage":"removing test container: %w","messagePattern":"removing test container: %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"cmd/hi/cleanup.go","lineNumber":42,"sourceCode":"\tif err != nil {\n\t\treturn fmt.Errorf(\"cleaning stale test containers: %w\", err)\n\t}\n\n\tif err := pruneDockerNetworks(ctx); err != nil { //nolint:noinlineerr\n\t\treturn fmt.Errorf(\"pruning networks: %w\", err)\n\t}\n\n\treturn nil\n}\n\n// cleanupAfterTest removes the test container and all associated integration test containers for the run.\nfunc cleanupAfterTest(ctx context.Context, cli *client.Client, containerID, runID string) error {\n\t// Remove the main test container\n\terr := cli.ContainerRemove(ctx, containerID, container.RemoveOptions{\n\t\tForce: true,\n\t})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"removing test container: %w\", err)\n\t}\n\n\t// Clean up integration test containers for this run only\n\tif runID != \"\" {\n\t\terr := killTestContainersByRunID(ctx, runID)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"cleaning up containers for run %s: %w\", runID, err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// killTestContainers terminates and removes all test containers.\nfunc killTestContainers(ctx context.Context) error {\n\tcli, err := createDockerClient(ctx)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"creating Docker client: %w\", err)","sourceCodeStart":24,"sourceCodeEnd":60,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/cmd/hi/cleanup.go#L24-L60","documentation":"Returned by cleanupAfterTest when cli.ContainerRemove with Force:true fails for the main test-suite container (headscale-test-suite-<runID>). Docker returns 404 if the container already vanished, 409 if it cannot be force-removed at this moment, and connection errors if the daemon is gone. Because this runs after a test finishes, it commonly masks (chains after) the test's own result.","triggerScenarios":"The test container exited and was garbage-collected by another cleanup process (404 Not Found); concurrent `hi` runs racing to remove the same container; daemon shutdown during teardown; daemon connectivity loss at the end of a long test.","commonSituations":"Running two `hi` invocations against the same Docker host; aggressive external cleanup scripts (docker system prune -f on a timer) removing stopped containers; Docker Desktop restarting mid-run; CI agents reaping resources.","solutions":["Check the wrapped text: 'No such container' means it was already removed — the test result itself is unaffected","Avoid running concurrent cleanup jobs (cron'd docker system prune) while integration tests run","Retry the hi command if the daemon was transiently unavailable","Use `hi cleanup` / `hi kill` deliberately instead of ad-hoc docker commands so run-ID labels stay consistent"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Before manual removal, check existence — mirroring what hi should tolerate.\n// (For hi users: prefer `hi kill` over docker rm.)","typeGuard":null,"tryCatchPattern":"In Go, treat errdefs.IsNotFound(err) from ContainerRemove as success (already gone); retry once on 409/500; propagate real connectivity errors. hi users: read the wrapped cause — 'No such container' can be ignored.","preventionTips":["Do not run concurrent external cleanup (docker system prune cron) during test runs","Use `hi kill`/`hi cleanup` instead of ad-hoc docker commands","Expect teardown errors to chain after the test result — always check the first error in the chain"],"tags":["docker","container-removal","teardown","integration-tests"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}