Yeachan-Heo/oh-my-codex · error · Error
checkpoint claim is foreign
Error message
checkpoint claim is foreign
What it means
A claim file exists at claimPath but its dev/ino identity does not match the evidence identity recorded in the recovery checkpoint. Because a foreign claim could belong to a different lock incarnation, the library aborts the recovery.
Source
Thrown at src/hooks/session.ts:1628
const evidenceIdentity = checkpoint.evidenceIdentity ?? checkpoint.identity;
const claimPath = join(context.lockPath, `owner.${ownerToken}.${recoveryToken}.recovery`);
const lock = await lstatRecoveryPath(context.lockPath);
const parkedLock = await lstatRecoveryPath(lockParkPath);
if (lock && parkedLock) throw new Error('checkpoint lock paths are both present');
if (parkedLock) {
if (!checkpoint.lockIdentity || !sameRecoveryIdentity(parkedLock, checkpoint.lockIdentity, 'directory')) throw new Error('checkpoint parked lock identity mismatch');
const quarantinePath = `${context.lockPath}.quarantine.${ownerToken}.${recoveryToken}`;
const completed = await completeRecoveryCheckpoint(checkpointPath, checkpointBytes, { dev: checkpointStat.dev, ino: checkpointStat.ino });
if (!completed.completed) throw new Error(completed.reason);
return { status: 'dead', lockPath: context.lockPath, evidenceSource: 'owner.json', safeToRecover: true, action: 'quarantined', recovered: true, reason: 'Dead session pointer lock recovery checkpoint resumed.', quarantinePath };
}
if (!lock || lock.isSymbolicLink() || !lock.isDirectory()) throw new Error('checkpoint lock is missing or foreign');
const source = await lstatRecoveryPath(checkpoint.sourcePath);
const claim = await lstatRecoveryPath(claimPath);
const evidencePath = source && sameRecoveryIdentity(source, evidenceIdentity, 'file') ? checkpoint.sourcePath
: claim && sameRecoveryIdentity(claim, evidenceIdentity, 'file') ? claimPath : undefined;
if (!evidencePath) throw new Error('checkpoint evidence is missing or foreign');
if (claim && !sameRecoveryIdentity(claim, evidenceIdentity, 'file')) throw new Error('checkpoint claim is foreign');
// v1/v2 did not persist the directory identity. It is safe to derive only
// while the original directory still contains the exact recorded evidence.
const lockIdentity = checkpoint.lockIdentity ?? { dev: lock.dev, ino: lock.ino };
if (!sameRecoveryIdentity(lock, lockIdentity, 'directory')) throw new Error('checkpoint lock identity mismatch');
const ownerBytes = await transactionDependencies.fs.readFile(evidencePath, 'utf8');
const owner = await inspectLockOwnerFile(evidencePath);
if (owner.status !== 'dead' || owner.owner?.token !== ownerToken || checkpoint.evidenceBytes !== undefined && checkpoint.evidenceBytes !== ownerBytes) throw new Error('checkpoint owner evidence changed');
const quarantinePath = `${context.lockPath}.quarantine.${ownerToken}.${recoveryToken}`;
const quarantine = await lstatRecoveryPath(quarantinePath);
if (quarantine) {
if (!sameRecoveryIdentity(quarantine, evidenceIdentity, 'file')) throw new Error('checkpoint quarantine is foreign');
} else {
await transactionDependencies.fs.link(evidencePath, quarantinePath);
const linked = await lstatRecoveryPath(quarantinePath);
if (!linked || !sameRecoveryIdentity(linked, evidenceIdentity, 'file')) throw new Error('checkpoint quarantine link mismatch');
}
// Revalidate the bytes, dead owner and identities immediately before moving
// the entire directory incarnation out of the canonical lock pathname.View on GitHub (pinned to 3ad79a8a6f)
Solutions
- Stat the claim file and compare against the checkpoint's recorded identity; if foreign, remove the stale checkpoint
- Ensure only one recovery process runs at a time for a lock path
- Retry the recovery from scratch after clearing the checkpoint
Defensive patterns
Strategy: validation
Validate before calling
const claim = await lstat(claimPath).catch(() => null);
if (claim && !identityMatches(claim, evidenceIdentity)) await fs.rm(checkpointPath, { force: true }); Try / catch
catch (e) { if (/checkpoint claim is foreign/.test(String(e))) { /* drop checkpoint, retry */ } else throw e; } Prevention
- Single-writer claim files
- Clear stale checkpoints after lock recreation
When it happens
Trigger: Resuming recovery when claimPath was recreated (new inode) by another session or an earlier recovery attempt after the checkpoint was written.
Common situations: Concurrent recoveries or lock reacquisition writing a new claim file; stale checkpoints from a previous lock generation.
Related errors
- checkpoint evidence is missing or foreign
- checkpoint lock is missing or foreign
- Unknown ultragoal id: ${options.goalId}
- Cannot record a ${options.status} checkpoint for ${goal.id}
- Cannot record a blocked checkpoint for ${goal.id} while it i
AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27).
Data as JSON: /api/errors/f4686f2c787ba8df.
Report an issue: GitHub.