{"record":{"id":"315a9e72204788e9","repo":"JuliusBrussee/caveman","slug":"native-session-key-mkdir-w","errorCode":null,"errorMessage":"native session key mkdir: %w","messagePattern":"native session key mkdir: %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proxy/internal/nativeruntime/marker.go","lineNumber":26,"sourceCode":"\t\"encoding/base64\"\n\t\"encoding/hex\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n)\n\nconst sessionKeyBytes = 32\n\nvar markerPattern = regexp.MustCompile(`\\[\\[caveman-session-v1 sid=\"([A-Za-z0-9_-]{1,384})\" sig=\"([0-9a-f]{64})\"\\]\\]`)\n\n// LoadOrCreateSessionKey returns one user-only HMAC key shared by CLI adapters\n// and local proxy. O_EXCL makes concurrent first startup converge on one key.\nfunc LoadOrCreateSessionKey(home string) ([]byte, error) {\n\tdir := filepath.Join(home, \"runtime\")\n\tif err := os.MkdirAll(dir, 0o700); err != nil {\n\t\treturn nil, fmt.Errorf(\"native session key mkdir: %w\", err)\n\t}\n\tif err := os.Chmod(dir, 0o700); err != nil {\n\t\treturn nil, fmt.Errorf(\"native session key chmod dir: %w\", err)\n\t}\n\tpath := filepath.Join(dir, \"session.key\")\n\tkey := make([]byte, sessionKeyBytes)\n\tif _, err := rand.Read(key); err != nil {\n\t\treturn nil, fmt.Errorf(\"native session key random: %w\", err)\n\t}\n\tfile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o600)\n\tif err == nil {\n\t\tif _, writeErr := file.Write(key); writeErr != nil {\n\t\t\t_ = file.Close()\n\t\t\t_ = os.Remove(path)\n\t\t\treturn nil, fmt.Errorf(\"native session key write: %w\", writeErr)\n\t\t}\n\t\tif syncErr := file.Sync(); syncErr != nil {\n\t\t\t_ = file.Close()","sourceCodeStart":8,"sourceCodeEnd":44,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/proxy/internal/nativeruntime/marker.go#L8-L44","documentation":"LoadOrCreateSessionKey creates <home>/runtime with mode 0700 before writing the shared HMAC session key. This error means MkdirAll failed — typically a permission problem on the parent directory, a read-only filesystem, or a path component that exists as a file instead of a directory.","triggerScenarios":"home points at a directory the process cannot write (owned by root, mode 0755 with different user); <home>/runtime already exists as a regular file; running with HOME unset so home resolves to an invalid path; container with a read-only volume mounted at home.","commonSituations":"Running the proxy/CLI under a service user whose HOME differs from expectations; stale 'runtime' file left by a bad install; immutable container filesystems.","solutions":["Check the underlying error: 'not a directory' means a file occupies <home>/runtime — remove or rename it","'permission denied' means fix ownership/permissions: chown the home dir to the running user, or chmod to allow writes","Set HOME (or the equivalent home argument) to a writable per-user directory","On read-only filesystems, point home at a writable volume"],"exampleFix":"# before\n$ ls -la ~/.caveman\nruntime    # a regular file -> Error[1066]\n\n# after\n$ rm ~/.caveman/runtime && mkdir -p ~/.caveman/runtime && chmod 700 ~/.caveman/runtime","handlingStrategy":"validation","validationCode":"func homeWritable(home string) error {\n    fi, err := os.Stat(filepath.Join(home, \"runtime\"))\n    if err == nil && !fi.IsDir() {\n        return fmt.Errorf(\"%s exists but is not a directory\", fi.Name())\n    }\n    if err != nil && !os.IsNotExist(err) {\n        return err\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Run the proxy and CLI as one user with an exclusive HOME","Pre-create <home>/runtime with 0700 in provisioning","Never let config management place a file named 'runtime' under home"],"tags":["filesystem","permissions","nativeruntime","session-key"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}