{"record":{"id":"2d8fd85a6551d7dc","repo":"juanfont/headscale","slug":"writing-stderr-log-w","errorCode":null,"errorMessage":"writing stderr log: %w","messagePattern":"writing stderr log: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/hi/docker.go","lineNumber":810,"sourceCode":"\tstderrPath := filepath.Join(logsDir, containerName+\".stderr.log\")\n\n\t// Create buffers to capture stdout and stderr separately\n\tvar stdoutBuf, stderrBuf bytes.Buffer\n\n\t// Demultiplex the Docker logs stream to separate stdout and stderr\n\t_, err = stdcopy.StdCopy(&stdoutBuf, &stderrBuf, logReader)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"demultiplexing container logs: %w\", err)\n\t}\n\n\t// Write stdout logs\n\tif err := os.WriteFile(stdoutPath, stdoutBuf.Bytes(), 0o644); err != nil { //nolint:gosec,noinlineerr // log files should be readable\n\t\treturn fmt.Errorf(\"writing stdout log: %w\", err)\n\t}\n\n\t// Write stderr logs\n\tif err := os.WriteFile(stderrPath, stderrBuf.Bytes(), 0o644); err != nil { //nolint:gosec,noinlineerr // log files should be readable\n\t\treturn fmt.Errorf(\"writing stderr log: %w\", err)\n\t}\n\n\tif verbose {\n\t\tlog.Printf(\"Saved logs for %s: %s, %s\", containerName, stdoutPath, stderrPath)\n\t}\n\n\treturn nil\n}\n","sourceCodeStart":792,"sourceCodeEnd":819,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/cmd/hi/docker.go#L792-L819","documentation":"os.WriteFile failed while writing the demultiplexed stderr log to <logsDir>/<containerName>.stderr.log. Identical failure class to the stdout write: missing directory, permission denied, or disk full. Note this happens after the stdout file was already written, so a disk-full failure here means you got a partial artifact set.","triggerScenarios":"os.WriteFile(stderrPath, ...) failing with EACCES, ENOSPC, or ENOENT after stdoutPath was written successfully — typically the last write that exhausts remaining disk space.","commonSituations":"Disk exactly filling up between the stdout and stderr writes; directory permissions changed mid-run; stderr output being much larger than stdout (hs-*.stderr.log is where headscale diagnostics go, and AGENTS.md says to read hs-*.stderr.log first when diagnosing flakes).","solutions":["Free disk space (prune control_logs/) and re-run — partial artifacts without the stderr log make diagnosis hard","Check permissions on control_logs/<runID>/","If stderr is huge because a container is crash-looping, inspect `docker logs <container>` directly to find the underlying test failure"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Same directory-writability probe as the stdout write; run once before extraction\nif err := os.MkdirAll(logsDir, 0o755); err != nil { return err }\nif free := diskFree(logsDir); free < 250*1024*1024 { // runs produce ~100 MB\n    return fmt.Errorf(\"insufficient disk space: %d bytes free\", free)\n}","typeGuard":null,"tryCatchPattern":"if err := os.WriteFile(stderrPath, stderrBuf.Bytes(), 0o644); err != nil {\n    if errors.Is(err, syscall.ENOSPC) {\n        // stderr is the most valuable artifact (hs-*.stderr.log); try a tmpfs fallback\n        _ = os.WriteFile(filepath.Join(os.TempDir(), containerName+\".stderr.log\"), stderrBuf.Bytes(), 0o644)\n    }\n    return fmt.Errorf(\"writing stderr log: %w\", err)\n}","preventionTips":["Reserve disk headroom for both log files before extraction","hs-*.stderr.log is the primary debugging artifact — never drop it silently on write failure","Monitor control_logs/ growth in long-lived CI workers"],"tags":["filesystem","disk-full","logging","integration-tests"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}