Fix Gemini thinking blocks, resync reliability, and startup UX - #58
Conversation
Change thinking format from [Thinking: subject] to [Thinking] with subject as the first line of content. This matches the frontend regex that renders collapsible thinking blocks, instead of showing thinking as plain inline text. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
writeMessages() assumed session files are append-only and only inserted messages with ordinals greater than the current max. During a full resync (triggered by parser changes), existing messages were never updated because ordinals hadn't grown. This meant parser fixes (like the thinking block format change) had no effect on already-synced sessions even after a full resync. Thread a forceReplace flag from ResyncAll through to writeBatch so it uses writeSessionFull (delete+reinsert) instead of the incremental append path. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
roborev: Combined Review (
|
writeSessionFull does DELETE+INSERT on both the messages table and the FTS5 index for every session. With 10K+ sessions this makes ResyncAll effectively hang. Add a cheap content comparison (message count + total content length) that skips the expensive replace when content hasn't changed. Only sessions actually affected by a parser change get the full FTS5 rewrite. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
roborev: Combined Review (
|
Replace the replaceIfChanged content-length heuristic with a cleaner approach: drop the FTS triggers and table before the resync, do all message replacements without per-row FTS overhead, then rebuild the index in one pass from the content table afterward. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The unconditional log.Printf in syncOneOpenCode printed with Go's default timestamp directly after the carriage-return progress line, producing garbled output. The same information is already logged behind the verbose flag in syncAllLocked. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
SyncPaths (called by file watcher) and SyncSingleSession (called by API handlers) were not holding syncMu, so they could run concurrently with ResyncAll. This caused DB writer contention and, with the FTS drop+rebuild approach, could insert messages while triggers were absent. Both now acquire syncMu to serialize with all other sync operations. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…esync Add Warnings field to SyncStats so ResyncAll can report FTS rebuild failures to the caller instead of silently leaving search broken. Add FTS search verification to the resync integration test. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… tools) Thoughts happen before the response text, so extract them first. Previously thinking blocks appeared after the assistant's response which was confusing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update syncMu comment to reflect it now serializes all sync operations. Re-store lastSyncStats after ResyncAll appends warnings so /api/v1/sync/status includes them. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
writeSessionFull called ReplaceSessionMessages per session, each a separate transaction. With batchSize=100, that's 100 transactions of DELETE+INSERT before progress updates, causing the resync to appear hung. Replace with a single-transaction bulk delete of all messages in the batch, then use the normal writeMessages INSERT path (which sees maxOrd=-1 and inserts all). This makes each batch: 1 bulk delete tx + N fast insert txs instead of N full replace txs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
roborev: Combined Review (
|
Write all log output to both stderr and ~/.agentsview/debug.log via io.MultiWriter so there is a persistent record for diagnosing resync issues. Add timing instrumentation to: - writeBatch (bulk delete phase, per-session write phase) - ResyncAll (FTS drop and rebuild) - InsertMessages, DeleteMessagesForSessions, ReplaceSessionMessages (logged when >100ms to avoid flooding) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ormal sync Address review findings from #7451: - If bulk DeleteMessagesForSessions fails, fall back to per-session writeSessionFull (atomic delete+insert) instead of continuing with the incremental path against stale data - Only emit "write batch" timing logs during forceReplace (resync), not during normal initial sync where they spam stdout Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
roborev: Combined Review (
|
Address review findings: - Truncate debug.log at startup if it exceeds 10MB to prevent unbounded disk growth from the append-only log file - Add tests for setupLogFile (file creation, dual output, open failure fallback) and truncateLogFile (over/under limit, missing) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the slow ResyncAll approach (bulk-delete + reinsert 400K messages with per-row trigger overhead) with a fresh-DB-and-swap strategy: 1. Open a new empty DB at a temp path 2. Point the engine at the new DB and run normal sync (pure inserts) 3. Copy insights from old DB via SQLite ATTACH 4. Close temp DB, rename over original, reopen original handle This reduces resync from 2+ minutes to seconds for large datasets by avoiding trigger overhead entirely. Changes: - Add DB.Path(), DB.Reopen() for file-swap support - Add DB.CopyInsightsFrom() using ATTACH/DETACH - Rewrite ResyncAll to use fresh-DB strategy - Remove forceReplace from writeBatch/collectAndBatch/syncAllLocked - Remove DeleteMessagesForSessions and DeleteSessionMessages - Clean up stale temp DB files on startup Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Address review #7456 findings: - Use os.Lstat in truncateLogFile to detect and skip symlinks, preventing accidental truncation of symlink targets - Replace hardcoded /nonexistent/path in TestTruncateLogFileMissing with t.TempDir()-based path for CI portability - Add TestTruncateLogFileSymlink to verify symlinks are preserved Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Address review #7459 findings: - Abort swap when sync discovers sessions but syncs zero (prevents replacing a populated DB with an empty one) - Open new DB handles before closing old ones in reopenLocked so the struct never points at closed handles on failure - Add TestResyncAllPreservesInsights to verify insights survive the fresh-DB-and-swap flow - Add TestResyncAllAbortsOnZeroSynced to verify the safety guard Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Address review #7463: os.WriteFile and os.Symlink errors were ignored, allowing the test to pass vacuously if symlink creation fails. Now fatals on write failure and skips with a message when symlinks are unsupported. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Address review #7464 findings: - Add Failed counter to SyncStats, incremented on hard parse/stat errors in collectAndBatch - Abort resync swap when Failed > Synced (not just when Synced == 0), preventing partial DB replacement when bulk failures occur - Replace non-deterministic abort test with chmod-based approach that guarantees a permission error, removing the t.Skip escape hatch Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Address review #7465: the broad t.Skip on any os.Symlink error could mask real setup bugs. Now only skips for EPERM/EACCES (environments where symlinks are unsupported) and fatals for unexpected errors. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Address review findings from #7465, #7466, #7468: - Add unexported filesOK counter (file-level) so abort guard compares Failed vs filesOK instead of Failed vs Synced (session-level), fixing a unit mismatch when fork detection produces multiple sessions per file - Skip chmod-based abort test on Windows and when running as root - Add ENOTSUP and ENOSYS to symlink test skip condition for filesystems that report unsupported symlinks with those errnos Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Address review #7471 findings: - Add TestResyncAllAbortsWithForkAndFailures exercising the exact unit-mismatch scenario: 1 fork file (Synced=2) + 2 failed files (Failed=2) aborts because Failed > filesOK(1), even though Failed == Synced - Fix SyncStats comment to reflect that TotalSessions includes OpenCode sessions, not just discovered files Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Stop sending log.Printf output to both stderr and the log file. Diagnostic logs now go to debug.log only, keeping the terminal clean during initial sync (no interleaved timing/HTTP logs with the progress bar). warnMissingDirs uses fmt.Fprintf(os.Stderr) directly since missing directory warnings are user-facing. Simplify initial sync summary to "N sessions synced" (plus failed count when >0), dropping the confusing total/skipped breakdown. Simplify resync modal to show sessions synced + failed (when >0), dropping skipped (always 0 during resync) and total (includes non-interactive files that produce no session). Add failed field to frontend SyncStats type to match Go struct. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
After setupLogFile redirects log output to the debug file, log.Fatalf in mustOpenDB and ListenAndServe would silently exit. Replace with a fatal() helper that prints to stderr and exits. Distinguish os.ErrNotExist from other stat errors in warnMissingDirs to avoid printing "not found" for permission or IO errors. Add failed:0 to all SyncStats test fixtures (sync.test.ts MOCK_STATS and client.test.ts SSE done-event JSON) to match the new required field. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
roborev: Combined Review (
|
Add TestResyncPreservesDataThroughSwap to verify sessions, messages, and projects survive the close-rename-reopen sequence end-to-end via the HTTP API. Add TestResyncConcurrentReads to verify concurrent readers don't panic or deadlock during resync. Add TestCloseRecoveryOnRenameFail to verify service recovery when rename fails. These tests run on both Linux and Windows in CI. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Address review findings: - Abort swap if CloseConnections fails (restore service via Reopen) - Fix TestConcurrentReadsWhileReopen: use WaitGroup.Go instead of manually managing Add/Done with channel close race - Add engine-level TestResyncAllPostReopenAvailability: verify reads and writes work after close-rename-reopen cycle - Add engine-level TestResyncAllConcurrentReads with reader barrier - Tighten TestResyncConcurrentReads: verify resync SSE done stats, add reader-start barrier, assert post-resync data integrity Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix && to || in post-resync SyncAll assertion so partial regressions are caught. Move readyCount.Done() after first successful GetSession call so the reader barrier ensures actual read overlap with resync. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Close origDB connections before copying insights so no writes land in the old DB after the copy. Persist lastSyncStats on all early return paths so /api/v1/sync/status reflects failure state. Gate FTS assertion in TestResyncAllReplacesMessageContent with HasFTS() so the test passes without the fts5 build tag. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Close newDB before removeTempDB on CloseConnections failure to prevent leaked handles and Windows file locks. Capture hadFTS before ResyncAll so FTS regressions are detected even when HasFTS() returns false post-resync. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All three views now exclude sub-agent/fork sessions and empty sessions: - Analytics buildWhere: add relationship_type NOT IN filter - GetStats: count root sessions directly instead of trigger counter - Default analytics date range: "All" instead of 1 year Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
TestSessionCountConsistency seeds root, subagent, fork, empty, and continuation sessions, then asserts that /api/v1/sessions, /api/v1/stats, and /api/v1/analytics/summary all report the same count (only root + continuation sessions with messages). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add session-count-consistency.spec.ts: verifies session list, analytics summary card, and status bar all show the same count - Add subagent, fork, and empty sessions to test fixture to ensure they are excluded from all three counts - Fix e2e server isolation: set COPILOT_DIR and OPENCODE_DIR to empty dir to prevent discovering real host sessions - Stop abbreviating session counts in dashboard (show full number with commas instead of "3.0K") Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Assert each view shows exactly 8 (the known root session count) instead of only checking equality. Catches regressions where all three views silently include subagent/fork/empty sessions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
roborev: Combined Review (
|
If session directories are temporarily inaccessible or misconfigured, discovery may return zero files. Previously this would proceed with the swap, replacing a populated DB with an empty one. Now ResyncAll snapshots the old session count and aborts if the new sync found zero files but the old DB had data. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Three fixes for ResyncAll: 1. Abort swap when CopyInsightsFrom fails instead of continuing and losing all insights. Follows the same recovery pattern as CloseConnections and rename failures. 2. Use filesDiscovered (file-only count) instead of TotalSessions in the empty-discovery guard so OpenCode sessions don't mask missing file-based sessions. 3. Handle oldStats error explicitly: fail closed by assuming the old DB has data worth protecting. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
TestSetupLogFile: close the log file before TempDir cleanup runs. On Windows, open file handles prevent directory deletion. Register cleanup after TempDir (LIFO ordering ensures file closes first). thinking block e2e: use .first() and scope child locators to the block, matching the pattern used by tool-block tests. The fixture session has multiple thinking blocks, causing strict mode violation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The emptyDiscovery check aborted ResyncAll when filesDiscovered==0, even if OpenCode sessions synced successfully. This blocked resync for OpenCode-only datasets. Add stats.Synced==0 to the condition so the guard only fires when no sessions were synced from any source. Add integration test for the OpenCode-only ResyncAll path. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The previous fix (stats.Synced == 0) prevented OpenCode-only abort but reintroduced the mixed-source data-loss risk: when file dirs are inaccessible but OpenCode syncs succeed, file-backed sessions would be silently dropped. Root fix: add DB.FileBackedSessionCount() that queries sessions where agent != 'opencode'. The emptyDiscovery guard now compares file discovery against the old file-backed count, correctly handling both cases: - OpenCode-only: oldFileSessions=0 → guard inactive → resync OK - Mixed source, files missing: oldFileSessions>0 → guard fires → abort protects file-backed sessions Add TestResyncAllAbortsMixedSourceEmptyFiles integration test. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
roborev: Combined Review (
|
database/sql's pool doesn't guarantee the same underlying connection across separate Exec calls. ATTACH DATABASE is connection-scoped, so the subsequent INSERT could run on a different connection that lacks the attached database. Use Conn(ctx) to pin a single connection for the ATTACH/INSERT/DETACH sequence. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add three new review guidelines (11-13) to suppress recurring false positives: - TOCTOU on local user-owned files (not exploitable) - NOT NULL schema constraints (NULL cannot exist in relationship_type) - Verify control flow before flagging logic errors Use TOML triple-quoted multi-line string for readability. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
roborev: Combined Review (
|
roborev: Combined Review (
|
The virtual list scroll clamp test was flaky on CI because it checked scrollTop immediately after filtering without waiting for the filtered results to render. Add a wait for the session list header to show the filtered count before asserting scroll position. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
8e1e3ef to
0c2f4a7
Compare
roborev: Combined Review (
|
|
"Finding 1 (Medium - Reopen failure): Not concerning. If Reopen() fails after a successful rename, the new DB Finding 2 (Medium - Legacy thinking truncation): This is by design. The two-pass regex was the entire point of |
…io#58) ## Summary ### Gemini thinking blocks - **Two-pass thinking regex**: Split `THINKING_RE` into `THINKING_MARKED_RE` (for `[/Thinking]`-delimited blocks, matched first) and `THINKING_LEGACY_RE` (fallback). Prevents fallback delimiters from truncating marked blocks. - **Thinking end markers**: All parsers (Claude, Gemini, OpenCode) emit `[/Thinking]` end markers. - **Gemini thinking format**: Parts joined with `\n\n`, ordered chronologically (thinking, content, tools). - **Merge consecutive thinking blocks**: Multiple Gemini thoughts collapse into a single collapsible block. - **Thinking-only filter fix**: Messages with thinking + response text are no longer hidden by the `showThinking` toggle. ### Session count consistency - **Aligned counts across views**: Session list, analytics summary, and status bar all use the same root-session filter (`message_count > 0 AND relationship_type NOT IN ('subagent', 'fork')`). - **Full numbers in dashboard**: Removed K/M abbreviation, always shows full number with comma separators. - **Default date range**: Analytics defaults to all-time instead of 1 year. - **Go HTTP integration test**: `TestSessionCountConsistency` seeds mixed session types and asserts all three endpoints agree. - **E2e Playwright test**: `session-count-consistency.spec.ts` verifies the same invariant through the full UI stack, asserting exact expected count. ### Resync reliability - **Fresh database build**: `ResyncAll` builds a new DB from scratch and swaps atomically, instead of in-place mutation. - **Atomic DB connections**: `atomic.Pointer[sql.DB]` for reader/writer so HTTP handlers don't race with connection swaps. - **Windows-safe swap**: Close connections before rename to avoid mandatory file locking failures. - **Empty-discovery guard**: Abort resync when file discovery returns zero but old DB has file-backed sessions. Uses `FileBackedSessionCount` (excludes OpenCode) so OpenCode-only datasets aren't blocked, while mixed-source data loss is prevented. - **Insights preservation**: Abort swap when `CopyInsightsFrom` fails instead of silently dropping insights. - **Failure tracking**: Abort when failures exceed successful syncs. - **FTS rebuild**: Drop and rebuild FTS index during resync. - **Sync serialization**: `SyncPaths` and `SyncSingleSession` serialized with `syncMu`. ### E2e test isolation - All agent directories (Claude, Codex, Copilot, Gemini, OpenCode) point to empty dirs in e2e-server.sh, preventing real session data from leaking into tests. - Test fixture seeds subagent, fork, and empty sessions alongside root sessions. ### Other - **Session ID for all agents**: Gemini, OpenCode, and Copilot sessions show the copyable session ID. - **Clean startup logging**: `log.Printf` goes to `~/.agentsview/debug.log` only. Missing directory warnings stay on stderr. - **Debug log management**: Truncate `debug.log` on startup when >10MB. - **Windows test fix**: Close log file before TempDir cleanup to avoid mandatory file locking. Fixes kenn-io#56 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Summary
Gemini thinking blocks
THINKING_REintoTHINKING_MARKED_RE(for[/Thinking]-delimited blocks, matched first) andTHINKING_LEGACY_RE(fallback). Prevents fallback delimiters from truncating marked blocks.[/Thinking]end markers.\n\n, ordered chronologically (thinking, content, tools).showThinkingtoggle.Session count consistency
message_count > 0 AND relationship_type NOT IN ('subagent', 'fork')).TestSessionCountConsistencyseeds mixed session types and asserts all three endpoints agree.session-count-consistency.spec.tsverifies the same invariant through the full UI stack, asserting exact expected count.Resync reliability
ResyncAllbuilds a new DB from scratch and swaps atomically, instead of in-place mutation.atomic.Pointer[sql.DB]for reader/writer so HTTP handlers don't race with connection swaps.FileBackedSessionCount(excludes OpenCode) so OpenCode-only datasets aren't blocked, while mixed-source data loss is prevented.CopyInsightsFromfails instead of silently dropping insights.SyncPathsandSyncSingleSessionserialized withsyncMu.E2e test isolation
Other
log.Printfgoes to~/.agentsview/debug.logonly. Missing directory warnings stay on stderr.debug.logon startup when >10MB.Fixes #56
🤖 Generated with Claude Code