JuliusBrussee/caveman · error · Error

pending ${label} transaction is malformed; refusing recovery

Error message

pending ${label} transaction is malformed; refusing recovery

What it means

Thrown during journal recovery when the parsed JSON is not a plain object (null, array, or non-object). The library requires a record-shaped journal before validating its fields, and refuses recovery on anything else to avoid acting on garbage data.

Source

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

  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 5184b3d11a)

Solutions

  1. Open the .pending journal and confirm it is a single JSON object; fix or delete it.
  2. Delete the malformed journal files and re-run the MCP server transaction to regenerate them.
  3. Check that no concurrent process (editor, sync tool) is rewriting the journal into a different shape.

Example fix

// before (journal content)
[{ "schema_version": 1 }]
// after
{ "schema_version": 1, "action": "install", ... }
Defensive patterns

Strategy: type-guard

Validate before calling

const parsed: unknown = JSON.parse(fs.readFileSync(p, 'utf8'));
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) { /* abort before recovery */ }

Type guard

const isRecord = (v: unknown): v is Record<string, unknown> => v !== null && typeof v === 'object' && !Array.isArray(v);

Try / catch

try { recoverPending(); } catch (e) {
  if (e.message.includes('is malformed; refusing recovery')) { /* delete journal and redo transaction */ }
}

Prevention

When it happens

Trigger: The pending journal file contains 'null', a JSON array like [], or a bare scalar/string instead of the expected {"schema_version":..., ...} object.

Common situations: A crash or buggy writer emptied the file to 'null'; a script overwrote the journal with an array of entries; manual editing mistakes.

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@5184b3d11a (2026-09-06). Data as JSON: /api/errors/b654c91becd9950c. Report an issue: GitHub.