{"record":{"id":"5b9d943df5e1ece9","repo":"JuliusBrussee/caveman","slug":"conflicting-committed-and-pending-integration-jour","errorCode":null,"errorMessage":"conflicting committed and pending integration journals for ${agent}; refusing recovery","messagePattern":"conflicting committed and pending integration journals for (.+?); refusing recovery","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":6899,"sourceCode":"    return value?.schema_version === 1 && value.agent === agent && Array.isArray(value.operations) ? value : undefined;\n  } catch { return undefined; }\n}\n\nfunction readNativeJournal(agent: string): NativeJournal | undefined {\n  return readNativeJournalAt(nativeJournalPath(agent), agent);\n}\n\nfunction readPendingNativeJournal(agent: string): NativeJournal | undefined {\n  return readNativeJournalAt(nativePendingJournalPath(agent), agent);\n}\n\nfunction recoverPendingNativeInstallUnlocked(agent: NativeAgent): boolean {\n  const pending = readPendingNativeJournal(agent);\n  if (!pending) return false;\n  const committed = readNativeJournal(agent);\n  if (committed) {\n    if (JSON.stringify(committed) !== JSON.stringify(pending)) {\n      throw new Error(`conflicting committed and pending integration journals for ${agent}; refusing recovery`);\n    }\n    unlinkSync(nativePendingJournalPath(agent));\n    return false;\n  }\n  const current = pending.operations.map((operation) => ({ file: operation.file, bytes: fileBytes(operation.file) }));\n  const restored = pending.operations.map((operation) => {\n    const before = nativeBackupBytes(operation);\n    const now = fileBytes(operation.file);\n    const isInstalled = Boolean(now && bytesHash(now) === operation.after_sha256);\n    const isBefore = operation.before_exists\n      ? Boolean(now && operation.before_sha256 && bytesHash(now) === operation.before_sha256)\n      : now === null;\n    if (!isInstalled && !isBefore) {\n      throw new Error(`${operation.file} changed during interrupted ${agent} install; refusing recovery`);\n    }\n    return { file: operation.file, bytes: before };\n  });\n  try {","sourceCodeStart":6881,"sourceCodeEnd":6917,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/cli/src/index.ts#L6881-L6917","documentation":"Interrupted native installs are recovered using a pending journal plus a committed journal per agent. Recovery expects either a pending journal alone (interrupted install) or a committed journal alone; finding BOTH with different contents is a state the recovery logic cannot interpret safely, so it refuses rather than guess. Identical journals are benign (the pending copy is deleted and recovery is skipped).","triggerScenarios":"Calling the install/recovery path when both nativePendingJournalPath(agent) and the committed journal exist and JSON.stringify comparison of the two journals differs — typically after a crash between committing and pending-journal cleanup, or after manual edits to the state directory.","commonSituations":"Process killed at exactly the wrong moment during a native install, users hand-editing or restoring partial copies of the caveman state directory, or downgrades/ upgrades that changed the journal schema between writes.","solutions":["Inspect both journal files for the agent and determine which reflects reality on disk.","If the committed journal is authoritative, delete the pending journal file.","If the pending journal is authoritative (install never completed), remove the committed journal and re-run recovery.","If unsure, uninstall and reinstall the agent integration to rebuild consistent state."],"exampleFix":"# before: conflicting journals\nls ~/.caveman/*/native/*/journal*   # both committed and pending present\n# after: keep the committed journal, drop the pending copy\nrm ~/.caveman/.../claude.pending.json && caveman install claude","handlingStrategy":"validation","validationCode":"import { readFileSync, existsSync } from \"node:fs\";\n\nfunction journalsConsistent(pending: string, committed: string): boolean {\n  if (!existsSync(pending) || !existsSync(committed)) return true;\n  try {\n    return readFileSync(pending, \"utf8\") === readFileSync(committed, \"utf8\");\n  } catch {\n    return false;\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  runRecovery(agent);\n} catch (e) {\n  if (e instanceof Error && e.message.includes(\"conflicting committed and pending\")) {\n    // stop automation; surface state paths to the operator for manual reconciliation\n    throw new FatalStateError(agent);\n  }\n  throw e;\n}","preventionTips":["Never edit caveman state directories by hand while journals exist.","Avoid killing the CLI mid-install; let it complete or use its own abort path.","After crashes, run recovery immediately before making other changes to agent files."],"tags":["recovery","journal","cli","state-corruption"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}