denoland/deno · error · TypeError

Cannot change header: headers are immutable

Error message

Cannot change header: headers are immutable

What it means

appendHeader step 3: when a Headers object's guard is 'immutable', any mutation throws. Headers attached to responses returned by fetch() are immutable per the Fetch spec, so set/append/delete on them fails.

Source

Thrown at ext/fetch/20_headers.js:181

 * @param {Headers} headers
 * @param {string} name
 * @param {string} value
 */
function appendHeader(headers, name, value) {
  // 1.
  value = normalizeHeaderValue(value);

  // 2.
  if (!checkHeaderNameForHttpTokenCodePoint(name)) {
    throw new TypeError(`Invalid header name: "${name}"`);
  }
  if (!checkForInvalidValueChars(value)) {
    throw new TypeError(`Invalid header value: "${value}"`);
  }

  // 3.
  if (headers[_guard] == "immutable") {
    throw new TypeError("Cannot change header: headers are immutable");
  }

  // 7.
  const list = headerListFromHeaders(headers);
  const lowerNames = ensureLowerNames(headers);
  const lowercaseName = byteLowerCase(name);
  for (let i = 0; i < lowerNames.length; i++) {
    if (lowerNames[i] === lowercaseName) {
      name = list[i][0];
      break;
    }
  }
  ArrayPrototypePush(list, [name, value]);
  ArrayPrototypePush(lowerNames, lowercaseName);
}

function appendHeaderToList(list, name, value) {
  value = normalizeHeaderValue(value);

View on GitHub (pinned to 89f33cbef2)

Solutions

  1. Rebuild the response mutably: const copy = new Response(res.body, res); copy.headers.set('x', 'y').
  2. Modify request headers before fetch instead of response headers after.
  3. Copy entries into a fresh Headers when you only need the values: new Headers(res.headers).

Example fix

// before
const res = await fetch(url);
res.headers.set('x-cache', 'miss'); // throws
// after
const res = await fetch(url);
const copy = new Response(res.body, res);
copy.headers.set('x-cache', 'miss');
Defensive patterns

Strategy: fallback

Try / catch

let h = res.headers;
try {
  h.set('x-tag', 'v');
} catch (e) {
  if (e instanceof TypeError && /immutable/.test(e.message)) {
    const copy = new Response(res.body, res);
    copy.headers.set('x-tag', 'v');
    return copy;
  }
  throw e;
}

Prevention

When it happens

Trigger: const res = await fetch(url); res.headers.set('x', 'y'); — also append or delete on fetched response headers.

Common situations: Middleware that rewrites downstream response headers; caching layers that tag responses after fetch returns.

Related errors


AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16). Data as JSON: /api/errors/f1cc85c402e0137b. Report an issue: GitHub.