JuliusBrussee/caveman · error

native session key chmod dir

Error message

native session key chmod dir: %w

What it means

LoadOrCreateSessionKey re-chmods the runtime directory to 0700 right after creating it; this error means that chmod failed even though MkdirAll just succeeded. In practice the directory was removed or replaced between the two calls, is owned by another uid, or sits on a mount without usable POSIX chmod semantics (NFS, FUSE, vfat/exFAT). The 0700 mode is a hard requirement because the directory guards the shared HMAC session key.

Solutions

  1. Check ownership of <home>/runtime and chown it to the current user
  2. Re-run the command — a concurrent-swap race usually does not repeat
  3. Move CAVEMAN_HOME off network/exotic filesystems onto a native local FS
  4. Delete <home>/runtime entirely and let the next start recreate it with correct modes
Defensive patterns

Strategy: try-catch

Try / catch

if _, err := nativeruntime.LoadOrCreateSessionKey(home); err != nil {
	if errors.Is(err, fs.ErrPermission) && strings.Contains(err.Error(), "chmod dir") {
		// <home>/runtime is foreign-owned or on a chmod-hostile mount — repair ownership or move CAVEMAN_HOME, do not blind-retry
		log.Printf("repair: sudo chown -R %d:%d %s or relocate home off NFS/FUSE", os.Getuid(), os.Getgid(), home)
	}
	return err
}

Prevention

When it happens

Trigger: Concurrent first startup where another process deletes/recreates <home>/runtime between MkdirAll and Chmod; the runtime dir was left root-owned by an earlier sudo run; home on an NFS/SMB/FUSE mount where chmod on directories errors or is denied.

Common situations: Two CLI adapters or the local proxy launched simultaneously; mixed sudo/non-sudo usage of the same home; corporate network home directories; external exFAT drives used as CAVEMAN_HOME.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@766dce6b13 (2026-08-18). Data as JSON: /api/errors/a6b950d092a61638. Report an issue: GitHub.

Appendix: source

Thrown at proxy/internal/nativeruntime/marker.go:29

	"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()
			_ = os.Remove(path)
			return nil, fmt.Errorf("native session key sync: %w", syncErr)
		}

View on GitHub (pinned to 766dce6b13)