sipeed/picoclaw · error

failed to create log file: %s

Error message

failed to create log file: %s

What it means

After the directory is ensured, InitPanic delegates to the platform-specific initPanicFile(filePath) (panic_unix.go / panic_win.go), which opens the panic log file and returns nil on any open failure. This error is raised when that writer comes back nil — the message prints only the path, so the actual OS error (permissions, EISDIR, EROFS, name too long) is swallowed by the platform helper and must be diagnosed externally.

Source

Thrown at pkg/logger/panic.go:20

import (
	"fmt"
	"io"
	"os"
	"path/filepath"
	"runtime/debug"
	"time"
)

var panicWriter io.WriteCloser

func InitPanic(filePath string) (func(), error) {
	if err := os.MkdirAll(filepath.Dir(filePath), 0o755); err != nil {
		return nil, fmt.Errorf("failed to create log directory: %w", err)
	}
	writer := initPanicFile(filePath)
	if writer == nil {
		return nil, fmt.Errorf("failed to create log file: %s", filePath)
	}
	if panicWriter != nil {
		_ = panicWriter.Close()
	}
	panicWriter = writer
	return func() {
		defer func() {
			writer.Close()
			panicWriter = nil
		}()
		if err := recover(); err != nil {
			RecoverPanicNoExit(err)

			os.Exit(1)
		}
	}, nil
}

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Manually test writability of the exact path as the running user (touch <path>) to surface the real OS error
  2. Fix ownership/permissions of the existing file or delete it so it can be recreated
  3. Choose a writable absolute path on a writable filesystem
  4. If diagnosing a hardened host, check audit logs (SELinux/AppArmor) for the denied open

Example fix

# before
InitPanic("/var/log/picoclaw/panic.log")  # returns nil-writer error, cause hidden

# after: reproduce the hidden error, then fix ownership
$ sudo -u appuser touch /var/log/picoclaw/panic.log
# touch: cannot touch ...: Permission denied
$ sudo chown appuser /var/log/picoclaw/panic.log
Defensive patterns

Strategy: validation

Validate before calling

// reproduce the hidden open error before relying on panic capture
f, err := os.OpenFile(panicPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
if err != nil {
    return fmt.Errorf("panic log %s not openable (real cause: %w)", panicPath, err)
}
f.Close()

Try / catch

if _, err := logger.InitPanic(panicPath); err != nil {
    // message only carries the path — the errno is lost, so probe the path directly for diagnosis
    fmt.Fprintf(os.Stderr, "panic capture failed for %s; probe the path for the real error: %v\n", panicPath, err)
}

Prevention

When it happens

Trigger: The file open inside initPanicFile failing: existing panic.log owned by another user (prior sudo run), filePath naming a directory, read-only filesystem, SELinux/AppArmor denial, ENAMETOOLONG. Because the helper discards the errno, callers only learn that creation failed for that path.

Common situations: Root-owned panic.log after earlier privileged runs; log location on a read-only volume; MAC (SELinux) blocking the open with no errno surfaced to the caller.

Related errors


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