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
- Reduce the hook payload: send only the event fields the hook needs, not the full transcript.
- Truncate or strip large message contents/attachments before invoking the hook.
- Increase or bypass the limit by pre-filtering in a wrapper script that pipes a slimmed payload.
- 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
- Send minimal event JSON to hooks, not full transcripts
- Store large content in files and reference paths in the payload
- Cap message content lengths before invoking hooks
- Keep the 2 MiB limit in mind when designing hook integrations
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
- cave_sandbox_input_limit
- clickhouse response exceeds
- payload_limit
- redact: payload exceeds redaction size limit
- cave_input_too_large
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)