JuliusBrussee/caveman · error · Error

cave_claude_header_invalid

Error message

cave_claude_header_invalid

What it means

mergeClaudeHeader() builds raw HTTP header text that gets injected into the SDK's header configuration, so header names containing ':' or CR/LF, or values containing CR/LF, are rejected outright — they would allow header smuggling or request splitting. Any violation throws cave_claude_header_invalid before the request is made.

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 27d5a3981a)

Solutions

  1. Trim and single-line the value: value.replace(/[\r\n]+/g, "").trim() before passing it in.
  2. Fix the source: strip CRLF when loading config files, or quote env values so the shell doesn't embed newlines.
  3. Validate header names are token-shaped (^[A-Za-z0-9-]+$) at your config layer so a colon can never reach mergeClaudeHeader.

Example fix

// before: value read from a CRLF config file
const token = readLine("auth.txt"); // "abc123\r"
mergeClaudeHeader(raw, "authorization", token); // throws

// after: normalize before merging
const token = readLine("auth.txt").replace(/[\r\n]+/g, "").trim();
mergeClaudeHeader(raw, "authorization", token);
Defensive patterns

Strategy: validation

Validate before calling

function assertHeaderSafe(name: string, value: string): void {
  if (!/^[!#$%&'*+.^_|~0-9A-Za-z-]+$/.test(name) || /[\r\n]/.test(value)) {
    throw new Error(`unsafe header ${name}`);
  }
}
// run before composing options that reach the Claude harness
assertHeaderSafe("x-cave-trace", traceValue.replace(/[\r\n]+/g, "").trim());

Type guard

function isSafeHeaderValue(value: string): boolean {
  return !/[\r\n]/.test(value);
}

Try / catch

try {
  merged = mergeClaudeHeader(merged, name, value);
} catch (error) {
  if (error instanceof Error && error.message === "cave_claude_header_invalid") {
    // source data is malformed — sanitize or reject the config, never retry as-is
    throw new Error(`header '${name}' rejected: sanitize CR/LF and ':' before retry`);
  }
  throw error;
}

Prevention

When it happens

Trigger: Passing a header name via the Claude harness options that includes a colon (e.g. "X-Cave-Trace: extra") or any \r/\n; or a header value with embedded newlines (multi-line tokens, pasted certs, values read from a file with trailing CRLF that wasn't trimmed).

Common situations: Reading header values from .env or config files on Windows (CRLF line endings leak \r into values); copy-pasting multi-line API keys or bearer tokens; programmatically composing header names from user input without sanitization.

Related errors


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