Repository navigation
fix(conversations): catch loadThreads rejection on mount to silence Sentry noise - #1513
Conversation
…entry noise The mount-time `dispatch(loadThreads()).unwrap().then(...)` chain had no `.catch()`, so when the core RPC `openhuman.threads_list` timed out, `.unwrap()` re-threw Redux's `SerializedError` (a plain object, not an `Error`). That surfaced as Sentry's "Non-Error promise rejection captured with value: Core RPC openhuman.threads_list timed out after 30000ms" (OPENHUMAN-REACT-X). The slice's `loadThreads.rejected` case already handles UI state — just swallow the rejection at the promise boundary so it isn't unhandled.
📝 WalkthroughWalkthroughThe ChangesMount-time thread loading error handling
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
app/src/pages/Conversations.tsx (1)
257-296: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick winRefactor this effect to use async/await and namespaced debug logging
Lines 257–296 use promise chaining instead of async/await, and console.warn instead of namespaced debug logging, both violating the coding guidelines. Convert to an async IIFE with try/catch and replace the console.warn with a namespaced debug logger for frontend logging compliance.
Proposed refactor
useEffect(() => { let cancelled = false; - void dispatch(loadThreads()) - .unwrap() - .then(data => { + void (async () => { + try { + const data = await dispatch(loadThreads()).unwrap(); // [`#1123`] Commented out — welcome-agent onboarding replaced by Joyride walkthrough // if (cancelled || skipInitialThreadSelectionRef.current) return; if (cancelled) return; @@ if (data.threads.length > 0) { @@ } else { void handleCreateNewThread(); } - }) - .catch(err => { + } catch (err) { if (cancelled) return; const message = err instanceof Error ? err.message : String(err?.message ?? err); - console.warn('[conversations] loadThreads failed on mount:', message); + debug('[conversations] loadThreads failed on mount:', message); - }); + } + })();🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/pages/Conversations.tsx` around lines 257 - 296, Replace the promise chain starting at dispatch(loadThreads()).unwrap() with an async IIFE: immediately await dispatch(loadThreads()).unwrap() inside a try block, keep the cancelled guard checks before and after the await, and preserve the thread-selection logic that uses store.getState().thread, setSelectedThread and loadThreadMessages; in the catch block use the project's namespaced debug/logger (e.g., logger.warn or debug('conversations')) instead of console.warn and log the error message (err instanceof Error ? err.message : String(err)); ensure handleCreateNewThread() is awaited/called the same way as before when no threads are returned.
🧹 Nitpick comments (1)
app/src/pages/Conversations.tsx (1)
292-295: ⚡ Quick winReplace direct
console.warnwith namespaced debug loggingLine 295 introduces direct console logging; this conflicts with the frontend logging guideline. Please use the project’s namespaced debug pattern and keep detail dev-scoped.
As per coding guidelines, “Use namespaced
debugfor frontend logging with dev-only detail; avoid logging secrets or full PII.”🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/pages/Conversations.tsx` around lines 292 - 295, The catch block inside loadThreads in Conversations.tsx currently uses console.warn; replace it with the project's namespaced debug logger (e.g., debug('conversations') or a module-level debugConversations instance) and call that instead, keeping the same message string but ensuring no sensitive/PII data is logged; add or reuse the appropriate import/initializer for the namespaced debug logger at the top of the file and swap the console.warn call in the catch (which references cancelled and message) to debugConversations(...) so logs remain dev-scoped.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@app/src/pages/Conversations.tsx`:
- Around line 257-296: Replace the promise chain starting at
dispatch(loadThreads()).unwrap() with an async IIFE: immediately await
dispatch(loadThreads()).unwrap() inside a try block, keep the cancelled guard
checks before and after the await, and preserve the thread-selection logic that
uses store.getState().thread, setSelectedThread and loadThreadMessages; in the
catch block use the project's namespaced debug/logger (e.g., logger.warn or
debug('conversations')) instead of console.warn and log the error message (err
instanceof Error ? err.message : String(err)); ensure handleCreateNewThread() is
awaited/called the same way as before when no threads are returned.
---
Nitpick comments:
In `@app/src/pages/Conversations.tsx`:
- Around line 292-295: The catch block inside loadThreads in Conversations.tsx
currently uses console.warn; replace it with the project's namespaced debug
logger (e.g., debug('conversations') or a module-level debugConversations
instance) and call that instead, keeping the same message string but ensuring no
sensitive/PII data is logged; add or reuse the appropriate import/initializer
for the namespaced debug logger at the top of the file and swap the console.warn
call in the catch (which references cancelled and message) to
debugConversations(...) so logs remain dev-scoped.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: dc6b889d-5a84-4c90-8c34-3dfb41d55390
📒 Files selected for processing (1)
app/src/pages/Conversations.tsx
…anches The mount-time .catch() block added in tinyhumansai#1513 normalises Redux's SerializedError plain object into a string for console.warn. Pull that logic into an exported pure helper so it can be unit-tested without mounting the full page, then exercise every branch (Error, SerializedError-shaped object, plain object, string, null/undefined, non-string message field). Coverage Gate on changed lines goes from 0% (the original 4-line inline catch was untested) to ≥80%. No behavior change beyond the refactor.
Summary
.catch()to the mount-timedispatch(loadThreads()).unwrap().then(...)chain inConversations.tsxso a failed initial thread fetch no longer surfaces as an unhandled promise rejection.Problem
Sentry issue OPENHUMAN-REACT-X (
UnhandledRejection: Non-Error promise rejection captured with value: Core RPC openhuman.threads_list timed out after 30000ms).When the core RPC
openhuman.threads_listtimed out on app mount, the chain atapp/src/pages/Conversations.tsx:257had no.catch().createAsyncThunk's.unwrap()re-throws Redux'sSerializedError(a plain object, not anErrorinstance), so the rejection became unhandled and Sentry tagged it as a "Non-Error promise rejection."Solution
.catch()after the existing.then()that bails when the effect has been cancelled and otherwise logs the message viaconsole.warn.loadThreads.rejectedcase already drives UI state — this just suppresses the unhandled rejection at the promise boundary.Submission Checklist
rejectedreducerImpact
threads_listRPC timeout on app mount no longer produces an unhandled rejection or a Sentry event for the user; the failure is logged to the console and the UI continues with whatever empty/cached state the slice reducer left behind.Related
AI Authored PR Metadata (required for Codex/Linear PRs)
Linear Issue
Commit & Branch
Validation Run
.catch()block added, Prettier-conformant)pnpm typecheck(run asnpx tsc --noEmit— clean)Validation Blocked
Behavior Changes
loadThreadson mount is caught and logged instead of surfacing as an unhandled promise rejection.openhuman.threads_listRPC fails or times out.Parity Contract
.then()chain is untouched; the rejected-action UI state from the slice still flows through reducers..catch()short-circuits when the effect'scancelledflag is set, matching the existing pattern in the.then()callback.Duplicate / Superseded PR Handling
Summary by CodeRabbit