Skip to content

fix: normalize headers reassigned in an onRequest hook#604

Open
greymoth-jp wants to merge 1 commit into
unjs:mainfrom
greymoth-jp:fix/normalize-headers-onrequest-hook
Open

fix: normalize headers reassigned in an onRequest hook#604
greymoth-jp wants to merge 1 commit into
unjs:mainfrom
greymoth-jp:fix/normalize-headers-onrequest-hook

Conversation

@greymoth-jp

@greymoth-jp greymoth-jp commented Jun 30, 2026

Copy link
Copy Markdown

resolveFetchOptions returns options.headers as a Headers instance, but an onRequest hook can reassign it to a plain object (a valid HeadersInit). In v2 the request flow reads context.options.headers.get("content-type") immediately after the hook runs, so reassigning a plain object throws TypeError: context.options.headers.get is not a function on any payload request with a JSON-serializable body.

This was already handled on v1 in #524 (and #436 before it), but the v2 rewrite reintroduced it. The normalization now lives inside the JSON-body branch and runs after that first .get() call, so it never gets a chance to run.

Repro:

await $fetch("/api", {
  method: "POST",
  body: { hello: "world" },
  onRequest(ctx) {
    ctx.options.headers = { "x-foo": "bar" }; // throws before this fix
  },
});

The fix mirrors #524: re-normalize options.headers back to a Headers instance right after the onRequest hook, and drop the now-redundant re-creation in the body branch (resolveFetchOptions already returns a fresh Headers, so the later .set() calls can mutate it directly).

Added a regression test covering both a Headers instance and a plain object reassigned in the hook, matching the v1 test. The new test fails without the change (headers.get is not a function) and passes with it. Full suite (29) is green and eslint / prettier / tsc --noEmit are clean.

Summary by CodeRabbit

  • Bug Fixes
    • Improved request header handling so headers remain safe to read and update after request hooks modify them.
    • Fixed an issue where reassigned headers could cause inconsistent request behavior when using different header formats.
  • Tests
    • Added coverage for header reassignment scenarios to confirm requests still send the expected headers.

An `onRequest` hook can reassign `options.headers` to a plain object, but
the request flow calls `.get()` on it right after the hook runs, throwing
`headers.get is not a function` for payload requests. Re-normalize it to a
`Headers` instance after the hook, mirroring the v1 fix in unjs#524.
@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f825c818-1ef3-4d8e-a926-58f29da14d0f

📥 Commits

Reviewing files that changed from the base of the PR and between 9102908 and 67b26c6.

📒 Files selected for processing (2)
  • src/fetch.ts
  • test/index.test.ts

📝 Walkthrough

Walkthrough

In $fetchRaw, headers are now normalized to a Headers instance after onRequest hooks run, guarding against reassignment to other HeadersInit shapes. The redundant headers re-initialization in the JSON body branch is removed. A new test covers both Headers instance and plain-object reassignment scenarios.

Changes

Header normalization after onRequest hooks

Layer / File(s) Summary
Normalization logic in $fetchRaw
src/fetch.ts
After onRequest hooks run, context.options.headers is coerced to a Headers instance if it isn't one already. The redundant context.options.headers re-initialization inside the JSON body branch is removed, relying on the earlier normalization.
onRequest header reassignment tests
test/index.test.ts
New test case asserting that reassigning ctx.options.headers to a Headers instance or a plain object in onRequest results in the correct normalized headers on the sent request.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

🐇 When headers get swapped in the hook's little game,
I wrap them in new Headers(...) all the same.
No matter the shape — object, instance, or more —
normalization ensures they behave as before.
Consistent and tidy, the fetch can proceed! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main fix: normalizing headers reassigned in an onRequest hook.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant