wavetermdev/waveterm · error
failed to open log file: %w
Error message
failed to open log file: %w
What it means
daemonize opens the job log file (O_CREATE|O_WRONLY|O_APPEND, 0600) and this error wraps the os.OpenFile failure. The daemon's stdout/stderr will be redirected to this file, so the job cannot start logging until it opens successfully.
Source
Thrown at pkg/jobmanager/jobmanager_unix.go:45
if err != nil {
return fmt.Errorf("failed to open /dev/null: %w", err)
}
err = unix.Dup2(int(devNull.Fd()), int(os.Stdin.Fd()))
if err != nil {
return fmt.Errorf("failed to dup2 stdin: %w", err)
}
devNull.Close()
logPath := wavebase.GetRemoteJobFilePath(jobId, "log")
logDir := filepath.Dir(logPath)
err = os.MkdirAll(logDir, 0700)
if err != nil {
return fmt.Errorf("failed to create log directory: %w", err)
}
logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
if err != nil {
return fmt.Errorf("failed to open log file: %w", err)
}
err = unix.Dup2(int(logFile.Fd()), int(os.Stdout.Fd()))
if err != nil {
return fmt.Errorf("failed to dup2 stdout: %w", err)
}
err = unix.Dup2(int(logFile.Fd()), int(os.Stderr.Fd()))
if err != nil {
return fmt.Errorf("failed to dup2 stderr: %w", err)
}
log.SetOutput(logFile)
log.Printf("job manager daemonized, logging to %s\n", logPath)
log.Printf("job owner clientid: %s\n", clientId)
signal.Ignore(syscall.SIGHUP)
return nil
}View on GitHub (pinned to a4447c1563)
Solutions
- Verify the parent directory exists and is writable by the current uid (it is just created by MkdirAll 0700)
- Remove or chown a stale log file owned by another uid: ls -l <logPath>
- Free disk space / check quota if errno is ENOSPC
- Shorten the jobId/path if NAME_MAX (255 bytes per component) is exceeded
Example fix
// before
logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
if err != nil {
return fmt.Errorf("failed to open log file: %w", err)
}
// after
logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
if err != nil {
if errors.Is(err, syscall.EACCES) {
os.Remove(logPath) // drop stale file owned by another uid
logFile, err = os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
}
if err != nil {
return fmt.Errorf("failed to open log file %s: %w", logPath, err)
}
} Defensive patterns
Strategy: retry
Validate before calling
logPath := wavebase.GetRemoteJobFilePath(jobId, "log")
if len(filepath.Base(logPath)) > 255 {
return fmt.Errorf("log file name exceeds NAME_MAX")
}
if fi, err := os.Stat(filepath.Dir(logPath)); err != nil || !fi.IsDir() {
return fmt.Errorf("log directory missing/unwritable")
} Try / catch
if err := daemonize(clientId, jobId); err != nil {
if strings.Contains(err.Error(), "failed to open log file") {
log.Printf("log open error: %v", errors.Unwrap(err))
// EACCES: remove/chown stale file; ENOSPC: free space; then retry daemonize
}
return err
} Prevention
- Run all job processes under a consistent uid so log files stay writable
- Monitor disk usage of the job log directory
- Keep job ids short to avoid path-length limits
- Clean up stale log files with wrong ownership/ACLs at startup
When it happens
Trigger: os.OpenFile(logPath, O_CREATE|O_WRONLY|O_APPEND, 0600) fails: log directory not writable, ENOSPC, path exceeds NAME_MAX, or the path exists with ownership/permissions preventing the current uid from writing.
Common situations: Log directory owned by a different uid after a prior run under root/sudo; quota or disk-full on the data volume; overly long jobId inflating the file path; immutable or ACL-restricted file left from a previous session.
Related errors
- failed to create log directory: %w
- failed to create backup directory: %w
- failed to create socket directory: %w
- failed to open /dev/null: %w
- failed to read directory: %w
AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01).
Data as JSON: /api/errors/f9232ae2ed36e9e7.
Report an issue: GitHub.