Skip to content

fix(upload): harden fetch upload streams#2905

Merged
sjinks merged 1 commit into
trunkfrom
fix/upload-fetch-failure-cause
Jun 23, 2026
Merged

fix(upload): harden fetch upload streams#2905
sjinks merged 1 commit into
trunkfrom
fix/upload-fetch-failure-cause

Conversation

@sjinks

@sjinks sjinks commented Jun 23, 2026

Copy link
Copy Markdown
Member

Summary

  • replaces upload progress PassThrough/data listeners with Transform streams that count bytes while forwarding them to undici.fetch
  • removes the unsupported Expect header before dispatching requests through undici.fetch
  • preserves useful low-level fetch cause details when fetchWithRetry exhausts retries
  • adds regression coverage for:
    • single-PUT uploads streaming exactly Content-Length bytes
    • TypeError: fetch failed with an underlying write ECONNRESET cause
    • incoming Expect: 100-continue headers

Why

Follow-up from PLTFRM-2491 / the custom deploy incident thread. After v4.0.7, the customer no longer sees the disturbed-stream or missing-duplex errors, but later debug traces exposed two more local undici failure modes:

[cause]: NotSupportedError: expect header not supported
code: 'UND_ERR_NOT_SUPPORTED'

and on the single-PUT path:

[cause]: RequestContentLengthMismatchError: Request body length does not match content-length header
code: 'UND_ERR_REQ_CONTENT_LENGTH_MISMATCH'

undici.fetch cannot send the Expect header, so leaving that header in presigned options guarantees a local failure before the request reaches S3.

For the content-length mismatch, the upload body was piped through a PassThrough with a data listener for progress. That can put the stream into flowing mode before undici attaches its reader, allowing progress accounting to consume bytes that are no longer delivered to fetch. Using a Transform keeps progress accounting in the same stream pipeline as the upload body and preserves backpressure/byte delivery.

If future retries still exhaust with a lower-level transport cause, the final error becomes more actionable, e.g. fetch failed: write ECONNRESET, without including presigned URLs or other request details.

Testing

  • npm run jest -- __tests__/lib/client-file-uploader.js __tests__/lib/fetch-with-retry-undici.js --runInBand (ran all non-e2e suites: 56 passed / 593 tests)
  • npm run check-types
  • npm run cmd:lint -- src/lib/client-file-uploader.ts __tests__/lib/client-file-uploader.js
  • npm run build

Copilot AI review requested due to automatic review settings June 23, 2026 00:13
@github-actions

github-actions Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves the error surfaced when fetchWithRetry exhausts retries by preserving low-level Undici fetch failure details (via the underlying cause) while still avoiding request/presigned URL details. It also adds regression coverage to ensure the new error message and cause chain are maintained.

Changes:

  • Wrap the final exhausted-retries error to append an underlying cause message (when present) and preserve the original error as cause.
  • Add a Jest regression test covering TypeError: fetch failed with an underlying write ECONNRESET cause.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
src/lib/client-file-uploader.ts Adds withCauseMessage() and uses it when retries are exhausted to preserve actionable fetch cause details.
tests/lib/client-file-uploader.js Adds regression test ensuring the final error message includes the underlying cause and preserves the original error via cause.

@sjinks sjinks force-pushed the fix/upload-fetch-failure-cause branch from cddbba6 to f25e1ef Compare June 23, 2026 00:41
@sjinks sjinks changed the title fix(upload): include fetch failure cause fix(upload): handle fetch transport failures Jun 23, 2026
@sjinks sjinks self-assigned this Jun 23, 2026
@sjinks sjinks force-pushed the fix/upload-fetch-failure-cause branch from f25e1ef to af1e5a5 Compare June 23, 2026 01:04
@sjinks sjinks changed the title fix(upload): handle fetch transport failures fix(upload): harden fetch upload streams Jun 23, 2026
@sjinks sjinks force-pushed the fix/upload-fetch-failure-cause branch from af1e5a5 to 424e862 Compare June 23, 2026 01:12

@rinatkhaziev rinatkhaziev left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:intensifies-more:

@sjinks sjinks merged commit bcec2a5 into trunk Jun 23, 2026
19 checks passed
@sjinks sjinks deleted the fix/upload-fetch-failure-cause branch June 23, 2026 01:20
@sonarqubecloud

Copy link
Copy Markdown

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.

3 participants