sipeed/picoclaw · error

failed to open log file: %w

Error message

failed to open log file: %w

What it means

After the log directory is created, EnableFileLogging opens the file with os.OpenFile(filePath, O_CREATE|O_WRONLY|O_APPEND, 0o644); failure is wrapped here. Common errnos: EACCES (file or parent not writable — e.g. the file exists but is owned by root from a previous sudo run), EISDIR (filePath names a directory), EROFS, ENAMETOOLONG. Note: on the success path the function also contains a latent bug — the later "failed to configure file logging" check wraps the already-nil err — so that companion message can never carry a cause.

Source

Thrown at pkg/logger/logger.go:186

	if s == "" {
		return
	}
	if level, ok := ParseLevel(s); ok {
		SetLevel(level)
	}
}

func EnableFileLogging(filePath string) error {
	mu.Lock()
	defer mu.Unlock()

	if err := os.MkdirAll(filepath.Dir(filePath), 0o755); err != nil {
		return fmt.Errorf("failed to create log directory: %w", err)
	}

	newFile, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
	if err != nil {
		return fmt.Errorf("failed to open log file: %w", err)
	}

	// Close old file if exists
	if logFile != nil {
		logFile.Close()
	}

	logFile = newFile

	if len(writers) != 1 {
		return fmt.Errorf("failed to configure file logging: %w", err)
	}

	writers = append(writers, logFile)
	logger = logger.Output(io.MultiWriter(writers...))

	return nil
}

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Check ownership of the existing file (ls -l) and chown/delete it so the current user can append
  2. Confirm filePath is a file location, not a directory
  3. Move logging to a writable volume/directory and use an absolute path
  4. If the filesystem is read-only by design, configure logging to stdout instead

Example fix

# before
$ sudo picoclaw ...   # creates /var/log/picoclaw/app.log as root
$ picoclaw ...         # fails: permission denied

# after
$ sudo rm /var/log/picoclaw/app.log   # or: chown $(id -u) /var/log/picoclaw/app.log
Defensive patterns

Strategy: try-catch

Validate before calling

// verify the exact file can be opened for append before wiring the logger
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
if err != nil {
    return fmt.Errorf("log path not usable (%w); fix ownership/mount or use stdout logging", err)
}
f.Close()

Try / catch

if err := logger.EnableFileLogging(path); err != nil {
    var errno syscall.Errno
    if errors.As(err, &errno) && errno == syscall.EACCES {
        // stale root-owned log from a previous privileged run
        return fmt.Errorf("log file %s not writable by uid %d — remove or chown it", path, os.Getuid())
    }
    fmt.Fprintf(os.Stderr, "file logging unavailable: %v\n", err)
}

Prevention

When it happens

Trigger: Re-running the app unprivileged after it once ran as root and created the log file 0644 root:root; filePath pointing at a directory; read-only mount; SELinux denial on the log location; path exceeds NAME_MAX.

Common situations: sudo-run processes leaving root-owned logs; containers with read-only root filesystems; log paths under mounted volumes with restrictive ownership; rotating tools replacing the file with a directory.

Related errors


AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15). Data as JSON: /api/errors/ff1abed889878110. Report an issue: GitHub.