Skip to content

fix: stop dropping command and runtime headers when the client has none - #15

Merged
maxholman[bot] merged 1 commit into
masterfrom
fix/resolve-headers-drops-headers
Sep 3, 2026
Merged

fix: stop dropping command and runtime headers when the client has none#15
maxholman[bot] merged 1 commit into
masterfrom
fix/resolve-headers-drops-headers

Conversation

@maxholman

@maxholman maxholman Bot commented Sep 3, 2026

Copy link
Copy Markdown

The regression

Reported downstream: 5 tests in services/listing/test/app.test.ts fail against @block65/rest-client@14.0.0 — every one that sends a body.

RestServiceClient.#resolveHeaders bails before merging anything:

async #resolveHeaders(command: Command, runtimeOptions?: RuntimeOptions) {
  if (!this.#headers) {
    return {};   // drops command.headers AND runtimeOptions.headers
  }

A client constructed without config.headers sends no headers at all — including the accept and content-type that json() sets on every call. That is why only body-sending requests fail: they arrive without content-type: application/json and the server never parses them.

How it happened

0f03f56 inlined the old resolveHeaders() helper from lib/common.ts. That helper's if (!headers) return {} guard was correct there, because it received the already-merged object:

headers: await resolveHeaders({
  ...this.#headers,
  ...command.headers,
  ...runtimeOptions?.headers,
}),

The refactor carried the guard along but re-pointed it at this.#headers alone, so it now short-circuits the merge rather than an empty result.

The same refactor also flipped precedence — client headers were spread over command headers instead of under them, so a command could no longer override a client default.

The fix

Resolve this.#headers ?? {} and merge unconditionally, restoring the pre-14.0.0 order: client < command < runtime.

Why the suite missed it

Every test in describe("Client") shares one client constructed with headers, so this.#headers was never falsy and the early return never ran. No test constructed a bare client, and no command carried a header colliding with a client one, so the precedence flip was invisible too.

Four tests added: a bare-client block (json() defaults; command + runtime headers) and two precedence tests. Three fail without the fix — the fourth (runtime over command) passed either way, since the runtime spread was last in both versions.

Checks

tsc clean, oxfmt --check clean, oxlint unchanged (same 2 pre-existing warnings), 41/41 tests pass.

Note

14.0.0 is published, so this needs a 14.0.1 once merged.

🤖 Generated with Claude Code

0f03f56 moved the `if (!headers) return {}` guard from the old
resolveHeaders() helper — which received the already-merged object — onto
this.#headers alone. A client constructed without config headers therefore
short-circuited the merge and sent no headers at all, losing command.headers,
runtimeOptions.headers, and json()'s own accept/content-type defaults. Every
request with a body arrived unparsed.

The same refactor also flipped precedence, spreading client headers over
command headers instead of under them. Both are restored to the pre-14.0.0
order: client < command < runtime.

Covered by four tests, three of which fail without this change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@maxholman
maxholman force-pushed the fix/resolve-headers-drops-headers branch from dd80b0f to 051cec9 Compare September 3, 2026 03:59
@maxholman
maxholman Bot merged commit cdd50fc into master Sep 3, 2026
4 checks passed
@maxholman
maxholman Bot deleted the fix/resolve-headers-drops-headers branch September 3, 2026 04:02
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