{"record":{"id":"9625a562a555b9b3","repo":"stablyai/orca","slug":"terminal-history-recovery-generation-changed-9625a5","errorCode":null,"errorMessage":"terminal_history_recovery_generation_changed","messagePattern":"terminal_history_recovery_generation_changed","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"src/main/daemon/terminal-history-recovery-quarantine.ts","lineNumber":80,"sourceCode":"    const stats = lstatSync(name === '.' ? sessionDir : join(sessionDir, name))\n    fingerprint.update(name)\n    fingerprint.update('\\0')\n    fingerprint.update(\n      [stats.dev, stats.ino, stats.mode, stats.size, stats.mtimeMs, stats.ctimeMs].join(':')\n    )\n    fingerprint.update('\\0')\n  }\n  return fingerprint.digest('hex')\n}\n\nexport function quarantineTerminalHistorySession(\n  basePath: string,\n  sessionId: string,\n  expectedFingerprint: TerminalHistoryDirectoryFingerprint\n): string {\n  const actualFingerprint = fingerprintTerminalHistorySession(basePath, sessionId)\n  if (actualFingerprint !== expectedFingerprint) {\n    throw new Error('terminal_history_recovery_generation_changed')\n  }\n\n  const sessionDir = join(basePath, getHistorySessionDirName(sessionId))\n  const ownerDir = getTerminalHistoryQuarantineOwnerDir(basePath, sessionId)\n  // Why: if rename is blocked, a later adapter must not attach a writer to the unreadable generation.\n  writeFileSync(join(sessionDir, RECOVERY_PROTECTION_MARKER), '')\n  mkdirSync(ownerDir, { recursive: true })\n  const quarantineDir = join(ownerDir, randomUUID())\n  renameSync(sessionDir, quarantineDir)\n  return quarantineDir\n}\n","sourceCodeStart":62,"sourceCodeEnd":92,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/src/main/daemon/terminal-history-recovery-quarantine.ts#L62-L92","documentation":"terminal_history_recovery_generation_changed in quarantineTerminalHistorySession: the directory fingerprint re-computed at quarantine time does not match the expectedFingerprint passed in. The session dir changed between freeze and quarantine, so quarantining would move the wrong generation. The function throws before writing the protection marker or renaming, leaving the directory untouched.","triggerScenarios":"quarantineTerminalHistorySession(basePath, sessionId, expectedFingerprint) where fingerprintTerminalHistorySession now returns a different value than expectedFingerprint — the dir tree (entries, inodes, mtimes) was modified after the expected fingerprint was captured.","commonSituations":"A concurrent writer or another recovery pass modified the directory between fingerprint capture and quarantine; filesystem metadata churn from sync/AV; a race between two recovery attempts on the same session.","solutions":["Re-capture the fingerprint immediately before calling quarantineTerminalHistorySession, or call it inside the same freeze that produced the expected fingerprint.","Serialize recovery operations per session so no two quarantines/fingerprints race.","Ensure no writer is active on the session dir during quarantine.","If the change is expected, accept that the prior generation is gone and skip quarantine."],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":"function isRecoveryGenerationChanged(e: unknown): boolean {\n  return e instanceof Error && e.message === 'terminal_history_recovery_generation_changed'\n}","tryCatchPattern":"try {\n  quarantineTerminalHistorySession(basePath, sessionId, expectedFingerprint)\n} catch (e) {\n  if (e instanceof Error && e.message === 'terminal_history_recovery_generation_changed') {\n    // generation changed under us; skip quarantine and let history disable for this session\n  } else { throw e }\n}","preventionTips":["Re-compute the fingerprint immediately before quarantine, inside the same freeze.","Serialize recovery operations per session so no two races overlap.","Ensure no writer is active on the dir during quarantine."],"tags":["history","recovery","quarantine","integrity","race"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}