Skip to content

🚑 report failing url instead of crashing on download errors#8

Merged
cowboyd merged 3 commits into
mainfrom
improve-download-error-reporting
Jul 8, 2026
Merged

🚑 report failing url instead of crashing on download errors#8
cowboyd merged 3 commits into
mainfrom
improve-download-error-reporting

Conversation

@taras

@taras taras commented Jul 6, 2026

Copy link
Copy Markdown
Member

Motivation

The frontside.com deploy workflow fails at the Staticalize step with:

error: Uncaught (in promise) TypeError: Invalid gzip header
    let content = yield* until(response.text());
    at ...
    at file:///tmp/deno-compile-staticalize-linux/staticalize.ts:127:51

The message gives no indication of which URL was being downloaded, so it is impossible to tell which page on the site is misbehaving.

The download operation only routed non-2xx responses into the graceful, collected error path. Any thrown exception — a gzip/transport decode error, an HTML parse error, or a filesystem write error — escaped uncaught, killed the whole run, and printed a raw stack trace with no source/referrer context, even though both are in scope at the throw site.

Approach

  • downloader.ts: wrap the download body in try/catch. On a thrown error (re-throwing if the run was aborted), return { ok: false, url, referrer, error } so it flows into the existing collected-error mechanism instead of crashing. The run keeps going, every page/asset that succeeds is still written, and it exits non-zero at the end. The non-2xx branch also now carries an error message for consistency.
  • progress.ts / main.ts: thread the optional error through DownloadError and print it in the failed-downloads summary.
  • staticalize.ts: attach the sitemap URL to read/parse failures of /sitemap.xml.
  • test/staticalize.test.ts: new case serving a page that advertises Content-Encoding: gzip with a non-gzip body — asserts the run does not crash, healthy pages are still written, and the bad page is skipped. Verified this test reproduces the exact Invalid gzip header crash without the fix.

Example output now:

1 failed downloads:

  http://127.0.0.1:8005/bad
    referrer: http://127.0.0.1:8005/
    error: TypeError: Invalid gzip header

Bumps deno.json version 0.2.20.2.6 (stale field; latest release is v0.2.5) for the follow-up v0.2.6 release that frontside.com's deploy workflow will consume.

Comment thread downloader.ts Outdated
Comment thread downloader.ts Outdated
Comment thread progress.ts Outdated
Comment thread main.ts
Comment thread downloader.ts Outdated
@taras

taras commented Jul 6, 2026

Copy link
Copy Markdown
Member Author

Pushed 721287a addressing all review threads (Error + cause model, hierarchy print, dropped the abort guard).

Ran the recompiled binary against the live site (--site=http://localhost:8005) — it did exactly its job and pinpointed the deploy failure: 259 pages OK, 349 failures, every one TypeError: Invalid gzip header, all confined to the proxied doc sub-sites /effection/*, /interactors/*, /graphgen/*. So the fix on this side is working; the remaining gzip issue is on frontside.com's proxying of those sub-paths.

@cowboyd

cowboyd commented Jul 7, 2026

Copy link
Copy Markdown
Member

Sounds good, although should this be an option? Basically whether we fail fast or not?

taras added 2 commits July 7, 2026 15:53
A thrown exception during a download (e.g. a gzip/transport decode error,
an HTML parse error, or a filesystem write error) escaped uncaught and
crashed the whole run with a raw stack trace that gave no indication of
which url was being fetched.

Route thrown exceptions into the existing collected-error path so the run
keeps going, writes every page/asset that succeeded, reports each failing
url with its referrer and the underlying reason, and exits non-zero at the
end. Also attach the failing url to sitemap read/parse errors.
Address review: carry a real Error (not a string) on failed downloads,
wrap the underlying error as `cause`, and print the full cause hierarchy
in the summary. Drop the unreachable signal.aborted re-throw guard —
effection unwinds a halt via generator return, so cancellation never
reaches this catch.
@taras taras force-pushed the improve-download-error-reporting branch from 721287a to 73b8d04 Compare July 7, 2026 20:07
@taras

taras commented Jul 7, 2026

Copy link
Copy Markdown
Member Author

Good call — added in 73b8d04. There's now a --strict flag (threaded through useStaticalizer, so it's a library option too). Default stays keep-going (download everything, report all failures at the end, exit non-zero); --strict aborts on the first failure and prints that error's cause hierarchy.

Also rebased the branch onto main to pick up the new Verify CI workflow.

Comment thread README.md
Comment thread main.ts Outdated
Comment thread downloader.ts Outdated
Comment thread downloader.ts Outdated
Comment thread downloader.ts Outdated
Comment thread downloader.ts Outdated
Comment thread downloader.ts Outdated
Comment thread downloader.ts Outdated
Comment thread downloader.ts Outdated
By default a failed download is collected and reported at the end while
the run continues. Add a --strict flag, threaded through useStaticalizer,
that re-throws on the first download failure so the run aborts.
@taras taras force-pushed the improve-download-error-reporting branch from 73b8d04 to d82bf18 Compare July 7, 2026 20:43
@taras

taras commented Jul 7, 2026

Copy link
Copy Markdown
Member Author

Addressed the review in d82bf18 — I'd over-engineered the fail-fast path. Rundown:

  • firstError capture / the skip / the iterator throw — all removed. --strict now just re-throws from the download (fail() throws when strict) and lets structured concurrency abort the run. No bookkeeping.
  • Comments — dropped the code-narrating ones; the catch comment is trimmed to just the surprise (these underlying errors don't name the url).
  • strict? on the types — removed the JSDoc; the description lives on the parser (config.ts).
  • README vs --help — regenerated the block to match the actual output verbatim.
  • main.ts comment / try-catch — gone; main.ts is back to its prior shape plus threading strict through.

One thing worth your call on the UX. Throwing from inside the download task can't be caught downstream to pretty-print it: the task buffer's scope.spawn makes each download a child of the resource scope, so a throw aborts that scope out of band (bypassing both the monitor's catch and the drain) — it lands on effection's default uncaught-error handler. So:

  • library (useStaticalizer): strict surfaces as a normal rejected operation — clean, caller formats it.
  • CLI: strict is a loud crash with effection's stack dump + Caused by:, rather than the collected-mode hierarchy. Exits 1 either way.

Making the CLI print the nice hierarchy in strict mode would mean reintroducing a result-based mechanism (roughly the thing you just told me to delete), so I left it as the clean crash. Happy to add the formatted variant if you'd rather have consistent output — your call.

@taras taras requested a review from cowboyd July 7, 2026 20:58
@cowboyd cowboyd merged commit 9d04dd5 into main Jul 8, 2026
1 check passed
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.

2 participants