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

  1. Stat the claim file and compare against the checkpoint's recorded identity; if foreign, remove the stale checkpoint
  2. Ensure only one recovery process runs at a time for a lock path
  3. 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

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


AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27). Data as JSON: /api/errors/f4686f2c787ba8df. Report an issue: GitHub.