JuliusBrussee/caveman · error
native session key mkdir
Error message
native session key mkdir: %w
What it means
Returned by nativeruntime.LoadOrCreateSessionKey when os.MkdirAll cannot create the runtime directory under the supplied home; the HMAC session key lives at <home>/runtime/session.key. The wrapped error is the raw *fs.PathError from MkdirAll, and the underlying errno (usually EACCES, EEXIST-on-a-regular-file, EROFS, or ENAMETOOLONG) tells the real story. Startup aborts before any key is minted.
Solutions
- Run ls -ld <home> <home>/runtime and fix ownership: sudo chown -R $(id -u):$(id -g) <home>
- If runtime exists as a regular file, remove it: rm <home>/runtime
- Point CAVEMAN_HOME (or HOME) at a writable directory on a local filesystem
- In containers, mount a writable volume over the caveman home instead of using the image layer
Defensive patterns
Strategy: validation
Validate before calling
func homeIsWritableByCurrentUser(home string) error {
info, err := os.Stat(home)
if err != nil {
return fmt.Errorf("home missing: %w", err)
}
if st, ok := info.Sys().(*syscall.Stat_t); ok && int(st.Uid) != os.Getuid() {
return fmt.Errorf("%s owned by uid %d, not %d — run: sudo chown -R %d:%d %s", home, st.Uid, os.Getuid(), os.Getuid(), os.Getgid(), home)
}
probe := filepath.Join(home, ".write-probe")
if err := os.WriteFile(probe, nil, 0o600); err != nil {
return fmt.Errorf("home not writable: %w", err)
}
return os.Remove(probe)
}
// before nativeruntime.LoadOrCreateSessionKey(home)
if err := homeIsWritableByCurrentUser(home); err != nil {
return err
} Type guard
func isPermissionErr(err error) bool {
return errors.Is(err, fs.ErrPermission)
} Try / catch
key, err := nativeruntime.LoadOrCreateSessionKey(home)
if err != nil {
if errors.Is(err, fs.ErrPermission) {
log.Printf("fix ownership first: sudo chown -R %d:%d %s", os.Getuid(), os.Getgid(), home)
}
return fmt.Errorf("session key startup: %w", err)
} Prevention
- Never launch caveman components under sudo or root cron — mixed uids create the foreign-owned home that produces EACCES
- Pin CAVEMAN_HOME to one writable local path per user and export it in launcher scripts
- Add a startup self-test that stats the home and compares uid before calling LoadOrCreateSessionKey
- In containers, mount a writable volume at the caveman home instead of relying on the image layer
When it happens
Trigger: Calling LoadOrCreateSessionKey(home) when <home> or a parent directory is owned by another user (classic: caveman was run once under sudo, then as the normal user); <home>/runtime already exists as a regular file; home sits on a read-only container layer or a full volume; HOME unset so home resolves to an unwritable path such as /.
Common situations: sudo-first-run leaving a root-owned ~/.caveman; CI containers with a read-only root filesystem; CAVEMAN_HOME pointing at a mount that vanished or is quota-exceeded; portable installs on media mounted read-only.
Understand the failure class
Background: mkdir permission denied (EACCES): failed to create directory errors explained — this error's family across 32 libraries.
Related errors
- native session key chmod dir
- native session key chmod
- native session key create
- sqlite parent is group/world writable
- : : inspect database parent
AI-assisted analysis of JuliusBrussee/caveman@766dce6b13 (2026-08-18).
Data as JSON: /api/errors/315a9e72204788e9.
Report an issue: GitHub.
Appendix: source
Thrown at proxy/internal/nativeruntime/marker.go:26
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"os"
"path/filepath"
"regexp"
)
const sessionKeyBytes = 32
var markerPattern = regexp.MustCompile(`\[\[caveman-session-v1 sid="([A-Za-z0-9_-]{1,384})" sig="([0-9a-f]{64})"\]\]`)
// LoadOrCreateSessionKey returns one user-only HMAC key shared by CLI adapters
// and local proxy. O_EXCL makes concurrent first startup converge on one key.
func LoadOrCreateSessionKey(home string) ([]byte, error) {
dir := filepath.Join(home, "runtime")
if err := os.MkdirAll(dir, 0o700); err != nil {
return nil, fmt.Errorf("native session key mkdir: %w", err)
}
if err := os.Chmod(dir, 0o700); err != nil {
return nil, fmt.Errorf("native session key chmod dir: %w", err)
}
path := filepath.Join(dir, "session.key")
key := make([]byte, sessionKeyBytes)
if _, err := rand.Read(key); err != nil {
return nil, fmt.Errorf("native session key random: %w", err)
}
file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o600)
if err == nil {
if _, writeErr := file.Write(key); writeErr != nil {
_ = file.Close()
_ = os.Remove(path)
return nil, fmt.Errorf("native session key write: %w", writeErr)
}
if syncErr := file.Sync(); syncErr != nil {
_ = file.Close()View on GitHub (pinned to 766dce6b13)