JuliusBrussee/caveman · error · Error
${field} is not canonical base64
Error message
${field} is not canonical base64 What it means
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.
Source
Thrown at packages/cli/src/index.ts:12326
function mcpMarkerBytes(mcp: { command: string; args: string[] }, tool: string, configPath?: string): Buffer {
return Buffer.from(JSON.stringify({
...(configPath ? { schema_version: 1 } : {}),
tool,
command: mcp.command,
args: mcp.args,
...(configPath ? { config_path: canonicalMcpConfigPath(configPath) } : {}),
}, null, 2) + "\n");
}
function validMcpMarkerBytes(bytes: Buffer, agent: "kilo" | "qwen", serverName: string): boolean {
return parseMcpServerMarkerBytes(agent, serverName, bytes) !== null;
}
function decodePendingBytes(value: unknown, field: string): Buffer | null {
if (value === null) return null;
if (typeof value !== "string") throw new Error(`${field} must be base64 or null`);
const bytes = Buffer.from(value, "base64");
if (bytes.toString("base64") !== value) throw new Error(`${field} is not canonical base64`);
return bytes;
}
function validOptionalHash(value: unknown): value is string | null {
return value === null || (typeof value === "string" && /^sha256:[0-9a-f]{64}$/.test(value));
}
type ReadOwnedMcpPendingJournal = {
journal: OwnedMcpPendingJournal;
configBefore: Buffer | null;
markerBefore: Buffer | null;
markerAfter: Buffer | null;
path: string;
bytes: Buffer;
};
function ownedMcpPendingLabel(agent?: string, serverName?: string): string {
return agent && serverName ? `${agent} ${serverName} MCP` : "owned MCP";View on GitHub (pinned to 5184b3d11a)
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.
Example fix
// before
const encoded = Buffer.from(bytes).toString('base64url')
// after
const encoded = Buffer.from(bytes).toString('base64') Defensive patterns
Strategy: validation
Validate before calling
function isCanonicalBase64(v: unknown): boolean {
return typeof v === 'string' && Buffer.from(v, 'base64').toString('base64') === v;
} Type guard
const isCanonicalB64 = (v: unknown): v is string => typeof v === 'string' && Buffer.from(v, 'base64').toString('base64') === v; Try / catch
try { decodePendingBytes(raw.config_before_base64, 'config_before_base64'); } catch (e) { /* re-encode field and retry once */ } Prevention
- 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.
When it happens
Trigger: A journal field like config_before_base64 contains base64url characters (-, _), missing '=' padding, embedded whitespace/newlines, or was encoded with a non-standard variant.
Common situations: 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.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- ${label} must be a canonical base64 Ed25519 public key
- ${field} must be base64 or null
- SKILL.md contains NUL bytes
- SKILL.md must be valid UTF-8
- pending ${label} transaction is malformed; refusing recovery
AI-assisted analysis of JuliusBrussee/caveman@5184b3d11a (2026-09-06).
Data as JSON: /api/errors/d7de81e2aff56591.
Report an issue: GitHub.