JuliusBrussee/caveman · error · Error

must be base64 or null

Error message

${field} must be base64 or null

What it means

decodePendingBytes validates a field of a pending MCP transaction journal. The value must be either JSON null or a base64 string; any other JSON type (number, boolean, object, array) is rejected. This enforces the journal's schema before bytes are decoded for integrity checks.

Solutions

  1. Restore the journal field to a valid base64 string or explicit null
  2. Delete the pending transaction journal and re-run the MCP operation from scratch
  3. Regenerate the journal by re-running the interrupted command instead of hand-editing

Example fix

// before
{ "config_before_base64": 12345 }
// after
{ "config_before_base64": "eyJhIjoxfQ==" }
Defensive patterns

Strategy: type-guard

Validate before calling

function isBase64OrNull(v: unknown): boolean {
  return v === null || (typeof v === 'string' && Buffer.from(v, 'base64').toString('base64') === v);
}

Type guard

function isDecodablePendingValue(v: unknown): v is string | null {
  return v === null || typeof v === 'string';
}

Try / catch

try {
  decodePendingBytes(journal.config_before_base64, 'config_before_base64');
} catch (e) {
  if (e instanceof Error && e.message.includes('must be base64 or null')) {
    // treat journal as invalid: discard or regenerate
  } else throw e;
}

Prevention

When it happens

Trigger: A hand-edited or partially written pending-transaction journal file contains a non-string, non-null value for config_before_base64, marker_before_base64, or marker_after_base64.

Common situations: Manually editing the journal file to repair a stuck transaction; a tool corrupting the JSON types; an older/newer schema version writing differently-typed fields.

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


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/015313b7b0fa47ea. Report an issue: GitHub.

Appendix: source

Thrown at packages/cli/src/index.ts:12695

}

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 3ee70a1026)