CPLAT-11784: back ccx cross-session search with a SQLite FTS5 index - #160
Merged
Merged
Conversation
Kairo-Kim
approved these changes
Aug 28, 2026
|
| CVE | Package | Version |
|---|---|---|
| CVE-2026-39824 | golang.org/x/sys |
v0.42.0 |
View full analysis in Upwind Console
Scan completed in 17s
Scan history (3 scans)
| Commit | Scanned at | New | Resolved | Net |
|---|---|---|---|---|
7163108 |
2026-08-28 13:02 UTC | 0 | -1 | -1 |
0b918b9 |
2026-08-29 21:42 UTC | 0 | -1 | -1 |
1db57dd < |
2026-08-29 21:44 UTC | 0 | -1 | -1 |
Last scanned: 1db57dd · 2026-08-29 21:44 UTC
|
| Commit | Scanned at | New | Resolved | Net |
|---|---|---|---|---|
7163108 |
2026-08-28 13:02 UTC | 0 | 0 | 0 |
0b918b9 |
2026-08-29 21:42 UTC | 0 | 0 | 0 |
1db57dd < |
2026-08-29 21:44 UTC | 0 | 0 | 0 |
Last scanned: 1db57dd · 2026-08-29 21:44 UTC
Ctrl+S re-read every transcript on each query. On a 2.5 GB / 995-session corpus that is 0.6-1.9s per search, growing with history. Index transcript content into ~/.claude/.ccx-index.db and query that instead, falling back to the full scan whenever the index cannot answer a query. Measured on the real corpus: worktree (broad) 1765ms -> 44ms goreleaser (rare) 674ms -> 5ms 워크트리 (Korean) 627ms -> 24ms tool:Bash worktree 1647ms -> 47ms Index is 401 MB, first build ~70s, incremental refresh 25-162ms. Choices worth knowing, all forced by measurement rather than taste: - modernc.org/sqlite (pure Go) keeps the goreleaser cross-compile matrix working; a CGO driver would break darwin/linux x amd64/arm64. - tokenize='trigram' preserves the existing strings.Contains semantics and Korean matching. unicode61 found 1930 hits where the scan found 10471, so it is not a drop-in. - detail=full is required, not chosen: trigram matching is internally a phrase query and FTS5 rejects those unless detail=full, which rules out the smaller layouts. - content='' with contentless_delete=1 avoids storing a second copy of text that is already on disk (410 MB -> 284 MB on a sample) while still allowing a changed file's rows to be deleted and reindexed. tool_result is deliberately not indexed: it is half the corpus and mostly file dumps, and including it nearly doubles the index. The modal says "tool output not indexed" so the gap is visible rather than silent, and queries with a sub-trigram term fall back to the scan automatically. Also fixes a pre-existing bug in executeSearch: it ranged the same result channel from two goroutines, so results were split non-deterministically and one goroutine's share was thrown away. Ctrl+S has been dropping results.
gavin-jeong
force-pushed
the
CPLAT-11784-search-fts5-index
branch
from
August 29, 2026 21:34
7163108 to
1db57dd
Compare
Filtering told you which blocks matched but not where, so the term still had to be found by eye in the pane on the right. Three paths were missing the paint: - Block filter (`/`) in the conversation view: decided visibility only. Now the surviving blocks have their matching text highlighted. - Cross-session search (Ctrl+S): the query was dropped at the jump, so the message you landed on looked like any other. The query's text terms now follow the jump into the preview and details. - Session-list preview: the one-line summary was already highlighted via highlightSnippet, but expanding a row showed the full body unpainted — which is precisely when you are hunting for the term. Structural filter tokens (is:tool, tool:Bash) select whole blocks rather than text within them, so highlightableTerms drops them; painting them would mark the literal string "is:tool" wherever it appeared. Negated terms (!foo) mark what must be absent and are dropped for the same reason. openConversation clears the carried highlight by default and the search path re-applies it immediately after. Seven of the eight callers are not search jumps, so defaulting to off is what keeps a stale query from leaking into an unrelated session.
gavin-jeong
force-pushed
the
CPLAT-11784-search-fts5-index
branch
from
August 29, 2026 21:36
1db57dd to
0b918b9
Compare
Kairo-Kim
approved these changes
Aug 29, 2026
This was referenced Aug 30, 2026
gavin-jeong
added a commit
that referenced
this pull request
Aug 30, 2026
#160 added a SearchMode argument to updateSearchResults while #162 added tests that call it. Both were green on their own branches and neither touched the other's files, so git merged them without conflict — but the merged tree does not compile: vet: search_resume_test.go:17: not enough arguments in call to a.updateSearchResults have ([]session.SearchResult) want ([]session.SearchResult, session.SearchMode) Production code is unaffected (go build passes); only the test callers are stale. Pass SearchModeScan, which is what these tests were written against — they exercise live badges and resume, not index coverage. Co-authored-by: keyolk <keyolk@gmail.com>
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.
JIRA: https://sendbird.atlassian.net/browse/CPLAT-11784
Problem
Cross-session search (
Ctrl+S) re-reads every transcript on each query. On my corpus (2.5 GB, 995 sessions) that is 0.6–1.9s per search, and it gets worse as history grows. There was no index at all.While measuring this I also found
executeSearchranging the same result channel from two goroutines — results were split non-deterministically between them and one goroutine's share was discarded.Ctrl+Shas been silently dropping results.What this does
Indexes transcript content into
~/.claude/.ccx-index.db(SQLite FTS5) and queries that, falling back to the full scan whenever the index can't answer a query.Measured on the real corpus, at the limit the TUI actually uses:
worktree(broad)goreleaser(rare)워크트리(Korean)tool:Bash worktreeIndex is 401 MB, first build ~70s, incremental refresh 25–162ms.
Design notes
Each of these was forced by measurement, not preference — worth knowing before changing them:
modernc.org/sqlite(pure Go) — keeps the goreleaser cross-compile matrix intact. A CGO driver likemattn/go-sqlite3would break darwin/linux × amd64/arm64 builds.tokenize='trigram'— the only option preserving the existingstrings.Containssemantics and Korean matching.unicode61returned 1930 hits where the scan returned 10471; it is not a drop-in.detail=full— required, not chosen. Trigram matching is internally a phrase query, and FTS5 rejects phrase queries unlessdetail=full. This rules out the smallerdetail=nonelayouts.content=''+contentless_delete=1— the text is already on disk, so storing a second copy is pure overhead (410 MB → 284 MB on a sample).contentless_deleteis what makes incremental reindexing possible at all.Known gap (intentional)
tool_resultis not indexed. It is half the corpus but mostly file dumps and command output; including it nearly doubles the index for little search value. Rather than hide this, the modal showstool output not indexed, and the full scan still finds that content.Queries with a term shorter than 3 characters fall back to the scan automatically — a trigram index cannot match them.
Testing
go test ./...).user:/assistant:/tool:scopes).tool_resultscan hit is found by the index. This is what caught a real bug —system_tagblocks were being dropped, costing 58 hits onworktree. Fixed by invertingindexableBlockto a denylist, plus a regression test.system_tagomission) to confirm they actually fail on the defects they cover.