fix(app-shell): gate /meta/* on a resolved session and identify HTTP failures in the log (#4042) - #4078
Merged
Merged
Conversation
…failures in the log (#4042) Opening a logged-out console painted ~30 red `HTTP request failed` lines before the login form was drawn. Two independent causes. 1. Requests fired before the session was known. ConnectedShellInner now withholds the metadata tree until GET /auth/get-session resolves, and the console's `/` route — which mounted ConnectedShell with no AuthGuard above it — is now guarded, so an anonymous visitor reaches /login without a single doomed request. Same fix in console-starter. 2. Two requests per type per mount, signed in as well. Consumers read metadata during the FIRST render, before any effect runs; MetadataProvider's preview-mode effect then cleared the cache on mount, discarding those entries mid-flight so the next render refetched them. That effect now skips its mount run. A second duplicate appeared only after a failure, where callers arriving just after the rejection each started a fresh attempt — a failed type now stays un-retried for ~1s, which collapses one mount's burst without touching refresh()/invalidate(). 3. `HTTP request failed` now names the request. The client passes method/url/status as a third argument, which every console-flattener renders as `[object Object]`; those fields now go into the message string too, alongside the structured bag. Nothing is newly silenced: the only demotion remains 404-on-an-optional- collection, and a 401 surviving the gate stays a visible, identified error. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Qqyix2QcnpUC9XeYVDzx3
…in-meta-401-noise
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
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.
Fixes #4042
Opening a logged-out console painted ~30 red
HTTP request failedlines before the login form was drawn. The card's triage split this into two independently deliverable halves; both are here.Premise check
Verified against
origin/mainbefore implementing. Both halves still hold, and the "extra trigger" the card asked me to fix or explain turned out to be two distinct mechanisms, one of which is not an unauthenticated artefact at all.A recording harness on the real provider stack reproduced the card's per-round table exactly — one mount, one consumer reading
objects:Half 1 — no
/meta/*before a session existsConnectedShellInnernow withholds the metadata tree untilGET /auth/get-sessionresolves.useAuth()outside anAuthProviderreportsisLoading: false, so a provider-less embed is untouched; every protected route already sat behind anAuthGuardthat resolves auth first, so the signed-in flow is unchanged.The actual entry point for the anonymous burst was the console's landing route:
< Route path="/" >mountedConnectedShellwith noAuthGuardabove it, so simply opening/_console/mounted the whole data layer as an anonymous visitor. It is now guarded, which also means an unauthenticated visitor reaches/loginwithout a single doomed request.examples/console-starterhad the identical shape and got the identical fix.Half 1b — the duplicate trigger, in two parts
Part one, and it was never an auth problem. Consumers read metadata during the FIRST render —
useActionModalreadsobjects, whose getter kicksensureType('object')andensureType('view')from the render phase — which is before any effect runs.MetadataProvider's preview-mode effect then cleared the whole cache on mount, discarding those two entries while their requests were in flight; the next render found themidleand refetched both. The effect now skips its mount run: on mount the cache is empty and there was never anything to drop, so the clear only ever meant something on a laterpreviewDraftschange. This doubledmeta/objectandmeta/viewon every mount, signed in included — the reporter saw it as 401 noise, but it was costing two wasted round trips per mount in normal authenticated use.Part two, failures only.
entry.promisecollapses callers that arrive while a request is in flight, but callers arriving just after a failure foundstatus: 'error'withpromise: nulland each started a fresh attempt. That is a real sequence rather than a hypothetical: the mount effect walksEAGER_TYPESserially, so by the time it reachedviewthe render-phase read ofviewhad already failed, and it re-requested it. A failed type now stays un-retried for ~1s, which collapses one mount's burst of callers into a single attempt. Deliberately not the 5-minutettlMs— a later caller still retries on its own, andrefresh()/invalidate()retry immediately and unconditionally, which is pinned by its own test.Half 2 — a failure that says which request failed
@objectstack/clientreports every non-2xx aslogger.error("HTTP request failed", undefined, { method, url, status, error }). The console's logger forwarded that verbatim, so the identifying fields lived only in the third argument — and anything that flattens a console record to text (a headless/CDP capture, a log shipper, a copied DevTools line) renders them[object Object]/Object. A screenful of failures could not tell you a single URL or status; the reporter had to diff the network panel by hand to establish that all 30 lines were one benign pre-login burst.The identifying fields now go into the message string itself:
The structured bag is still passed alongside for DevTools to expand — text for the flatteners, object for the inspectors, neither at the other's expense.
formatHttpFailureMessageandcreateQuietHttpLoggerare exported so the contract is testable and an app wiring its ownObjectStackClientgets the same identified failures.Nothing is newly silenced, which the card called out explicitly. The only demotion remains 404-on-an-optional-collection (
sys_presence,sys_activity) — an expected outcome of a request we still mean to make. A 401 that survives the session gate, e.g. a mid-session expiry, stays a visible, fully-identified error, pinned by a dedicated test. The cure for doomed requests is not issuing them, never hiding them once issued.Reverse verification
Direction predicted before running: revert the source, keep the new tests, expect red. Done with a patch-file revert (never
git stash— shared stack).14 of 15 assertions went red, each in the predicted direction:
The one that stayed green is
renders through once auth resolves with NO session— that test asserts the non-regression direction (a consumer mountingConnectedShelloutside anAuthGuardmust still render once the answer is known), so it is correctly green both before and after. Reporting it rather than reshaping it to fit the red/green template.Tests
New:
MetadataProvider.requestBudget.test.tsx(3),ConnectedShell.sessionGate.test.tsx(3),httpFailureLogging.test.ts(9). Both directions are pinned throughout — no/meta/*while auth is pending, and the same three requests once each after it resolves.origin/mainmerged in before opening, per the region-exclusivity note against #4047.Generated by Claude Code