JuliusBrussee/caveman · error
cave_claude_header_invalid
cave_claude_header_invalid
Error message
cave_claude_header_invalid
What it means
mergeClaudeHeader validates header names and values before merging into the Claude headers blob: a name containing ':', CR, or LF, or a value containing CR/LF, throws cave_claude_header_invalid to prevent header injection/malformed headers.
Solutions
- Sanitize name/value: strip or reject any of ':', '\r', '\n' before calling
- Split 'Name: value' strings into separate name and value arguments
- Trim and validate with the same regex the library uses: /[:\r\n]/ on name, /[\r\n]/ on value
Example fix
// before
mergeClaudeHeader(raw, `X-${label}`, valueWithNewline);
// after
const safeName = `X-${label.replace(/[:\r\n]/g, "")}`;
const safeValue = valueWithNewline.replace(/[\r\n]/g, " ");
mergeClaudeHeader(raw, safeName, safeValue); Defensive patterns
Strategy: validation
Validate before calling
const sanitizeHeader = ([name, value]) => [name.replace(/[:\r\n]/g, ""), value.replace(/[\r\n]/g, " ")]; Object.fromEntries(Object.entries(headers).map(sanitizeHeader));
Type guard
const isSafeHeader = ([n, v]) => !/[:\r\n]/.test(n) && !["\r", "\n"].some(c => v.includes(c));
Try / catch
try { await runClaudeAgent(opts); } catch (e) { if (e.message === "cave_claude_header_invalid") { opts.headers = sanitizeHeaders(opts.headers); return runClaudeAgent(opts); } throw e; } Prevention
- Sanitize any header material derived from user input
- Never embed 'Name: value' strings as a single value
- Reject CR/LF at your config boundary
When it happens
Trigger: Passing a header name with a colon or newline (e.g. "X:Foo" or "X\nFoo"), or a value containing CRLF, into the Claude headers configuration (via claudeHeaders / agent definition headers).
Common situations: Building headers from untrusted/user input without sanitizing; template strings embedding newlines; copy-pasted headers that include "Name: value" as a single value.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- cave_claude_max_budget_invalid
- cave_claude_max_turns_invalid
- cave_claude_tool_contract_unsupported
- header cannot be forwarded
- apiKey, baseURL, and agent are required
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/c7a8d91e5ca3fa7a.
Report an issue: GitHub.
Appendix: source
Thrown at packages/agent/src/claude-runtime.ts:451
for (const [name, value] of Object.entries(headers)) {
raw = mergeClaudeHeader(raw, name, value);
}
return raw ?? "";
}
function stripCaveHeaders(raw: string | undefined): string | undefined {
const kept = (raw ?? "").split(/\r\n|\n|\r/).filter((line) => {
if (!line.trim()) return false;
const colon = line.indexOf(":");
const name = (colon < 0 ? line : line.slice(0, colon)).trim().toLowerCase();
return !name.startsWith("x-cave-");
});
return kept.length === 0 ? undefined : kept.join("\n");
}
function mergeClaudeHeader(raw: string | undefined, name: string, value: string): string {
if (/[:\r\n]/.test(name) || /[\r\n]/.test(value)) {
throw new Error("cave_claude_header_invalid");
}
const target = name.toLowerCase();
const kept = (raw ?? "").split(/\r\n|\n|\r/).filter((line) => {
if (!line.trim()) return false;
const colon = line.indexOf(":");
return (colon < 0 ? line : line.slice(0, colon)).trim().toLowerCase() !== target;
});
kept.push(`${name}: ${value}`);
return kept.join("\n");
}
function resolveClaudeModel(definition: AgentDefinition, rootDir: string): string {
const configured = typeof definition.model === "string"
? definition.model
: process.env.CAVE_MODEL ?? localModel(rootDir) ??
(process.env.ANTHROPIC_API_KEY ? "anthropic/claude-haiku-4-5" : undefined);
if (configured === undefined) throw new Error("cave_claude_model_required");
const separator = configured.indexOf("/");View on GitHub (pinned to 3ee70a1026)