Fix session index reconciliation and stuck loading state#72
Conversation
bbsngg
left a comment
There was a problem hiding this comment.
PR Review Summary
Overall this is a solid improvement that addresses real UX issues (stuck loading) and data consistency problems (placeholder vs source-of-truth separation). The architecture direction is sound. A few items worth addressing:
Issues
1. Dead code in reconcileClaudeSessionIndex (server/projects.js)
When targetSessionId is null, the function calls getSessions(projectName, 0, 0) then immediately hits if (!targetSessionId) return result; — the code block below it (lines ~708-711 with the .session field) is unreachable.
2. Duplicated timestamp normalization logic
toIsoString()inprojects.jsduplicatesnormalizeSessionTimestamp()indb.jsnormalizeSessionTimestampandnormalizeSessionCreatedAtindb.jsare nearly identical (only difference is thereplace('T', ' ').slice(0, 19)formatting)
Consider consolidating to avoid behavioral drift.
3. Hardcoded magic values
- The 5000ms timeout in
useChatSessionState.tsfor clearing stale loading state should be extracted to a named constant with a comment explaining the chosen duration. If WebSocket reconnection takes longer than 5s, this could prematurely clear a legitimately active session's loading state. - The string
'Resuming...'appears in bothChatInterface.tsxanduseChatSessionState.ts— should be a shared constant.
4. Sequential awaits in Gemini/Codex reconcile loops (server/projects.js)
The for...of + await pattern in getGeminiSessions and getCodexSessions sync paths will serialize DB writes. Consider Promise.all/Promise.allSettled for parallelism when session count is non-trivial.
Minor / Non-blocking
- Sidebar sort order change (
lastActivity || createdAtinstead ofcreatedAt || lastActivity) for codex/gemini is a behavior change — makes sense (sort by recent activity), just confirming it's intentional. setTimeout(async () => ...)inscheduleClaudeSessionIndexReconcile— the try/catch covers it, but unhandled rejections inside setTimeout callbacks can be subtle; current code is fine.- Error-path reconcile in
claude-sdk.jscatch block usesPromise.allSettledwhich silently swallows reconcile failures — acceptable since it's best-effort cleanup.
Verdict
Good to merge after addressing items 1-3. Item 4 is a nice-to-have optimization.
…ract constants, parallelize reconcile - Remove unreachable code block in reconcileClaudeSessionIndex - Remove duplicate toIsoString() from projects.js, reuse normalizeSessionTimestamp from db.js - Extract RESUMING_STATUS_TEXT constant and STATUS_VALIDATION_TIMEOUT_MS constant - Parallelize Gemini/Codex syncIndex loops with Promise.allSettled - Add comment explaining normalizeSessionCreatedAt format difference Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Pushed
All checks pass: |
|
Thanks for the fix. I found two issues worth addressing before merge: High
Medium
I did not see other obvious correctness issues, and local typecheck passes. The main missing coverage is regression testing for these two paths: preventing repeated |
…letion - Break status-check loop by using a ref for pendingStatusValidationSessionId in the loadMessages effect, removing it from the dependency array so that resolving the pending status no longer re-triggers the effect - Move codex-complete emission before reconcileCodexSessionIndex so the UI settles immediately; reconciliation runs as post-completion housekeeping - Move gemini-complete emission before reconcileGeminiSessionIndex, same pattern Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Pushed High: check-session-status retrigger loopRoot cause: The main Fix: Introduced a ref ( Medium: Codex/Gemini completion blocked by reconciliationRoot cause: Both Fix: Reordered to emit Testing notes
|
Summary
claude-completeResuming...timer state as an active run, while keeping a short resume/status-check grace windowTesting
node --check server/projects.jsnode --check server/claude-sdk.jsnpm run typecheck.jsonldata in the local DBsession_metadata