JuliusBrussee/caveman · error · Error

hook payload too large

Error message

hook payload too large

What it means

Thrown by stdin() in the native hook fast path when the JSON payload piped into the hook process exceeds 2 MiB. The hook reads stdin incrementally and aborts as soon as the accumulated byte count crosses the limit, protecting the native binary from unbounded memory use. Callers (Claude/agent hook pipelines) sending oversized transcripts or attachments trigger it.

Solutions

  1. Reduce the hook payload: send only the event fields the hook needs, not the full transcript.
  2. Truncate or strip large message contents/attachments before invoking the hook.
  3. Increase or bypass the limit by pre-filtering in a wrapper script that pipes a slimmed payload.
  4. Move big data to a file and pass a path in the JSON payload instead of inline content.

Example fix

// before
{ "transcript": <entire 5MB session json> } | caveman-hook
// after
{ "type": "PostToolUse", "tool": "Bash", "summary": "..." } | caveman-hook
Defensive patterns

Strategy: validation

Validate before calling

const payload = JSON.stringify(hookEvent);
if (Buffer.byteLength(payload) > 2 * 1024 * 1024) {
  // strip or truncate large fields before piping to the hook
  delete hookEvent.transcript;
}
process.stdout.write(JSON.stringify(hookEvent));

Try / catch

try {
  runHookWithStdin(payload);
} catch (e) {
  if (e instanceof Error && e.message === "hook payload too large") {
    console.error("Hook payload exceeds 2 MiB; trim transcript/attachments and resend");
  } else throw e;
}

Prevention

When it happens

Trigger: Piping a hook payload >2*1024*1024 bytes into the native hook binary's stdin, e.g. a conversation transcript with very large embedded file contents or base64 attachments.

Common situations: Agent configured to feed full session transcripts to hooks, large pasted documents/images included in the payload, or a misconfigured hook forwarding an entire log file instead of an event summary.

Understand the failure class

Background: payload too large / request exceeds maximum size: why libraries cap bytes and how to fix oversize payloads — this error's family across 50 libraries.

Related errors


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

Appendix: source

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

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";
}

// Return as soon as the accumulated bytes parse as one complete JSON payload
// instead of waiting for EOF. The host writes one object and closes, but under
// the Windows pipe implementation that close can lag arbitrarily (#729/#833),
// and a hook that blocks on EOF spends the host's whole budget waiting for a
// close it already has all the data for.
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);
    const joined = Buffer.concat(chunks);
    // A partial payload throws and we wait for more bytes.
    try { JSON.parse(joined.toString("utf8")); } catch { continue; }
    return joined;
  }
  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>;

View on GitHub (pinned to 3ee70a1026)