JuliusBrussee/caveman · error · Error

pending transaction is malformed; refusing recovery

Error message

pending ${label} transaction is malformed; refusing recovery

What it means

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.

Solutions

  1. Restore the journal to the expected object schema with all required keys (action, agent, config_path, schema_version, etc.)
  2. Delete the invalid journal and re-run the MCP operation to generate a fresh one
  3. Verify with jq that the file parses as an object: jq -e 'type == "object"' journal.json

Example fix

// before
[]
// after
{ "schema_version": 1, "action": "install", "agent": "kilo", "config_path": "...", "transaction_id": "..." }
Defensive patterns

Strategy: validation

Validate before calling

import { readFileSync } from 'fs';
function journalIsObject(path: string): boolean {
  try {
    const v = JSON.parse(readFileSync(path, 'utf8'));
    return !!v && typeof v === 'object' && !Array.isArray(v);
  } catch { return false; }
}

Type guard

function isJournalObject(v: unknown): v is Record<string, unknown> {
  return !!v && typeof v === 'object' && !Array.isArray(v);
}

Try / catch

try {
  recoverPendingTransaction(label, path);
} catch (e) {
  if (e instanceof Error && e.message.includes('transaction is malformed')) {
    // journal unusable: remove it and re-run the MCP operation
  } else throw e;
}

Prevention

When it happens

Trigger: The journal file contains valid JSON but not an object — e.g. the file was overwritten with '[]', '"text"', 'null', or a bare number.

Common situations: 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.

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/b654c91becd9950c. Report an issue: GitHub.

Appendix: source

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

  return agent && serverName ? `${agent} ${serverName} MCP` : "owned MCP";
}

function readOwnedMcpPendingJournalAt(
  path: string,
  expected: { agent?: "kilo" | "qwen"; serverName?: string; configPath?: string; locatorPath?: string } = {},
): ReadOwnedMcpPendingJournal | null {
  const label = ownedMcpPendingLabel(expected.agent, expected.serverName);
  let bytes: Buffer;
  let value: Record<string, unknown>;
  try {
    bytes = readFileSync(path);
    value = JSON.parse(bytes.toString("utf8")) as Record<string, unknown>;
  } catch (error) {
    if ((error as NodeJS.ErrnoException).code === "ENOENT") return null;
    throw new Error(`cannot read pending ${label} transaction: ${(error as Error).message}`);
  }
  if (!value || typeof value !== "object" || Array.isArray(value)) {
    throw new Error(`pending ${label} transaction is malformed; refusing recovery`);
  }
  if (process.platform !== "win32" && (statSync(path).mode & 0o077) !== 0) {
    throw new Error(`pending ${label} transaction has unsafe permissions; refusing recovery`);
  }
  const keys = [
    "action", "agent", "config_after_sha256", "config_before_base64", "config_before_mode", "config_path",
    "config_before_sha256", "marker_after_base64", "marker_after_sha256", "marker_before_base64", "marker_before_mode",
    "marker_before_sha256", "marker_path", "schema_version", "server_name", "transaction_id",
  ].sort();
  const journalAgent = value.agent;
  const journalServer = value.server_name;
  if (Object.keys(value).sort().join("\0") !== keys.join("\0")
    || value.schema_version !== 1
    || typeof value.transaction_id !== "string"
    || !/^[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)
    || (journalAgent !== "kilo" && journalAgent !== "qwen")
    || typeof journalServer !== "string"
    || !mcpServerToolName(journalServer)

View on GitHub (pinned to 3ee70a1026)