Repository navigation
fetch transport: permanent-error classification; both transports: 204/304/HEAD rows, CONTROL_BYTE regex, producer-failure race #82
Copy link
Copy link
Closed
Labels
area:transportTransport, async model, seams: TRANSPORT-* ASYNC-* SEAM-*Transport, async model, seams: TRANSPORT-* ASYNC-* SEAM-*spec:conformanceConformance-suite behavior, or a gap in the suite itselfConformance-suite behavior, or a gap in the suite itselftype:bugDefect in shipped behaviorDefect in shipped behavior
Milestone
Description
Part of the audit remediation umbrella #67. Milestone 5. Severity: MEDIUM. Requirements: TRANSPORT-9, TRANSPORT-14, TRANSPORT-20, RETRY-2, SEAM-30.
Current SDK behavior
- Every native fetch error is retryable.
fetch-transport.ts:282-289wraps every non-abort
error asTransportFailureError, whichclassify.ts:71treats as retryable.Request
accepts any absolute URL, softp://example.comreachesfetch, which rejects with a
permanentTypeError; the retry engine spends the whole budget on it. The undici transport
has aTERMINAL_ARGUMENT_CODESguard (undici-transport.ts:185-231) with a comment stating
why a permanent misconfiguration must not retry. The two transports classify the same
condition oppositely. - No body-less response coverage. For 204, 304 and any HEAD response, fetch forwards
globalThis.Response.body, which isnull(fetch-transport.ts:182); undici wraps
result.bodyunconditionally intoDemandDrivenStream(undici-transport.ts:349), a
non-null empty stream. fetch setsreasonPhrase(:180); undici never does. No fixture in
packages/transport-conformance/src/fixtures.ts:24-111returns 204/304 and no row issues
HEAD. CONTROL_BYTEomits LF.transport-shared/src/header-mapping.ts:6is
/[\x00-\x08\x0B-\x1F\x7F]/u, skipping\x0Aalong with the intended\x09. Harmless
today becauseHeaders.addInboundrejects LF and thetry/catchdrops the header.- Producer-failure race. When
producerFailurewins the race
(fetch-transport.ts:278-281,undici-transport.ts:489-504),send()rejects while the
native call is pending.abandonunwinds the producer but does not abort the fork, so a
response that arrives later is discarded with its body neither cancelled nor drained.
sdk-design-nodejs/03-….md:86-90states the obligation. Not driven by the auditor.
Expected behavior
Both transports throw a non-retryable error for a permanent misconfiguration and a retryable
TransportFailureError for a network failure. Both transports produce the same
Response.body shape for 204, 304 and HEAD, and the reasonPhrase gap is ledgered beside
§10 item 13. The inbound control-byte gate covers every C0 byte except HTAB. A send that fails
on the producer side aborts the native call so no response is stranded.
Notes and leads
- fetch: map
TypeErrors for unsupported scheme, invalid URL, forbidden method, and invalid
header to a non-IoErrorDexpaceError(or the bareTypeErrorundici uses). Keep
fetch failedwith a networkcauseasTransportFailureError. - Body-less: decide
body === nullfor body-less responses (the WHATWG shape), make undici
match, and add conformance rows for 204, 304 and HEAD assertingbody,contentLength,
reasonPhrase. - Regex:
/[\x00-\x08\x0A-\x1F\x7F]/u. - Race: abort the forked signal in the producer-failure branch, then verify with an
instrumented transport whose native call resolves after the producer fails. - Add a conformance row for an unsupported scheme that both transports run.
- Patch changesets for
@dexpace/transport-fetch,@dexpace/transport-undici,
@dexpace/transport-shared.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
area:transportTransport, async model, seams: TRANSPORT-* ASYNC-* SEAM-*Transport, async model, seams: TRANSPORT-* ASYNC-* SEAM-*spec:conformanceConformance-suite behavior, or a gap in the suite itselfConformance-suite behavior, or a gap in the suite itselftype:bugDefect in shipped behaviorDefect in shipped behavior
Activity
docs: ledger — wave 6b landed (#82), run complete; re-anchor item 13 …
Wahbeh-Mohammad commentedon Sep 5, 2026
Addressed in PR #97, merged into
audit/remediation-67; reachesmvpthrough PR #98. Decision record:docs/audit-67-decisions.md.Merge pull request #98 from dexpace/audit/remediation-67