sipeed/picoclaw · error

failed to create log directory: %w

Error message

failed to create log directory: %w

What it means

EnableFileLogging first ensures the parent directory of the requested log file exists via os.MkdirAll(filepath.Dir(filePath), 0o755). If that mkdir fails, the wrapped OS error is returned and file logging is never enabled (the package keeps its previous sink; the mutex-guarded state is unchanged).

Source

Thrown at pkg/logger/logger.go:181

}

// SetLevelFromString sets the log level from a string value.
// If the string is empty or not a recognized level name, the current level is kept.
func SetLevelFromString(s string) {
	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)
	}

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Choose a log path under a directory the process can write to (e.g. within the instance/data dir)
  2. Pre-create the directory with correct ownership (install -d -o app /var/log/app)
  3. Remove any regular file occupying a needed directory component
  4. Use an absolute filePath so the parent dir does not depend on the working directory

Example fix

# before
EnableFileLogging("/var/log/picoclaw/app.log")  # unprivileged user

# after
EnableFileLogging("/home/app/.picoclaw/logs/app.log")
Defensive patterns

Strategy: validation

Validate before calling

func logDirReady(path string) bool {
    dir := filepath.Dir(path)
    if err := os.MkdirAll(dir, 0o755); err != nil {
        return false
    }
    probe := filepath.Join(dir, ".probe")
    if err := os.WriteFile(probe, nil, 0o644); err != nil {
        return false
    }
    _ = os.Remove(probe)
    return true
}

Try / catch

if err := logger.EnableFileLogging(path); err != nil {
    // keep the process alive on stdout/stderr logging, but surface the misconfiguration
    fmt.Fprintf(os.Stderr, "file logging unavailable (%v); continuing on stderr\n", err)
}

Prevention

When it happens

Trigger: (1) EACCES: log directory path under a root-owned or non-writable parent (e.g. /var/log without privileges); (2) ENOTDIR: a path component is a regular file ("/tmp/x" is a file, log path "/tmp/x/app.log"); (3) EROFS: read-only filesystem; (4) relative filePath whose parent resolves against an unexpected cwd; (5) ENAMETOOLONG.

Common situations: Pointing log_file at /var/log/... while running unprivileged; running under systemd without WriteDirectories/permissions; a stale file squatting on a directory name; read-only container mounts for config-derived log paths.

Related errors


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