{"record":{"id":"99f1359d5f9e5172","repo":"stablyai/orca","slug":"codex-hooks-json-changed-while-orca-prepared-its-t","errorCode":null,"errorMessage":"Codex hooks.json changed while Orca prepared its trust repair","messagePattern":"Codex hooks\\.json changed while Orca prepared its trust repair","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"src/main/codex/codex-real-home-hook-install.ts","lineNumber":81,"sourceCode":"function getRealHomeConfigTomlPath(): string {\n  return join(getSystemCodexHomePath(), 'config.toml')\n}\n\n/** Orca-side state dir; nothing extra is ever written into the user's ~/.codex. */\nfunction getRealHomeHookStateDir(userDataPath: string): string {\n  return join(userDataPath, 'codex-real-home-hooks')\n}\n\nfunction assertHooksJsonGeneration(\n  hooksJsonPath: string,\n  hooksWritePath: string,\n  expectedRaw: string | null\n): void {\n  const currentRaw = existsSync(hooksJsonPath) ? readFileSync(hooksJsonPath, 'utf-8') : null\n  if (currentRaw !== expectedRaw || resolveHooksJsonWritePath(hooksJsonPath) !== hooksWritePath) {\n    // Why: the pre-mutation RPC can overlap a user's editor save. Abort rather\n    // than atomically replacing a newer file with the stale parsed snapshot.\n    throw new Error('Codex hooks.json changed while Orca prepared its trust repair')\n  }\n}\n\n/**\n * Ensures the real-home hook state matches the settings: installs and trusts\n * the Orca status hook when enabled, sweeps it when opted out. Idempotent and\n * synchronous (launch prep); repeat calls are cheap — an unchanged hooks.json\n * write no-ops and a valid grant ledger skips the RPC session entirely.\n * Never throws: any failure logs and leaves the host on the managed lane.\n */\nexport function ensureRealHomeCodexHookState(args: {\n  hooksEnabled: boolean\n  userDataPath: string\n}): RealHomeCodexHookLane {\n  // Why: the grant client caches failed probes, but mutating and rolling back\n  // hooks.json before consulting it still adds synchronous work to every pane.\n  if (args.hooksEnabled && currentLane === 'unavailable' && Date.now() < installRetryAfterMs) {\n    return currentLane","sourceCodeStart":63,"sourceCodeEnd":99,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/src/main/codex/codex-real-home-hook-install.ts#L63-L99","documentation":"Thrown by assertHooksJsonGeneration when the raw bytes of the user's real-home hooks.json (or its resolved write path) changed between the initial read (readHooksJsonWithRaw) and the pre-write generation check. Orca snapshots the file, prepares an in-memory mutation, then re-reads on disk before writing; a concurrent external save (user's editor, another codex instance) invalidates the snapshot to avoid clobbering newer content with a stale parse.","triggerScenarios":"A user saved hooks.json in their editor between Orca's snapshot read and the writeHooks callback; another Orca process or codex TUI rewrote hooks.json; the resolved write path (symlink target via resolveHooksJsonWritePath) shifted because the symlink changed.","commonSituations":"User actively editing hooks.json while Orca does launch-prep hook install; two Orca windows racing; a hook-manager extension rewriting the file; the file is a symlink whose target was swapped.","solutions":["Close editors/tools editing ~/.codex/hooks.json during launch, then retry — ensureRealHomeCodexHookState retries on the next pane.","If using a hook-manager extension, pause it or coordinate so only one writer mutates hooks.json at a time.","Confirm hooks.json is not a symlink whose target changes (resolveHooksJsonWritePath must be stable).","Rely on the managed-home fallback: a failed install leaves currentLane='unavailable' and status still works.","Re-enable real-home hooks after the external edit completes."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Minimize the window between snapshot and write; avoid long async gaps in writeHooks.\n// Before install, warn the user if hooks.json mtime changed very recently:\nimport { statSync } from 'node:fs'\nconst mtimeMs = statSync(hooksJsonPath).mtimeMs\nif (Date.now() - mtimeMs < 2000) {\n  // likely being edited; defer the install to the next pane launch\n}","typeGuard":"function isHooksJsonGenerationError(error: unknown): boolean {\n  return error instanceof Error && error.message === 'Codex hooks.json changed while Orca prepared its trust repair'\n}","tryCatchPattern":"try {\n  ensureRealHomeCodexHookState({ hooksEnabled: true, userDataPath })\n} catch (error) {\n  if (isHooksJsonGenerationError(error)) {\n    // non-fatal: managed lane continues; retry on next pane launch\n    currentLane = 'unavailable'\n  } else throw error\n}","preventionTips":["Avoid editing ~/.codex/hooks.json while Orca is launching.","The install is retried on the next pane; no manual action needed for transient races.","Keep hooks.json a real file (stable symlink target), not a swapping symlink.","Coordinate hook managers so only one writer mutates hooks.json at a time."],"tags":["codex","hooks","concurrency","filesystem","generation-guard","real-home"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}