fix(frontend): transient 429 handling and guided no-snapshot states - #226
Merged
parthrohit22 merged 4 commits intoJul 27, 2026
Merged
Conversation
The API client blindly retried a 429 with fixed exponential backoff, ignoring Retry-After entirely -- itself a retry-storm risk. 429 is now excluded from that transport-level retry; ApiError parses Retry-After (delta-seconds or HTTP-date, defensive against missing/malformed values) and exposes it as retryAfterSeconds. The analysis poller treats a 429 as transient (mirroring the existing network-error recovery pattern) instead of the terminal "Analysis Failed" state: it shows an accessible countdown, auto-retries once the window elapses, and bounds automatic retries. The manual "Try again"/"Restart analysis" actions are disabled for the same cooldown to prevent retry-storming from repeated manual clicks.
…tation A repository can be "completed" (analysed) yet still have no sealed ri.v1 snapshot for its current revision, the same 404 Dependencies/Architecture already distinguish. Engineering Review, Insights, and Documentation previously collapsed that into a generic error message with a "Retry" button that could never succeed. They now show the same "No sealed snapshot yet -- Run analysis" guided empty state Dependencies already established, reusing the existing EmptyState component. None of the named product surfaces (Architecture, Dependency Graph, AI Workspace, Engineering Review, Documentation, Insights) are actually dead or unreachable in the current nav registry -- this narrows the issue to the concrete, provable gap found on inspection.
…ncelled The standalone rate-limited banner and the cancelled banner's disabled, countdown-labelled Restart button communicated the same cooldown independently, so a 429 hit while restarting a cancelled job showed both at once. Suppress the standalone banner while cancelled.
parthrohit22
marked this pull request as ready for review
July 27, 2026 18:48
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.
Summary
Two related honest/recoverable-state fixes, bundled because both extend the
exact same pattern and touch the same frontend error/state-handling layer:
"Analysis Failed," and the API client itself blindly retried 429s with fixed
exponential backoff while completely ignoring
Retry-After— a retry-stormrisk baked into the transport layer.
"repository analysed but no sealed snapshot yet" 404 into a generic error
message with a "Retry" button that could never succeed, instead of the
guided "No sealed snapshot yet — Run analysis" state Dependencies already
established.
Linked issue
Closes #169
Closes #178
Roadmap alignment
product surface must present an honest, evidence-grounded state; a
transient condition must never be indistinguishable from a real failure,
and a recoverable state must never be indistinguishable from a dead end.
use — a user who hits a transient rate limit or an unsealed snapshot must
be able to recover and keep using the product, not bounce off a
misleading terminal error.
bugs (429 read as "Analysis Failed"; Review/Insights/Documentation showing
a generic unguided error) and pass only after the fix; verified locally.
What changed
#169 (backend contract already existed — this is a frontend fix)
shared/services/api/errors.ts:ApiErrornow carries the rawRetry-Afterheader; newparseRetryAfter()handles delta-seconds,HTTP-date, and defends against missing/malformed values. A new
ApiError.retryAfterSecondsgetter prefers the header, falling back to thebackend's
details.retryAfterSecondsbody field.shared/services/api/client.ts: 429 is excluded from the blindtransport-level retry (previously retried with fixed 1s/2s backoff
regardless of
Retry-After— itself the retry-storm risk). All threeApiErrorconstruction sites now capture the header.features/analysis/hooks/useAnalysisPipeline.ts: a new'rate-limited'connectionStatus, mirroring the existing network-error recovery pattern.A 429 during polling starts a
Retry-After-driven countdown andauto-retries once it elapses, bounded to
MAX_RATE_LIMIT_AUTO_RETRIES = 3attempts and
RATE_LIMIT_MAX_WAIT_SECONDS = 120per wait. The manualrestart()action also respects the cooldown instead of accepting anotherclick mid-wait.
app/pages/AnalysisPipelinePage.tsx: an accessible (role="status")countdown banner replaces the terminal "Analysis Failed" state for a 429;
the "Restart analysis" button is disabled and labelled with the remaining
wait during a cooldown.
#178
features/review/hooks/useReview.ts,features/insights/hooks/useInsights.ts,features/documentation/hooks/useDocumentation.ts: each gained anoSnapshotflag, set exactly like the existinguseDependencies.tspattern (
isApiError(caught) && caught.isNotFound).app/pages/EngineeringReviewPage.tsx,InsightsPage.tsx,DocumentationPage.tsx: each renders the sameEmptyState— "No sealedsnapshot yet" with a "Run analysis" action — that
DependenciesPage.tsxalready established, instead of a generic destructive-styled error.
Scope note on #178's premise: I checked
productSurfaces.tsxand none ofthe six named surfaces (Architecture, Dependency Graph, AI Workspace,
Engineering Review, Documentation, Insights) are actually dead or
unreachable — all are
ready/previewin the registry. The real,reproducible gap was narrower (see above), and I scoped the fix to that
rather than reworking navigation. Architecture gets the equivalent fix under
#217 (separate PR, since it's a backend contract change).
Acceptance criteria completed
#169
Retry-After.removed; manual restart disabled during cooldown).
MAX_RATE_LIMIT_AUTO_RETRIES = 3,RATE_LIMIT_MAX_WAIT_SECONDS = 120).role="status").Retry-Aftervalues (
errors.test.ts).(
useAnalysisPipeline.test.ts).#178
already correct or fixed under chore: remove pre-snapshot Architecture fallback and fabricated line/complexity metrics (CQ-H1) #217; Review/Insights/Documentation fixed
here.
(run analysis).
Testing performed
Backend was not touched by this PR; the backend gate (
pytest) was notre-run here since no backend file changed (verified via
git diff --stat).Screenshots
Not applicable — the rate-limited countdown and no-snapshot states reuse
existing visual patterns already shipped (the warning-styled connection banner
for #169, the
EmptyStatecomponent already visible on the Dependencies pagefor #178). No new visual design.
Security and data considerations
None. No auth/owner-scoping change. The 404 detection reuses the existing
ApiError.isNotFoundcheck already used byuseDependencies.ts. No new datais stored, logged, or sent to the backend. The
Retry-Afterheader is parseddefensively (regex-guarded,
Date.parseresult validated) so a malformed oradversarial header value cannot produce
NaN/negative delays or an infinitewait — it falls back to a fixed default instead.
Dependencies and blocked work
None. Independent of PR #225 (#217) — both extend the same established
noSnapshot/honest-state pattern but touch different files (#217 isbackend-contract + Architecture-only; this PR is Review/Insights/Documentation
Scope changes or remaining work
None for #169/#178 as scoped. AI Workspace was deliberately left out of the
#178 fix: it doesn't have the same blank/dead-end problem the issue
describes — its chat interface stays interactive and only shows an error
after an explicit user action (asking a question), which is a materially
different, lower-severity situation not covered by the issue's evidence.
Contributor checklist
devupstream/devupstream/devCloses) is used only because both issues are fully resolved