{"record":{"id":"37e8c3d1b86436a5","repo":"JuliusBrussee/caveman","slug":"could-not-remove-credentials-from-macos-keychain","errorCode":null,"errorMessage":"could not remove credentials from macOS Keychain","messagePattern":"could not remove credentials from macOS Keychain","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":18708,"sourceCode":"function genericKeychainGet(service: string, account: string): string {\n  try {\n    return execFileSync(\"security\", [\"find-generic-password\", \"-s\", service, \"-a\", account, \"-w\"], {\n      encoding: \"utf8\",\n      stdio: [\"ignore\", \"pipe\", \"ignore\"],\n    }).trim();\n  } catch {\n    return \"\";\n  }\n}\n\nfunction genericKeychainDelete(service: string, account: string) {\n  try {\n    execFileSync(\"security\", [\"delete-generic-password\", \"-s\", service, \"-a\", account], { stdio: \"ignore\" });\n  } catch (error) {\n    // macOS security exits 44 for errSecItemNotFound. Absence is the desired\n    // postcondition; every other failure means the secret may still exist.\n    if ((error as { status?: unknown }).status === 44) return;\n    throw new Error(\"could not remove credentials from macOS Keychain\");\n  }\n}\n\nfunction caveHome() {\n  return process.env.CAVEMAN_HOME ?? join(homedir(), \".caveman\");\n}\n\nfunction credentialsPath() {\n  return join(caveHome(), \"credentials\");\n}\n\nfunction fileTokenSet(token: string) {\n  ensureCavemanHome();\n  try { chmodSync(credentialsPath(), 0o600); } catch { /* created below */ }\n  writeFileSync(credentialsPath(), token, { mode: 0o600 });\n  chmodSync(credentialsPath(), 0o600);\n}\n","sourceCodeStart":18690,"sourceCodeEnd":18726,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/cli/src/index.ts#L18690-L18726","documentation":"Thrown when deleting stored credentials from the macOS Keychain via `security delete-generic-password` fails with an exit status other than 44 (errSecItemNotFound, which is treated as success since the goal is removal). This means the credential entry may still exist in the Keychain after a logout/cleanup. It is a defensive guard so silent partial logouts are detected.","triggerScenarios":"execFileSync('security', ['delete-generic-password','-s',service,'-a',account]) exits with any status except 0 or 44: e.g. the Keychain is locked, the security binary is missing/broken, or permission to the item is denied (user clicks Deny).","commonSituations":"macOS Keychain locked after sleep/restart, TCC/security prompts denied during logout, running in a context without a user Keychain (CI, SSH session without access), or corrupted Keychain entry.","solutions":["Unlock the Keychain (open Keychain Access, or `security unlock-keychain`) and retry the logout.","Re-run the logout command and click 'Allow' on any Keychain access prompt.","Check that /usr/bin/security exists and is functional (`security --version`).","Remove the entry manually: `security delete-generic-password -s <service> -a <account>` and inspect the reported error."],"exampleFix":"// before\nexecFileSync(\"security\", [\"delete-generic-password\", \"-s\", service, \"-a\", account], { stdio: \"ignore\" });\n// after\n// unlock first, then retry:\nexecFileSync(\"security\", [\"unlock-keychain\"]); // or open Keychain Access and unlock\nexecFileSync(\"security\", [\"delete-generic-password\", \"-s\", service, \"-a\", account], { stdio: \"ignore\" });","handlingStrategy":"try-catch","validationCode":"// pre-check the item exists before removal\nconst status = require(\"child_process\").spawnSync(\n  \"security\", [\"find-generic-password\", \"-s\", service, \"-a\", account], { stdio: \"ignore\" });\nif (status.status === 0) {\n  // item exists; ensure Keychain is unlocked before delete\n  require(\"child_process\").execFileSync(\"security\", [\"unlock-keychain\"]);\n}","typeGuard":"function isKeychainDeleteFailure(e: unknown): e is Error {\n  return e instanceof Error && e.message.includes(\"could not remove credentials from macOS Keychain\");\n}","tryCatchPattern":"try {\n  cavemanLogout();\n} catch (e) {\n  if (isKeychainDeleteFailure(e)) {\n    console.error(\"Keychain delete failed; unlock Keychain Access and retry logout:\", e.message);\n  } else throw e;\n}","preventionTips":["Keep the login Keychain unlocked before running logout in scripts","Run logout in an interactive session so Keychain permission prompts can be answered","Verify /usr/bin/security is available (macOS only code path)","Re-run logout if it fails; absence of the entry is the success condition"],"tags":["macos","keychain","credentials","security"],"backgroundTag":"missing-credentials","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}