Repository navigation
Stop a bad transcript or a stale search batch crashing the TUI - #50
Merged
Merged
Conversation
Searching transcripts could die with `DuplicateIds: sr-0`.
`_clear_search_results` emptied the hit list and awaited
`ListView.clear()` without first bumping `_search_token`, and that
await is a yield point: a batch the outgoing scan had already handed
over with `call_from_thread` was applied during the clear, its token
still looking current, mounting an `sr-0` that the fresh scan then
collided with. Bump the token and cancel the scan before the await,
the invariant the other teardown paths already follow.
Three fallbacks stop an unreadable transcript taking the session with
it. `_apply_search_batch` builds and mounts each row inside a `try`,
appending to `_search_hits` only once the row is up so the `sr-{index}`
ids stay in step. `get_sessions_for_path` caught only `ClaudeLogError`,
so an unknown entry shape raised straight into the home screen's render
path; it now drops the log and counts it, as `iter_hits` already did.
And `_show_session_log` wraps the render as well as the parse — a
transcript can parse cleanly and still carry text a widget refuses — so
a render failure falls back to the raw log rather than an empty screen.
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.
Summary
Searching transcripts could kill the TUI outright with
DuplicateIds: Tried to insert a widget with ID 'sr-0'. This fixes the race behind it, and adds the fallbacks that stop one unreadable transcript taking the session with it.The crash
_clear_search_resultsemptied_search_hitsand awaitedListView.clear()without first bumping_search_token. That await is a yield point, so a batch the outgoing scan had already handed over withcall_from_threadwas applied during the clear — its token still looked current, so it mountedsr-0into the list. The fresh scan then appended its ownsr-0.sequenceDiagram participant W as scan worker (thread) participant L as event loop participant V as #search-results W->>L: call_from_thread(_apply_search_batch, token=T) Note over L: query changes → _clear_search_results() L->>V: await ListView.clear() L-->>L: queued batch runs during the await Note over L: token still T — looks current L->>V: append sr-0 (stale) Note over L: _start_transcript_search() bumps to T+1 L->>V: append sr-0 💥 DuplicateIdsThe token is now bumped (and the scan cancelled) before the await, which is the invariant the other teardown paths —
_start_transcript_search,_stop_transcript_search— already followed. This one just didn't.Not crashing on a bad transcript
Three places, each degrading to something specific rather than dying:
_apply_search_batchbuilds and mounts each result inside atry. A row that won't mount costs that row (logged astui.search_row_failedwith the traceback)._search_hitsis appended to only after the row mounts, so thesr-{index}ids stay in step with the hits a restore indexes into.get_sessions_for_pathcaught onlyClaudeLogError, so anAttributeErrorfrom one unknown entry shape raised straight into the home screen's render path, which runs on every render. It now catchesExceptionper log, counts it into the existingfailed=field and logsclaude.session_unparsedwith the traceback. Same contractsearch.iter_hitsalready kept.read_transcript. A transcript can parse cleanly and still carry text a widget refuses (aCollapsibletitle is parsed as markup — the documentedMarkupErrorcrash)._show_session_lognow wraps the render too and re-renders raw, which builds nothing butStatics. A failure of the raw render is an error screen, since there is nothing simpler to draw.Tests
994 passing. New coverage:
test_a_batch_in_flight_when_results_are_cleared_is_dropped— reproduces the crash; verified it fails without the token fix.test_a_row_that_cannot_be_mounted_is_skippedtest_an_unexpected_parser_crash_is_skipped_tootest_a_render_crash_falls_back_to_the_raw_logtest_a_raw_render_crash_is_an_error_screenDocs
CLAUDE.md updated in three places: the
is_cancelled-isn't-a-synchronisation-primitive gotcha now names the await-before-bump trap, and the raw-fallback section covers the render half and the discovery path.