{"record":{"id":"b3353816edfe6438","repo":"stablyai/orca","slug":"terminal-history-recovery-protected","errorCode":null,"errorMessage":"terminal_history_recovery_protected","messagePattern":"terminal_history_recovery_protected","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"src/main/daemon/history-manager.ts","lineNumber":64,"sourceCode":"  ) {\n    this.onWriteError = opts?.onWriteError\n    this.checkpointMaxBytes = opts?.checkpointMaxBytes ?? TERMINAL_HISTORY_CHECKPOINT_MAX_BYTES\n    // Why: a quit between tombstone and reclaim leaves the tree on disk; nothing else rescans the queue.\n    schedulePendingSessionTreeRemovals(this.basePath)\n  }\n\n  async openSession(sessionId: string, opts: OpenSessionOptions): Promise<void> {\n    let recoveryFreeze = opts.recoveryFreeze\n    try {\n      this.disabledSessions.delete(sessionId)\n      const dir = join(this.basePath, getHistorySessionDirName(sessionId))\n      recoveryFreeze ??= await this.freezeForRecovery(sessionId)\n      const activeFreeze = this.requireRecoveryFreeze(sessionId, recoveryFreeze)\n\n      if (opts.quarantineUnreadableRecovery) {\n        quarantineTerminalHistorySession(this.basePath, sessionId, activeFreeze.fingerprint ?? null)\n      } else if (hasTerminalHistoryRecoveryProtection(this.basePath, sessionId)) {\n        throw new Error('terminal_history_recovery_protected')\n      } else if (\n        fingerprintTerminalHistorySession(this.basePath, sessionId) !== activeFreeze.fingerprint\n      ) {\n        throw new Error('terminal_history_recovery_generation_changed')\n      }\n      this.recoveryFreezes.delete(sessionId)\n      mkdirSync(dir, { recursive: true })\n\n      const meta: SessionMeta = {\n        cwd: opts.cwd,\n        cols: opts.cols,\n        rows: opts.rows,\n        startedAt: new Date().toISOString(),\n        endedAt: null,\n        exitCode: null\n      }\n      writeFileSync(join(dir, 'meta.json'), JSON.stringify(meta, null, 2))\n","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/src/main/daemon/history-manager.ts#L46-L82","documentation":"terminal_history_recovery_protected: openSession detected a '.unreadable-recovery' marker file in the session's history directory. This marker is written when a previous generation was quarantined as unreadable, and its presence blocks a new writer from attaching to potentially corrupt data. Opening is refused unless quarantineUnreadableRecovery is explicitly requested to re-quarantine.","triggerScenarios":"openSession on a sessionId whose history dir contains the RECOVERY_PROTECTION_MARKER, with opts.quarantineUnreadableRecovery falsy. Typically a prior recovery attempt marked the directory unreadable and a new open is racing in without the quarantine flag.","commonSituations":"A crashed/corrupt history directory left the protection marker after a failed quarantine; restarting a session whose prior generation was unreadable; an external tool partially clearing the recovery-quarantine dir but leaving the marker.","solutions":["Pass quarantineUnreadableRecovery: true in OpenSessionOptions if you intend to re-quarantine and start clean.","If the marker is stale and the directory is known-good, remove the .unreadable-recovery marker file manually (clearTerminalHistoryRecoveryProtection).","Investigate why the marker was left behind (prior write failure / disk issue) before forcing removal.","Treat history as best-effort: the session can still open without history rather than retrying indefinitely."],"exampleFix":"// before: plain open on a protected dir\nawait history.openSession(id, { cwd, cols, rows })\n\n// after: re-quarantine to start clean\nawait history.openSession(id, { cwd, cols, rows, quarantineUnreadableRecovery: true })","handlingStrategy":"fallback","validationCode":"import { hasTerminalHistoryRecoveryProtection } from './terminal-history-recovery-quarantine'\nif (hasTerminalHistoryRecoveryProtection(basePath, sessionId)) {\n  // decide: re-quarantine (quarantineUnreadableRecovery: true) or open without history\n}","typeGuard":"function isHistoryRecoveryProtected(e: unknown): boolean {\n  return e instanceof Error && e.message === 'terminal_history_recovery_protected'\n}","tryCatchPattern":"try {\n  await history.openSession(id, { cwd, cols, rows })\n} catch (e) {\n  if (e instanceof Error && e.message === 'terminal_history_recovery_protected') {\n    // re-quarantine and start clean\n    await history.openSession(id, { cwd, cols, rows, quarantineUnreadableRecovery: true })\n  } else { throw e }\n}","preventionTips":["Pass quarantineUnreadableRecovery when you intend to discard an unreadable generation.","Investigate why the protection marker was left before removing it manually.","Treat history as best-effort: let the session open without history if recovery is blocked."],"tags":["history","recovery","quarantine","integrity"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}