ruvnet/ruflo · error · HttpFetchValidationError

INVALID_HEADER_VALUE

INVALID_HEADER_VALUE

Error message

header "${key}" must be a string

What it means

Thrown by validateHeaders when a header value is not a string. HTTP headers must be string-typed for fetch; numbers, booleans, objects, or arrays are rejected to avoid silent coercion bugs and Node fetch type errors downstream. The error carries code INVALID_HEADER_VALUE and names the offending header key.

Source

Thrown at v3/@claude-flow/cli/src/mcp-tools/http-fetch-tools.ts:116

  const out: Record<string, string> = {};
  for (const [key, value] of Object.entries(headers)) {
    const lower = key.toLowerCase();
    if (!allowAuth) {
      if ((FORBIDDEN_HEADERS_EXACT as readonly string[]).includes(lower)) {
        throw new HttpFetchValidationError(
          `header "${key}" is not allowed without CLAUDE_FLOW_HTTP_FETCH_ALLOW_AUTH=1`,
          'FORBIDDEN_HEADER',
        );
      }
      if (FORBIDDEN_HEADER_PREFIXES.some((p) => lower.startsWith(p))) {
        throw new HttpFetchValidationError(
          `header "${key}" is not allowed without CLAUDE_FLOW_HTTP_FETCH_ALLOW_AUTH=1`,
          'FORBIDDEN_HEADER',
        );
      }
    }
    if (typeof value !== 'string') {
      throw new HttpFetchValidationError(
        `header "${key}" must be a string`,
        'INVALID_HEADER_VALUE',
      );
    }
    out[key] = value;
  }
  return out;
}

function clampNumber(raw: unknown, defaultValue: number, max: number): number {
  if (raw === undefined || raw === null) return defaultValue;
  const n = Number(raw);
  if (!Number.isFinite(n) || n <= 0) return defaultValue;
  return Math.min(Math.floor(n), max);
}

export interface HttpFetchResult {
  success: boolean;

View on GitHub (pinned to 6b01dc5a68)

Solutions

  1. Stringify every header value: String(value) or template literals.
  2. Move non-string metadata out of headers into the request body or a separate field.
  3. Validate headers with an object-string-values type guard before calling http_fetch.
  4. For JSON content, set 'content-type': 'application/json' and pass the JSON string in the body, not headers.

Example fix

// before
http_fetch({ url, headers: { 'x-retry': 3 } })
// after
http_fetch({ url, headers: { 'x-retry': String(3) } })
Defensive patterns

Strategy: type-guard

Validate before calling

function stringifyHeaders(headers) {
  const out = {};
  for (const [k, v] of Object.entries(headers)) out[k] = typeof v === 'string' ? v : String(v);
  return out;
}

Type guard

function isStringHeaderRecord(h: unknown): h is Record<string, string> {
  return typeof h === 'object' && h !== null && !Array.isArray(h)
    && Object.values(h).every((v) => typeof v === 'string');
}

Prevention

When it happens

Trigger: Passing a headers object where any value is a number (e.g. timeout: 30), boolean, object, array, or null. The check runs after the forbidden-header gate, so a forbidden header with a non-string value throws FORBIDDEN_HEADER first.

Common situations: Passing a numeric retry/timeout as a header; a config object mistakenly nested in headers; a boolean flag; JSON objects intended as header metadata; Date objects.

Related errors


AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12). Data as JSON: /api/errors/74b9fc4d9b8d210a. Report an issue: GitHub.