fix(web): extend bestEffort to conversation history and settings daemon calls#33163
fix(web): extend bestEffort to conversation history and settings daemon calls#33163devin-ai-integration[bot] wants to merge 1 commit into
Conversation
…on calls
Closes LUM-2199
- Add bestEffort: true to captureError in use-conversation-history.ts
(WEB-7: 77 events from 503 'still starting up' during pagination)
- Add bestEffort: true to captureError in web-search-card.tsx
(WEB-2H: raw {detail} errors from HeyAPI throwOnError)
- Extend isExpectedDaemonTransientError() to detect raw {detail: string}
objects thrown by HeyAPI's throwOnError — not just ApiError instances.
HeyAPI throws the parsed JSON response body verbatim, so the function
now matches known daemon transient detail substrings (still starting up,
organization-id header, authentication credentials, bad gateway).
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 473c2d0777
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if (cancelled) return; | ||
| setWebSearchHasStoredKey(false); | ||
| captureError(error, { context: "settings-ai-web-search-read-secret" }); | ||
| captureError(error, { context: "settings-ai-web-search-read-secret", bestEffort: true }); |
There was a problem hiding this comment.
Gate or retry before suppressing secret-read errors
In platform mode this daemon call can fail with the org-header race that bestEffort now filters, but this effect is a one-shot read: it does not depend on useIsOrgReady() and has no retry/invalidation path when the org store finishes hydrating. In that scenario the catch above leaves webSearchHasStoredKey(false) and the settings card never re-reads the existing key, so suppressing the error here hides a state that can leave the UI incorrectly thinking no key is stored; please gate the request or retry before treating it as best-effort.
Useful? React with 👍 / 👎.
Prompt / plan
Closes LUM-2199
Sentry audit found two remaining daemon call sites reporting expected transient errors:
use-conversation-history.tspagination error handlerweb-search-card.tsxsettings secret readBoth needed
bestEffort: trueon theircaptureErrorcalls (introduced in PR #33160). WEB-2H additionally exposed a gap: HeyAPI'sthrowOnError: truethrows the raw JSON response body ({detail: "..."}) as a plain object — not anApiErrorinstance — soisExpectedDaemonTransientError()didn't match it.Changes
capture-error.ts— ExtendisExpectedDaemonTransientError()to handle raw{detail: string}objects. After the existingApiErrorbranch, check for plain objects with adetailstring and match against known daemon transient substrings (case-insensitive):"still starting up","organization-id header","authentication credentials","bad gateway". This covers the Django REST framework error shape that HeyAPI throws verbatim.use-conversation-history.ts— AddbestEffort: trueto the pagination errorcaptureErrorcall.web-search-card.tsx— AddbestEffort: trueto the secret-read errorcaptureErrorcall.Why this is safe
isExpectedDaemonTransientErrorgains a new branch after the existingApiErrorcheck; existingApiErrormatching is unchanged.typeof === "object", non-null,"detail" in error,typeof detail === "string", and substring match against a fixed allowlist. Objects withoutdetailor with non-matching messages pass through to Sentry.Alternatives not taken
normalizeToError()first soisExpectedDaemonTransientErroronly seesErrorinstances. Rejected because the normalizedErrorloses thestatuscode thatApiErrorcarries, making status-based checks impossible. The two-branch approach (ApiError by status, raw object by detail string) preserves both detection paths.throwOnErrorresponses and wrap them inApiError. Rejected because it would change the error shape across every call site in the app, which is a much larger change with higher regression risk.Root cause analysis
bestEffortmode was introduced in PR fix(web): add bestEffort mode to captureError for daemon transient errors #33160 but only applied touseActiveConversationanduseConversationSync. These two call sites were missed because they were in different domains (chat pagination, settings).captureErrorcall sites whenbestEffortwas introduced.captureErroroption, grep for all existingcaptureErrorcalls and evaluate each one.docs/CONVENTIONS.mdalready documentsbestEffortusage. The pattern is clear; this was a coverage gap, not a knowledge gap.Test plan
bun test src/lib/sentry/capture-error.test.ts) — including 10 new tests for raw{detail}object detection andbestEffortintegration with raw HeyAPI errors.bunx tsc --noEmitclean (pre-existing errors inlocal-mode.tsare on main).bun run lintclean (no new warnings in changed files).Link to Devin session: https://app.devin.ai/sessions/c2e17ff1867f4ebd90aac007ea0f5453
Requested by: @ashleeradka