{"record":{"id":"d7de81e2aff56591","repo":"JuliusBrussee/caveman","slug":"field-is-not-canonical-base64","errorCode":null,"errorMessage":"${field} is not canonical base64","messagePattern":"(.+?) is not canonical base64","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":12326,"sourceCode":"function mcpMarkerBytes(mcp: { command: string; args: string[] }, tool: string, configPath?: string): Buffer {\n  return Buffer.from(JSON.stringify({\n    ...(configPath ? { schema_version: 1 } : {}),\n    tool,\n    command: mcp.command,\n    args: mcp.args,\n    ...(configPath ? { config_path: canonicalMcpConfigPath(configPath) } : {}),\n  }, null, 2) + \"\\n\");\n}\n\nfunction validMcpMarkerBytes(bytes: Buffer, agent: \"kilo\" | \"qwen\", serverName: string): boolean {\n  return parseMcpServerMarkerBytes(agent, serverName, bytes) !== null;\n}\n\nfunction decodePendingBytes(value: unknown, field: string): Buffer | null {\n  if (value === null) return null;\n  if (typeof value !== \"string\") throw new Error(`${field} must be base64 or null`);\n  const bytes = Buffer.from(value, \"base64\");\n  if (bytes.toString(\"base64\") !== value) throw new Error(`${field} is not canonical base64`);\n  return bytes;\n}\n\nfunction validOptionalHash(value: unknown): value is string | null {\n  return value === null || (typeof value === \"string\" && /^sha256:[0-9a-f]{64}$/.test(value));\n}\n\ntype ReadOwnedMcpPendingJournal = {\n  journal: OwnedMcpPendingJournal;\n  configBefore: Buffer | null;\n  markerBefore: Buffer | null;\n  markerAfter: Buffer | null;\n  path: string;\n  bytes: Buffer;\n};\n\nfunction ownedMcpPendingLabel(agent?: string, serverName?: string): string {\n  return agent && serverName ? `${agent} ${serverName} MCP` : \"owned MCP\";","sourceCodeStart":12308,"sourceCodeEnd":12344,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/5184b3d11ac6a1acb7d44b9bfaa31698157cff97/packages/cli/src/index.ts#L12308-L12344","documentation":"Thrown by decodePendingBytes when a journal field is a string but not the canonical base64 encoding of its bytes (Buffer.from(value,'base64').toString('base64') !== value). This rejects non-canonical forms such as base64 with wrong padding or urlsafe alphabets, preventing ambiguous or tampered journal data.","triggerScenarios":"A journal field like config_before_base64 contains base64url characters (-, _), missing '=' padding, embedded whitespace/newlines, or was encoded with a non-standard variant.","commonSituations":"Re-encoding journal bytes with a urlsafe encoder; stripping padding with sed; copying base64 through a channel that inserted line wraps; generating journal files with a different tool version.","solutions":["Re-encode the bytes with standard padded base64: Buffer.from(bytes).toString('base64').","Normalize the value: strip whitespace, convert urlsafe chars back (+, /), and re-pad to a multiple of 4 with '='.","Regenerate the journal by re-running the pending transaction rather than fixing the encoding by hand."],"exampleFix":"// before\nconst encoded = Buffer.from(bytes).toString('base64url')\n// after\nconst encoded = Buffer.from(bytes).toString('base64')","handlingStrategy":"validation","validationCode":"function isCanonicalBase64(v: unknown): boolean {\n  return typeof v === 'string' && Buffer.from(v, 'base64').toString('base64') === v;\n}","typeGuard":"const isCanonicalB64 = (v: unknown): v is string => typeof v === 'string' && Buffer.from(v, 'base64').toString('base64') === v;","tryCatchPattern":"try { decodePendingBytes(raw.config_before_base64, 'config_before_base64'); } catch (e) { /* re-encode field and retry once */ }","preventionTips":["Use the standard (not base64url) encoder when writing journal fields.","Do not wrap base64 values with newlines or strip padding.","Round-trip check encodings in tests before writing journals."],"tags":["base64","encoding","journal-validation"],"backgroundTag":"invalid-argument-format","analyzedSha":"5184b3d11ac6a1acb7d44b9bfaa31698157cff97","analyzedAt":"2026-09-06T12:00:26.372Z","contentChangedAt":"2026-09-06T12:00:26.372Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}