JuliusBrussee/caveman · error · Error
${field} must be base64 or null
Error message
${field} must be base64 or null What it means
Thrown by decodePendingBytes when a pending-journal field that should hold bytes is neither null nor a base64 string. The journal stores config/marker snapshots as canonical base64; any other JSON type cannot be decoded safely, so recovery refuses to proceed.
Source
Thrown at packages/cli/src/index.ts:12324
}
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;
};
View on GitHub (pinned to 5184b3d11a)
Solutions
- Set the offending field to a valid canonical base64 string (Buffer.from(bytes).toString('base64')) or literal null.
- Regenerate the journal by re-running the transaction (install/uninstall the MCP server) instead of hand-editing it.
- Delete the .pending journal files only if you understand the transaction will not be recovered, then re-apply the config change.
- Check the journal against the expected keys/schema_version before recovery.
Example fix
// before
{ "config_before_base64": 123 }
// after
{ "config_before_base64": "eyJtY3BTZXJ2ZXJzIjp7fX0=" } Defensive patterns
Strategy: validation
Validate before calling
function isBase64OrNull(v: unknown): boolean {
return v === null || (typeof v === 'string' && Buffer.from(v, 'base64').toString('base64') === v);
} Type guard
const isB64OrNull = (v: unknown): v is string | null => v === null || (typeof v === 'string' && /^[A-Za-z0-9+/]*={0,2}$/.test(v)); Try / catch
try { journal = readOwnedMcpPendingJournal(); } catch (e) { console.error('Pending journal unusable, skipping recovery:', e.message); } Prevention
- Always encode journal byte fields with Buffer.from(bytes).toString('base64').
- Never hand-edit .pending journal files.
- Validate journal JSON against the schema before attempting recovery.
When it happens
Trigger: Calling the pending-transaction recovery path with a journal JSON file where config_before_base64, marker_before_base64, or marker_after_base64 is a number, object, array, or boolean instead of a base64 string or null.
Common situations: Hand-editing a .pending journal file; a third-party tool rewriting the journal with wrong types; schema drift after a version upgrade changed field encodings.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- ${field} is not canonical base64
- cursor.${key} must be a string
- ${label} must be a canonical base64 Ed25519 public key
- pending ${label} transaction is malformed; refusing recovery
AI-assisted analysis of JuliusBrussee/caveman@5184b3d11a (2026-09-06).
Data as JSON: /api/errors/015313b7b0fa47ea.
Report an issue: GitHub.