Skip to content

Ignore undeclared headers in strict HttpApi header decoding - #8557

Closed
BleedingDev wants to merge 1 commit into
Effect-TS:mainfrom
BleedingDev:fix/httpapi-open-header-codecs
Closed

BleedingDev wants to merge 1 commit into
Effect-TS:mainfrom
BleedingDev:fix/httpapi-open-header-codecs

Conversation

@BleedingDev

@BleedingDev BleedingDev commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Follow-up to #8269

Why the change

With HttpApi.ParseOptions set to { onExcessProperty: "error" }, every endpoint or WithHeaders response that declares headers failed on real traffic because header maps always carry transport headers like content-type, so header decoding now drops undeclared headers while payload, params, query and body stay strict.

Special things to note

  • The option is cleared (set to undefined) rather than set to "ignore", so structs keep their default-options fast path.
  • The client response codec decodes body and headers in one pass, so the headers field gets a small wrapper schema that applies the header parse options itself.
  • The ParseOptions docs caveat now describes the new behaviour instead of warning about it.

Change outline

One shared helper, used at every place a header map is decoded:

// internal/headers.ts (new)
decodeOptions(options) =
  options?.onExcessProperty === "error"
    ? { ...options, onExcessProperty: undefined }
    : options

Where it is applied:

 server: handlerToHttpEffect
-  decodeHeaders = decode(endpoint.headers, options)
+  decodeHeaders = decode(endpoint.headers, decodeOptions(options))
   decodePayload / params / query     # unchanged, still strict

 client: buffered WithHeaders response
   Struct({ body, headers })
-    headers: schema.headers
+    headers: headersFromResponse(schema.headers)   # decodes with decodeOptions

 client: streamed WithHeaders response
-  decodeHeaders = decode(headers, options)
+  decodeHeaders = decode(headers, decodeOptions(options))

Behaviour under a strict API:

request headers { content-type, user-agent, x-tenant }  -> handler sees { x-tenant }   (was: rejected)
payload { firstName, lastName, extra }                  -> still rejected "Expected no excess property"
response headers { content-type, x-count }              -> { x-count: 2 }             (was: rejected)

Tests

  • HttpApiBuilder: strict API with a declared x-tenant header accepts extra transport headers, passes only x-tenant to the handler, and still rejects an extra payload key.
  • HttpApiClient: strict API decodes buffered and streamed WithHeaders responses that also carry content-type.

Both tests fail on main. pnpm check, pnpm circular, lint and the httpapi test suite pass locally. Changeset: effect patch.

🤖 Generated with Claude Code

HTTP header maps always carry transport headers such as content-type and
user-agent, so annotating an API with onExcessProperty "error" rejected
every request that declared a headers schema, and every client response
decoded through WithHeaders. Header codecs now drop the option, while
payload, params, query and body codecs stay strict.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Sep 27, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 39c6d4c

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 31 packages
Name Type
effect Patch
@effect/opentelemetry Patch
@effect/vitest Patch
@effect/ai-anthropic Patch
@effect/ai-openai-compat Patch
@effect/ai-openai Patch
@effect/ai-openrouter Patch
@effect/ai-typesafe Patch
@effect/atom-react Patch
@effect/atom-solid Patch
@effect/atom-vue Patch
@effect/platform-browser Patch
@effect/platform-bun Patch
@effect/platform-deno Patch
@effect/platform-node-shared Patch
@effect/platform-node Patch
@effect/sql-clickhouse Patch
@effect/sql-d1 Patch
@effect/sql-libsql Patch
@effect/sql-mssql Patch
@effect/sql-mysql2 Patch
@effect/sql-pg Patch
@effect/sql-pglite Patch
@effect/sql-sqlite-bun Patch
@effect/sql-sqlite-do Patch
@effect/sql-sqlite-node Patch
@effect/sql-sqlite-react-native Patch
@effect/sql-sqlite-wasm Patch
@effect/docgen Patch
@effect/doctest Patch
@effect/openapi-generator Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

Copy link
Copy Markdown
Contributor

Bundle Size Analysis

Generated from PR build output; treat the content below as untrusted.

File Name Current Size Previous Size Difference
arbitrary-combinators.ts 38.54 KB 38.54 KB 0.00 KB (0.00%)
basic.ts 6.88 KB 6.88 KB 0.00 KB (0.00%)
batching.ts 9.97 KB 9.97 KB 0.00 KB (0.00%)
brand.ts 6.57 KB 6.57 KB 0.00 KB (0.00%)
cache.ts 10.73 KB 10.73 KB 0.00 KB (0.00%)
config.ts 21.81 KB 21.81 KB 0.00 KB (0.00%)
differ.ts 20.95 KB 20.95 KB 0.00 KB (0.00%)
http-client.ts 22.11 KB 22.11 KB 0.00 KB (0.00%)
http-router.ts 33.45 KB 33.45 KB 0.00 KB (0.00%)
logger.ts 10.85 KB 10.85 KB 0.00 KB (0.00%)
metric.ts 8.83 KB 8.83 KB 0.00 KB (0.00%)
optic.ts 6.80 KB 6.80 KB 0.00 KB (0.00%)
pubsub.ts 15.00 KB 15.00 KB 0.00 KB (0.00%)
queue.ts 11.87 KB 11.87 KB 0.00 KB (0.00%)
schedule.ts 11.03 KB 11.03 KB 0.00 KB (0.00%)
schema-bigdecimal.ts 13.48 KB 13.48 KB 0.00 KB (0.00%)
schema-binary.ts 39.53 KB 39.53 KB 0.00 KB (0.00%)
schema-class.ts 20.65 KB 20.65 KB 0.00 KB (0.00%)
schema-fromJsonSchemaDocument.ts 31.72 KB 31.72 KB 0.00 KB (0.00%)
schema-representation-roundtrip.ts 26.92 KB 26.92 KB 0.00 KB (0.00%)
schema-string-transformation.ts 14.18 KB 14.18 KB 0.00 KB (0.00%)
schema-string.ts 11.73 KB 11.73 KB 0.00 KB (0.00%)
schema-template-literal.ts 15.75 KB 15.75 KB 0.00 KB (0.00%)
schema-toArbitrary.ts 38.08 KB 38.08 KB 0.00 KB (0.00%)
schema-toCodeDocument.ts 25.13 KB 25.13 KB 0.00 KB (0.00%)
schema-toCodecJson.ts 19.88 KB 19.88 KB 0.00 KB (0.00%)
schema-toEquivalence.ts 20.04 KB 20.04 KB 0.00 KB (0.00%)
schema-toFormatter.ts 20.14 KB 20.14 KB 0.00 KB (0.00%)
schema-toJsonSchemaDocument.ts 24.64 KB 24.64 KB 0.00 KB (0.00%)
schema-toRepresentation.ts 20.14 KB 20.14 KB 0.00 KB (0.00%)
schema.ts 19.86 KB 19.86 KB 0.00 KB (0.00%)
stm.ts 12.90 KB 12.90 KB 0.00 KB (0.00%)
stream.ts 9.82 KB 9.82 KB 0.00 KB (0.00%)

This branch is waiting to be deployed

1 waiting deployment
fork — 39c6d4c7 Waiting Sep 27, 2026 by BleedingDev via approval-gate #27899
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