refactor(url): parse URLs as values instead of catching TypeErrors - #727
Merged
Makisuo merged 2 commits intoSep 1, 2026
Merged
Conversation
…pated `IngestAttributeMappingForbiddenError` was added with the admin gate but never regenerated into `ANTICIPATED_ERROR_IDENTIFIER_LIST`, so the drift test that compares the checked-in list against reflection has been failing on main since. Regenerated output only. Without it the error counts as a 5xx-style failure in the SDK's span status, so an ordinary 403 would show up as an error event.
`new URL(...)` throws a `TypeError` the type system cannot see, and every
`catch` answering it flattens "not a URL" together with whatever else the block
could fail on. Eight sites were spelling that out by hand, each with its own
copy of the recovery.
`@maple/domain/url` holds the three shapes they wanted — `parseUrl` (Option),
`parseUrlWithBase` for the relative form, and `urlPathname`, whose empty string
is what the request predicates were expressing as `catch { return false }`. The
existing local copies in `pull-request-ref` and `github-hosts` now use it too.
Two behaviour changes fall out of the sweep:
- `safeFetch` resolved a redirect's `Location` with a bare
`new URL(location, validated)`. That header is whatever the far end sent, so a
malformed one threw a raw `TypeError` out of the SSRF guard — neither a
rejection callers can handle nor a response. It is a `UrlValidationError` now,
with a regression test.
- `validateExternalUrlSync` threw `UrlValidationError`, a `Schema.TaggedError`,
which the Effect wrapper then had to re-recognise on the way back out. The
check is `validateExternalUrlResult` returning a `Result`, `validateExternalUrl`
is `Effect.fromResult` over it, and its two callers read the failure instead of
catching it. All 69 existing guard cases are kept, as Result assertions.
`safeFetch` itself stays a Promise: moving it onto `HttpClient` is a rewrite of
the redirect and credential-stripping loop, not a parsing change.
An error occurred while trying to automatically change base from
fix/05-github-enterprise-host
to
fix/04-self-hosted-ingestion-ui
September 1, 2026 15:33
🍁 Maple PR previewWarning Preview cleanup could not be confirmed. The Alchemy teardown outcome was Final commit |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Sixth of the stack, based on #725.
new URL(...)throws aTypeErrorthe type system cannot see, and everycatchanswering it flattens "not a URL" together with whatever else the block could fail on. Eight sites were spelling that out by hand, each with its own copy of the recovery.@maple/domain/urlholds the three shapes they wanted —parseUrl(Option),parseUrlWithBasefor the relative form, andurlPathname, whose empty string is exactly what the request predicates inworker.tswere expressing ascatch { return false }. The existing local copies inpull-request-refandgithub-hostsuse it too, so there is one implementation rather than four.Converted:
apps/apiworker route predicates (×3),WorkersAiHttpClient,GithubProvideravatar derivation,PlanetScaleDiscoveryService,query-engine'shostOf, andurl-validator.Two behaviour changes fall out of the sweep
safeFetchcould throw a rawTypeErrorout of the SSRF guard. It resolved a redirect'sLocationwith a barenew URL(location, validated). That header is whatever the far end sent, so a malformed one escaped as neither a rejection callers can handle nor a response. It is aUrlValidationErrornow, with a regression test verified to fail against the old code.validateExternalUrlSyncthrewUrlValidationError— aSchema.TaggedError, thrown, whichvalidateExternalUrlthen had to re-recognise withinstanceofon the way back out. The check is nowvalidateExternalUrlResultreturning aResult,validateExternalUrlisEffect.fromResultover it, and both callers read the failure rather than catching it. All 69 existing guard cases are preserved asResultassertions — none dropped, none loosened.safeFetchitself stays a Promise. Moving it ontoHttpClientmeans rewriting the redirect and credential-stripping loop that #717 just landed, which is not a parsing change and wants its own PR.Unrelated fix in the first commit
IngestAttributeMappingForbiddenErrorshipped with the admin gate (#713) but was never regenerated intoANTICIPATED_ERROR_IDENTIFIER_LIST, so the drift test comparing that list against reflection has been failing onmainsince it merged. The first commit is the regenerated output. Beyond the red suite, an ordinary 403 was being recorded as an error event rather than an anticipated 4xx.Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.