{"record":{"id":"b654c91becd9950c","repo":"JuliusBrussee/caveman","slug":"pending-label-transaction-is-malformed-refusin","errorCode":null,"errorMessage":"pending ${label} transaction is malformed; refusing recovery","messagePattern":"pending (.+?) transaction is malformed; refusing recovery","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":12733,"sourceCode":"  return agent && serverName ? `${agent} ${serverName} MCP` : \"owned MCP\";\n}\n\nfunction readOwnedMcpPendingJournalAt(\n  path: string,\n  expected: { agent?: \"kilo\" | \"qwen\"; serverName?: string; configPath?: string; locatorPath?: string } = {},\n): ReadOwnedMcpPendingJournal | null {\n  const label = ownedMcpPendingLabel(expected.agent, expected.serverName);\n  let bytes: Buffer;\n  let value: Record<string, unknown>;\n  try {\n    bytes = readFileSync(path);\n    value = JSON.parse(bytes.toString(\"utf8\")) as Record<string, unknown>;\n  } catch (error) {\n    if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return null;\n    throw new Error(`cannot read pending ${label} transaction: ${(error as Error).message}`);\n  }\n  if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n    throw new Error(`pending ${label} transaction is malformed; refusing recovery`);\n  }\n  if (process.platform !== \"win32\" && (statSync(path).mode & 0o077) !== 0) {\n    throw new Error(`pending ${label} transaction has unsafe permissions; refusing recovery`);\n  }\n  const keys = [\n    \"action\", \"agent\", \"config_after_sha256\", \"config_before_base64\", \"config_before_mode\", \"config_path\",\n    \"config_before_sha256\", \"marker_after_base64\", \"marker_after_sha256\", \"marker_before_base64\", \"marker_before_mode\",\n    \"marker_before_sha256\", \"marker_path\", \"schema_version\", \"server_name\", \"transaction_id\",\n  ].sort();\n  const journalAgent = value.agent;\n  const journalServer = value.server_name;\n  if (Object.keys(value).sort().join(\"\\0\") !== keys.join(\"\\0\")\n    || value.schema_version !== 1\n    || typeof value.transaction_id !== \"string\"\n    || !/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(value.transaction_id)\n    || (journalAgent !== \"kilo\" && journalAgent !== \"qwen\")\n    || typeof journalServer !== \"string\"\n    || !mcpServerToolName(journalServer)","sourceCodeStart":12715,"sourceCodeEnd":12751,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/cli/src/index.ts#L12715-L12751","documentation":"After reading the pending transaction journal, the CLI validates its top-level shape: it must be a non-null, non-array JSON object. Anything else (a JSON string, number, array, or literal null) fails this structural check and the journal is rejected for recovery.","triggerScenarios":"The journal file contains valid JSON but not an object — e.g. the file was overwritten with '[]', '\"text\"', 'null', or a bare number.","commonSituations":"A user or script accidentally wrote a different JSON payload to the journal path; a partially-overwritten file that happens to still parse; testing the journal mechanism by writing placeholder content.","solutions":["Restore the journal to the expected object schema with all required keys (action, agent, config_path, schema_version, etc.)","Delete the invalid journal and re-run the MCP operation to generate a fresh one","Verify with jq that the file parses as an object: jq -e 'type == \"object\"' journal.json"],"exampleFix":"// before\n[]\n// after\n{ \"schema_version\": 1, \"action\": \"install\", \"agent\": \"kilo\", \"config_path\": \"...\", \"transaction_id\": \"...\" }","handlingStrategy":"validation","validationCode":"import { readFileSync } from 'fs';\nfunction journalIsObject(path: string): boolean {\n  try {\n    const v = JSON.parse(readFileSync(path, 'utf8'));\n    return !!v && typeof v === 'object' && !Array.isArray(v);\n  } catch { return false; }\n}","typeGuard":"function isJournalObject(v: unknown): v is Record<string, unknown> {\n  return !!v && typeof v === 'object' && !Array.isArray(v);\n}","tryCatchPattern":"try {\n  recoverPendingTransaction(label, path);\n} catch (e) {\n  if (e instanceof Error && e.message.includes('transaction is malformed')) {\n    // journal unusable: remove it and re-run the MCP operation\n  } else throw e;\n}","preventionTips":["Never overwrite the journal path with arbitrary JSON payloads","Verify journal shape with jq after any manual edit","Ensure only the CLI writes to the journal file","Validate JSON type == object before restoring a journal from backup"],"tags":["journal","validation","schema","mcp"],"backgroundTag":"schema-validation-failed","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}