JuliusBrussee/caveman · error

hook payload too large

Error message

hook payload too large

What it means

The native fast hook reads the agent's hook payload (tool-call JSON) from stdin with a hard 2 MiB cap to keep the hot path cheap and avoid unbounded memory. When accumulated chunk bytes exceed 2*1024*1024 the read loop aborts with this error. It is a deliberate resource guard, not a parsing failure.

Source

Thrown at packages/cli/src/native-hook-fast.ts:100

  if (explicit !== undefined && explicit !== "") return explicit === "safe" || explicit === "max" || explicit === "record" ? explicit : "record";
  const mode = configuredMode();
  return mode === "record" ? "record" : mode === "pixel" ? "max" : "safe";
}

function profile(): NativeProfile {
  const explicit = process.env.CAVEMAN_NATIVE_PROFILE?.trim().toLowerCase() as NativeProfile | undefined;
  if (explicit) return PROFILES.has(explicit) ? explicit : "record-only";
  const mode = policyMode();
  return mode === "record" ? "record-only" : mode === "max" ? "full-max" : "full-safe";
}

async function stdin(): Promise<Buffer> {
  const chunks: Buffer[] = [];
  let bytes = 0;
  for await (const chunk of process.stdin) {
    const value = Buffer.from(chunk);
    bytes += value.length;
    if (bytes > 2 * 1024 * 1024) throw new Error("hook payload too large");
    chunks.push(value);
  }
  return Buffer.concat(chunks);
}

function bounded(value: unknown, max = 160): string | undefined {
  if (typeof value !== "string") return undefined;
  const clean = value.replace(/[\r\n\0]/g, " ").trim();
  return clean ? clean.slice(0, max) : undefined;
}

function digestObject(value: unknown): { bytes: number; sha256: string } | undefined {
  if (value && typeof value === "object" && !Array.isArray(value)) {
    const candidate = value as Record<string, unknown>;
    if (typeof candidate.bytes === "number" && candidate.bytes >= 0 && typeof candidate.sha256 === "string" && /^sha256:[0-9a-f]{6,64}$/i.test(candidate.sha256)) {
      return { bytes: Math.floor(candidate.bytes), sha256: candidate.sha256.toLowerCase() };
    }
  }

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Reduce the payload: have the tool input reference files by path instead of inlining megabytes of content.
  2. Filter hook events: configure the agent's hook matcher so only the event names you need are delivered to the native hook.
  3. If you control the producer, chunk or truncate large fields before they reach the hook process.
  4. Do not attempt to raise the cap by patching; it is compiled into the guard — restructure the payload instead.

Example fix

// before: tool input inlines a 5 MiB string
{ "tool_input": { "content": "<5 MiB of text>" } }
// after: reference by path
{ "tool_input": { "path": "large-file.txt" } }
Defensive patterns

Strategy: validation

Validate before calling

// Producer side: keep hook events small before they reach the native hook
function assertPayloadSize(json: unknown): void {
  const bytes = Buffer.byteLength(JSON.stringify(json) ?? "");
  if (bytes > 2 * 1024 * 1024) {
    throw new Error(`hook payload ${bytes}B exceeds 2 MiB; trim tool_input content`);
  }
}

Try / catch

try {
  const payload = await stdin();
} catch (e) {
  if (e instanceof Error && e.message === "hook payload too large") {
    process.exit(0); // treat oversized event as no-op: never fail the agent's tool call
  }
  throw e;
}

Prevention

When it happens

Trigger: An agent emits a hook event (e.g. PreToolUse/PostToolUse JSON) whose serialized payload exceeds 2 MiB — typically huge file writes, enormous command strings, or massive tool outputs embedded in the event.

Common situations: Write/Edit tools with multi-megabyte file contents, pasted base64 blobs, agents streaming giant diffs through hooks, or a misconfigured hook that forwards whole conversation transcripts.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/342fa956f11c86d0. Report an issue: GitHub.