{"record":{"id":"73fa2b32065df91c","repo":"gastownhall/beads","slug":"identity-read-proxy-secret-w","errorCode":null,"errorMessage":"identity: read proxy secret: %w","messagePattern":"identity: read proxy secret: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/dbproxy/identity/identity.go","lineNumber":58,"sourceCode":"// WriteSecret creates and atomically writes a new control-listener secret.\n// Each proxy start intentionally rotates the previous secret.\nfunc WriteSecret(rootDir string) (string, error) {\n\traw := make([]byte, 32)\n\tif _, err := rand.Read(raw); err != nil {\n\t\treturn \"\", fmt.Errorf(\"identity: generate proxy secret: %w\", err)\n\t}\n\tsecret := hex.EncodeToString(raw)\n\tif err := atomicfile.WriteFile(filepath.Join(rootDir, SecretFileName), []byte(secret+\"\\n\"), 0o600); err != nil {\n\t\treturn \"\", fmt.Errorf(\"identity: write proxy secret: %w\", err)\n\t}\n\treturn secret, nil\n}\n\n// ReadSecret reads and validates the control-listener secret.\nfunc ReadSecret(rootDir string) (string, error) {\n\tdata, err := os.ReadFile(filepath.Join(rootDir, SecretFileName)) // #nosec G304 - rootDir is the workspace proxy root, not user input\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"identity: read proxy secret: %w\", err)\n\t}\n\tsecret := strings.TrimSpace(string(data))\n\tif len(secret) != 64 {\n\t\treturn \"\", errors.New(\"identity: invalid proxy secret\")\n\t}\n\tif _, err := hex.DecodeString(secret); err != nil {\n\t\treturn \"\", errors.New(\"identity: invalid proxy secret\")\n\t}\n\treturn secret, nil\n}\n","sourceCodeStart":40,"sourceCodeEnd":69,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/dbproxy/identity/identity.go#L40-L69","documentation":"ReadSecret loads <rootDir>/proxy.secret and validates it is a 64-character hex string. This error wraps any failure from os.ReadFile: the secret file does not exist, permission is denied, or it is a directory. The library throws it because the control-listener secret cannot be read back for authentication; note the intentionally-rotated secret means a stale reader will simply not find the current file.","triggerScenarios":"Calling identity.ReadSecret(rootDir) before WriteSecret ever ran (proxy.secret absent), after the workspace root was recreated or moved, when the file exists but the current user lacks read permission (0600 owned by another user), or when rootDir points at the wrong workspace.","commonSituations":"A client or child process starting before the parent proxy wrote the secret (startup race), running as a different user than the proxy (0600 perms deny access), pointing at a stale or deleted workspace, or a workspace partially restored from backup without proxy.secret.","solutions":["Ensure identity.WriteSecret was called for this rootDir before ReadSecret (correct startup order)","Verify the file exists: stat <rootDir>/proxy.secret, and confirm rootDir is the right workspace","Fix read permissions (chown/chmod) — the file is 0600 and only the owner can read it","Handle fs.ErrNotExist explicitly with errors.Is and fall back to writing a fresh secret"],"exampleFix":"// before\nsecret, err := identity.ReadSecret(rootDir) // fails if proxy.secret missing\n// after\nsecret, err := identity.ReadSecret(rootDir)\nif errors.Is(err, fs.ErrNotExist) {\n    secret, err = identity.WriteSecret(rootDir)\n}","handlingStrategy":"validation","validationCode":"secretPath := filepath.Join(rootDir, identity.SecretFileName)\nif _, err := os.Stat(secretPath); err != nil {\n    if errors.Is(err, fs.ErrNotExist) {\n        // write a fresh secret before reading\n        _, err = identity.WriteSecret(rootDir)\n        return err\n    }\n    return err\n}","typeGuard":null,"tryCatchPattern":"secret, err := identity.ReadSecret(rootDir)\nif err != nil {\n    var perr *fs.PathError\n    if errors.As(err, &perr) && errors.Is(perr.Err, fs.ErrNotExist) {\n        // proxy never started here or workspace reset: recreate via WriteSecret\n    } else if errors.As(err, &perr) && errors.Is(perr.Err, fs.ErrPermission) {\n        // wrong user: chown or re-run as the proxy's user\n    }\n    return err\n}","preventionTips":["Establish startup order: proxy writes the secret before clients/children read it","Run all workspace processes as the same user (file mode is 0600)","Detect stale workspaces: recreate proxy.secret after workspace recreation","Point every process at the same rootDir value"],"tags":["go","filesystem","secret","file-not-found"],"backgroundTag":"file-not-found","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}