stablyai/orca · warning · Error

terminal_history_recovery_generation_changed

Error message

terminal_history_recovery_generation_changed

What it means

terminal_history_recovery_generation_changed in openSession: the current fingerprint of the session history directory does not match the fingerprint captured at freeze time. The directory tree changed between freeze and open (concurrent write, external modification, or corruption), so opening would mix generations. The freeze is abandoned and the session's history is disabled.

Source

Thrown at src/main/daemon/history-manager.ts:68

    schedulePendingSessionTreeRemovals(this.basePath)
  }

  async openSession(sessionId: string, opts: OpenSessionOptions): Promise<void> {
    let recoveryFreeze = opts.recoveryFreeze
    try {
      this.disabledSessions.delete(sessionId)
      const dir = join(this.basePath, getHistorySessionDirName(sessionId))
      recoveryFreeze ??= await this.freezeForRecovery(sessionId)
      const activeFreeze = this.requireRecoveryFreeze(sessionId, recoveryFreeze)

      if (opts.quarantineUnreadableRecovery) {
        quarantineTerminalHistorySession(this.basePath, sessionId, activeFreeze.fingerprint ?? null)
      } else if (hasTerminalHistoryRecoveryProtection(this.basePath, sessionId)) {
        throw new Error('terminal_history_recovery_protected')
      } else if (
        fingerprintTerminalHistorySession(this.basePath, sessionId) !== activeFreeze.fingerprint
      ) {
        throw new Error('terminal_history_recovery_generation_changed')
      }
      this.recoveryFreezes.delete(sessionId)
      mkdirSync(dir, { recursive: true })

      const meta: SessionMeta = {
        cwd: opts.cwd,
        cols: opts.cols,
        rows: opts.rows,
        startedAt: new Date().toISOString(),
        endedAt: null,
        exitCode: null
      }
      writeFileSync(join(dir, 'meta.json'), JSON.stringify(meta, null, 2))

      if (!opts.quarantineUnreadableRecovery) {
        // Why: a crash before the first checkpoint must not replay a cleanly ended prior session.
        for (const staleFile of [
          join(dir, 'checkpoint.json'),

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Re-freeze and re-open once the directory is quiescent (no concurrent writers).
  2. If quarantine is appropriate, pass quarantineUnreadableRecovery to quarantine the changed generation and start fresh.
  3. Ensure no other process touches the history base path during recovery (disable sync/AV on that dir).
  4. Accept that history is disabled for this session and continue without it.
Defensive patterns

Strategy: fallback

Type guard

function isRecoveryGenerationChanged(e: unknown): boolean {
  return e instanceof Error && e.message === 'terminal_history_recovery_generation_changed'
}

Try / catch

try {
  await history.openSession(id, opts)
} catch (e) {
  if (e instanceof Error && e.message === 'terminal_history_recovery_generation_changed') {
    // history disabled internally; continue without it, or re-quarantine
    await history.openSession(id, { ...opts, quarantineUnreadableRecovery: true })
  } else { throw e }
}

Prevention

When it happens

Trigger: openSession where fingerprintTerminalHistorySession(basePath, sessionId) differs from activeFreeze.fingerprint, with no quarantine requested and no protection marker. The dir was modified after the recovery freeze was taken.

Common situations: Another process (or a leftover writer) modified the history directory between freezeForRecovery and openSession; filesystem metadata changed (inode/mtime) due to AV scanning or a sync tool; a crash left a partially-written directory.

Related errors


AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12). Data as JSON: /api/errors/19b53885bd9a91ad. Report an issue: GitHub.