store: bare catch in fetchCatalog relabels every error as "Catalog endpoint is unavailable", masking the static-gen bailout #66
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found while fixing #64 (PR #65). Filed rather than folded into that PR, which is scoped to catalog cache freshness.
The defect
apps/store/lib/catalog.tswraps the catalog fetch in a barecatchthat discards the caught value:The intent is good and should be preserved: the original error may carry the API key or internal network detail, and #47-era work deliberately kept credentials out of build logs. But the
catchis unconditional, so every throw from thatawait— not just a network failure — is relabelled as an endpoint outage.Why this is worth fixing, concretely
Next's static-generation bailout is thrown from inside that
await. While measuring #64 I setcache: "no-store"and the build failed with:The actual error, recovered only by temporarily logging the caught value, was:
Those two messages send you to completely different places. The real one names the caching constraint and links the fix; the one actually printed says the management API is down, which sends you to check the container, the API key, and the network — none of which are involved.
This is not hypothetical. That constraint (
output: "export"forbids an uncached fetch) is precisely what dictates the caching design in #64, so the next person to touch caching here is the most likely person to hit it, and they will be handed the least useful possible message.Suggested fix
Keep redacting genuine network failures; stop swallowing framework control-flow errors. A
NEXT_STATIC_GEN_BAILOUT(and Next'sDynamicServerError) is generated by the framework, contains no credentials, and must reach the build log intact. Rethrowing on thatcodewhile keeping the current redaction for everything else is enough.Worth checking whether the same shape guards the other two
catchblocks in the same function (response.json()→"Catalog response is not valid JSON"); that one is narrower and probably fine, but it should be looked at in the same pass rather than left as the twin.Acceptance
A static-generation bailout raised inside
fetchCatalogsurfaces with its own message andcodein the build output, while a real network failure still fails with a message that does not containCATALOG_API_KEYor internal host detail. Existing coverage inapps/store/lib/catalog.test.tsalready asserts the redaction behaviour and must keep passing.Adding the recommended fix shape, from the review of PR #65 (review id=24), plus the strongest argument for prioritising this.
Recommended shape
Rethrow anything carrying a Next
digestor bailoutcode; keep the friendly redacted message for genuine network faults. Framework control-flow errors are generated by Next, contain no credentials, and must reach the build log intact.Why this is worth more than it looks
The reviewer of #65 hit this independently, and it cost real work in both directions:
output: "export"forbids an uncached fetch) presents as a network outage. The reviewer had to delete thecatchto recover the real error, exactly as I did.NEXT_STATIC_GEN_BAILOUTtext as verbatim build output. It is not what the build prints at that commit — the mask rewrites it. That needed a correction round on #65.So this defect has now independently misled two agents on the same constraint, and made a PR body wrong. That is the argument for fixing it.
Sibling to check in the same pass
fetchCataloghas a secondcatcharoundresponse.json()producing"Catalog response is not valid JSON". It is narrower and probably fine, but it should be looked at in the same pass rather than left as the twin.Evidence of cost: this masking has now caused the same string to be mis-stated three times, across three different artifacts
Recorded while PR #65 was in review, because it is the strongest available argument for fixing this and it is not obvious from reading the code.
NEXT_STATIC_GEN_BAILOUTis the.codeon Next'sStaticGenBailoutError(next/dist/client/components/static-generation-bailout.js:23). It is never part of any printed message. The barecatchinapps/store/lib/catalog.tsrewrites the throw into"Catalog endpoint is unavailable", so nobody investigating ever sees the real error, and three separate attempts to describe it were each wrong in a different way:"Catalog endpoint is unavailable".dynamic = "error"/revalidate: 0message and the constant stays on.code. Caught by review.{ code: 'NEXT_STATIC_GEN_BAILOUT' }inside a produced-output block. Caught by review.Three behaviours that are genuinely distinct, which is why this kept going wrong:
main)Error: Catalog endpoint is unavailabledynamic = "error"/revalidate: 0message, aloneconsole.error(e){ code: 'NEXT_STATIC_GEN_BAILOUT' }line — because Node's inspector appends own enumerable properties to an error object, not because the build printed itThe third row is the trap: it looks like build output and is not. Two engineers investigating the same failure by slightly different methods get three different impressions, and none of them is the plain truth that the constant is a property and never a message.
Why this matters beyond tidiness: the masked error is a static-generation bailout, which is precisely the constraint that dictates the caching design in
catalog.ts. So the next person to touch caching here is the most likely person to hit it, and they will be told the API is down. That is not hypothetical — it happened twice in one wave, to an author and a reviewer working on the file the catch lives in.Recommended shape (unchanged, restated with the reason)
Rethrow anything carrying a Next digest or bailout code; keep the friendly redacted message for genuine network faults. Check the sibling
catchonresponse.json()in the same function in the same pass — it is the twin, and this codebase has a track record of fixing one instance and leaving its twin.Whoever fixes this: make it fail on purpose once and confirm the real error reaches the console, rather than assuming the rethrow works.