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

  1. Run ls -ld <home> <home>/runtime and fix ownership: sudo chown -R $(id -u):$(id -g) <home>
  2. If runtime exists as a regular file, remove it: rm <home>/runtime
  3. Point CAVEMAN_HOME (or HOME) at a writable directory on a local filesystem
  4. 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

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


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)