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 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.

Source

Thrown at src/main/daemon/terminal-history-recovery-quarantine.ts:80

    const stats = lstatSync(name === '.' ? sessionDir : join(sessionDir, name))
    fingerprint.update(name)
    fingerprint.update('\0')
    fingerprint.update(
      [stats.dev, stats.ino, stats.mode, stats.size, stats.mtimeMs, stats.ctimeMs].join(':')
    )
    fingerprint.update('\0')
  }
  return fingerprint.digest('hex')
}

export function quarantineTerminalHistorySession(
  basePath: string,
  sessionId: string,
  expectedFingerprint: TerminalHistoryDirectoryFingerprint
): string {
  const actualFingerprint = fingerprintTerminalHistorySession(basePath, sessionId)
  if (actualFingerprint !== expectedFingerprint) {
    throw new Error('terminal_history_recovery_generation_changed')
  }

  const sessionDir = join(basePath, getHistorySessionDirName(sessionId))
  const ownerDir = getTerminalHistoryQuarantineOwnerDir(basePath, sessionId)
  // Why: if rename is blocked, a later adapter must not attach a writer to the unreadable generation.
  writeFileSync(join(sessionDir, RECOVERY_PROTECTION_MARKER), '')
  mkdirSync(ownerDir, { recursive: true })
  const quarantineDir = join(ownerDir, randomUUID())
  renameSync(sessionDir, quarantineDir)
  return quarantineDir
}

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Re-capture the fingerprint immediately before calling quarantineTerminalHistorySession, or call it inside the same freeze that produced the expected fingerprint.
  2. Serialize recovery operations per session so no two quarantines/fingerprints race.
  3. Ensure no writer is active on the session dir during quarantine.
  4. If the change is expected, accept that the prior generation is gone and skip quarantine.
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 {
  quarantineTerminalHistorySession(basePath, sessionId, expectedFingerprint)
} catch (e) {
  if (e instanceof Error && e.message === 'terminal_history_recovery_generation_changed') {
    // generation changed under us; skip quarantine and let history disable for this session
  } else { throw e }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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