juanfont/headscale · error

writing stdout log: %w

Error message

writing stdout log: %w

What it means

os.WriteFile failed while writing the demultiplexed stdout log to <logsDir>/<containerName>.stdout.log. This is a plain filesystem error: the logs directory does not exist at write time, permissions on the directory are wrong, the path is not writable, or the disk is full. Integration runs write ~100 MB of logs, so ENOSPC is a common cause.

Source

Thrown at cmd/hi/docker.go:805

	}
	defer logReader.Close()

	// Create log files following the headscale naming convention
	stdoutPath := filepath.Join(logsDir, containerName+".stdout.log")
	stderrPath := filepath.Join(logsDir, containerName+".stderr.log")

	// Create buffers to capture stdout and stderr separately
	var stdoutBuf, stderrBuf bytes.Buffer

	// Demultiplex the Docker logs stream to separate stdout and stderr
	_, err = stdcopy.StdCopy(&stdoutBuf, &stderrBuf, logReader)
	if err != nil {
		return fmt.Errorf("demultiplexing container logs: %w", err)
	}

	// Write stdout logs
	if err := os.WriteFile(stdoutPath, stdoutBuf.Bytes(), 0o644); err != nil { //nolint:gosec,noinlineerr // log files should be readable
		return fmt.Errorf("writing stdout log: %w", err)
	}

	// Write stderr logs
	if err := os.WriteFile(stderrPath, stderrBuf.Bytes(), 0o644); err != nil { //nolint:gosec,noinlineerr // log files should be readable
		return fmt.Errorf("writing stderr log: %w", err)
	}

	if verbose {
		log.Printf("Saved logs for %s: %s, %s", containerName, stdoutPath, stderrPath)
	}

	return nil
}

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Check `df -h .` and prune old runs under control_logs/ — runs generate ~100 MB of logs each
  2. Verify write permission on the target directory: `ls -ld control_logs/<runID>` and fix ownership if it differs from the current user
  3. Ensure nothing deletes logsDir between test start and artifact extraction (concurrent cleanup job)
  4. Re-run the test after freeing space
Defensive patterns

Strategy: validation

Validate before calling

// Verify the target is writable before the run starts
if err := os.MkdirAll(logsDir, 0o755); err != nil { return err }
probe := filepath.Join(logsDir, ".writeprobe")
if err := os.WriteFile(probe, nil, 0o644); err != nil { return err }
os.Remove(probe)

Try / catch

if err := os.WriteFile(stdoutPath, stdoutBuf.Bytes(), 0o644); err != nil {
    if errors.Is(err, syscall.ENOSPC) {
        // disk full: fall back to stderr-only or a summary so the run still reports
    }
    return fmt.Errorf("writing stdout log: %w", err)
}

Prevention

When it happens

Trigger: os.WriteFile(stdoutPath, ...) failing with ENOENT (logsDir removed between MkdirAll and write), EACCES/EPERM (directory owned by another user), or ENOSPC (disk full after ~100 MB of logs from prior runs).

Common situations: control_logs/ filling the disk or a tmpfs-backed workspace running out of space; running `hi` under a different user than the one that created control_logs/ (permission mismatch); the workspace directory being cleaned concurrently.

Related errors


AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15). Data as JSON: /api/errors/f6dfbe080d3b8a7d. Report an issue: GitHub.