{"record":{"id":"f4686f2c787ba8df","repo":"Yeachan-Heo/oh-my-codex","slug":"checkpoint-claim-is-foreign","errorCode":null,"errorMessage":"checkpoint claim is foreign","messagePattern":"checkpoint claim is foreign","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/hooks/session.ts","lineNumber":1628,"sourceCode":"    const evidenceIdentity = checkpoint.evidenceIdentity ?? checkpoint.identity;\n    const claimPath = join(context.lockPath, `owner.${ownerToken}.${recoveryToken}.recovery`);\n    const lock = await lstatRecoveryPath(context.lockPath);\n    const parkedLock = await lstatRecoveryPath(lockParkPath);\n    if (lock && parkedLock) throw new Error('checkpoint lock paths are both present');\n    if (parkedLock) {\n      if (!checkpoint.lockIdentity || !sameRecoveryIdentity(parkedLock, checkpoint.lockIdentity, 'directory')) throw new Error('checkpoint parked lock identity mismatch');\n      const quarantinePath = `${context.lockPath}.quarantine.${ownerToken}.${recoveryToken}`;\n      const completed = await completeRecoveryCheckpoint(checkpointPath, checkpointBytes, { dev: checkpointStat.dev, ino: checkpointStat.ino });\n      if (!completed.completed) throw new Error(completed.reason);\n      return { status: 'dead', lockPath: context.lockPath, evidenceSource: 'owner.json', safeToRecover: true, action: 'quarantined', recovered: true, reason: 'Dead session pointer lock recovery checkpoint resumed.', quarantinePath };\n    }\n    if (!lock || lock.isSymbolicLink() || !lock.isDirectory()) throw new Error('checkpoint lock is missing or foreign');\n    const source = await lstatRecoveryPath(checkpoint.sourcePath);\n    const claim = await lstatRecoveryPath(claimPath);\n    const evidencePath = source && sameRecoveryIdentity(source, evidenceIdentity, 'file') ? checkpoint.sourcePath\n      : claim && sameRecoveryIdentity(claim, evidenceIdentity, 'file') ? claimPath : undefined;\n    if (!evidencePath) throw new Error('checkpoint evidence is missing or foreign');\n    if (claim && !sameRecoveryIdentity(claim, evidenceIdentity, 'file')) throw new Error('checkpoint claim is foreign');\n    // v1/v2 did not persist the directory identity. It is safe to derive only\n    // while the original directory still contains the exact recorded evidence.\n    const lockIdentity = checkpoint.lockIdentity ?? { dev: lock.dev, ino: lock.ino };\n    if (!sameRecoveryIdentity(lock, lockIdentity, 'directory')) throw new Error('checkpoint lock identity mismatch');\n    const ownerBytes = await transactionDependencies.fs.readFile(evidencePath, 'utf8');\n    const owner = await inspectLockOwnerFile(evidencePath);\n    if (owner.status !== 'dead' || owner.owner?.token !== ownerToken || checkpoint.evidenceBytes !== undefined && checkpoint.evidenceBytes !== ownerBytes) throw new Error('checkpoint owner evidence changed');\n    const quarantinePath = `${context.lockPath}.quarantine.${ownerToken}.${recoveryToken}`;\n    const quarantine = await lstatRecoveryPath(quarantinePath);\n    if (quarantine) {\n      if (!sameRecoveryIdentity(quarantine, evidenceIdentity, 'file')) throw new Error('checkpoint quarantine is foreign');\n    } else {\n      await transactionDependencies.fs.link(evidencePath, quarantinePath);\n      const linked = await lstatRecoveryPath(quarantinePath);\n      if (!linked || !sameRecoveryIdentity(linked, evidenceIdentity, 'file')) throw new Error('checkpoint quarantine link mismatch');\n    }\n    // Revalidate the bytes, dead owner and identities immediately before moving\n    // the entire directory incarnation out of the canonical lock pathname.","sourceCodeStart":1610,"sourceCodeEnd":1646,"githubUrl":"https://github.com/Yeachan-Heo/oh-my-codex/blob/3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2/src/hooks/session.ts#L1610-L1646","documentation":"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.","triggerScenarios":"Resuming recovery when claimPath was recreated (new inode) by another session or an earlier recovery attempt after the checkpoint was written.","commonSituations":"Concurrent recoveries or lock reacquisition writing a new claim file; stale checkpoints from a previous lock generation.","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"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const claim = await lstat(claimPath).catch(() => null);\nif (claim && !identityMatches(claim, evidenceIdentity)) await fs.rm(checkpointPath, { force: true });","typeGuard":null,"tryCatchPattern":"catch (e) { if (/checkpoint claim is foreign/.test(String(e))) { /* drop checkpoint, retry */ } else throw e; }","preventionTips":["Single-writer claim files","Clear stale checkpoints after lock recreation"],"tags":["lock-recovery","checkpoint","file-identity"],"backgroundTag":"stale-checkpoint-recovery","analyzedSha":"3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2","analyzedAt":"2026-08-27T22:18:39.783Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}