Repository navigation
fix(chat): a search pick opens a message that changed since it was searched - #2988
Merged
Merged
Conversation
…arched (#2958) A pick looked for the row named exactly as the turn was when searched (conversationTurnAnchor), so an answer that went on, or whose first tool's output came, was reported as gone. When no row has that name back to the conversation's beginning, the pick now takes the one turn of the match's role and time that still holds the match with the same words before it, and none when two do (Codex dates answers the user steered between by one time).
|
Note Currently processing new changes in this PR. This may take a few minutes, please wait...
|
Review round: the pick now settles on the rows shown once its page is in (a refresh may have changed them while earlier pages were read) and opens a row only when exactly one turn is the match by its name or changed since (pickedTurn); an answer that grew while a later answer of its time took its old name opens none. A Markdown mark that streamed in and closed (a link) no longer hides the words before the match.
… and a match at the start counts only its own answer Codex re-review: a pick whose named turn changed before its row was drawn stayed loading; it now settles on the turn changed since too. Words before the match compare exactly unless a snippet was cut, so an answer that begins with the match no longer counts a later answer of its time.
…, and a pick settles at once omo review: two answers of one time that both begin with the match counted each other, so neither opened. A turn changed since the search must now go on from the searched turn's opening (its first text grew, or its first tool is the same call up to its output). The pick settles on the first draw after its pages are in, so it never waits on a row that was renamed meanwhile.
…nto the match omo review: turns sharing a name (an untimed prompt sent twice) both counted, so neither opened, where main opened the first; the first named turn opens again unless a turn changed since also is the match. A changed turn's words after the match must go on as they did, and a pick with no list to show it in fails instead of waiting.
…e match omo review: an unchanged answer followed by a longer answer of its time that begins with it (Codex dates both by the task's start) was reported missing. A grown turn can only lose its old name to a later turn, so only a changed turn before the named one stops the pick. An image link's mark is ignored like a link's.
Owner
Author
|
Review findings and how each was resolved (omo + Codex Terra xhigh, four passes): Fixed in this PR
Left, not blocking
CodeRabbit was rate-limited on these heads, so the reviews above stand in for it. |
…y name only omo review: answers after the named turn (a task steered twice) or untimed turns that begin with the match blocked an unchanged message that main opens. Only a changed turn of its time before the named one blocks it, a missing time tells no turn apart, and a link mark after the match is ignored like one before it.
Owner
Author
|
Fifth pass (omo + Codex on b747b5f), resolved in f6caa8b:
|
Owner
Author
devswha
added a commit
that referenced
this pull request
Nov 5, 2026
…rown answer's name (#3001) Fixes #2993 ## What changes A conversation search pick stopped reading earlier pages as soon as a loaded turn had the match's name (`conversationTurnAnchor`). When the searched answer grew after the search and sits on an older page not loaded yet, a later answer of the same time that begins with the same words (Codex dates answers the user steered between by the task's start) had the old name, and the pick opened it. - `pickSettled` (`shared/conversation-search.ts`): the pick keeps reading earlier pages until a turn has the match's name **and** a turn before that one is of an earlier time than the match (or the beginning is read). A turn that changed since can lose its name only to a later turn of its own time, and turns come in time order except answers dated by an earlier task start, which all follow that start. Then `pickedTurn` decides on the rows shown, as before: it opens the one turn that is the match, or none (`several` -> "missing") when an earlier turn grew into the match and a later one took its name. A time that cannot be parsed tells nothing (the pick reads on); a match without a time goes by name only, as before. - Turns of the match's time that share its name (their first 80 characters agree, or they start with the same tool call) go by which still has the matched words anywhere in its text, progress notes included; none opens unless exactly one does. Reading back could otherwise bring the older one in first and open it, and the searched answer's words never leave its text, though they can leave the search (Codex can mark it commentary later), so that case opens none. - Usual cost: a Claude-style answer has its prompt of an earlier time right before it, so nothing more is read; a Codex answer reads at most back to the previous task. - The issue's second case (comment: a Codex prompt whose inline picture is paired with its local path after the search) is not changed here: it says "missing", never opens a wrong turn, and is tracked in #3003. A rule accepting any picture first was tried and reverted after review (it could open another prompt with a different picture). No per-turn transcript id was added: this stays within the client and the shared helpers, with no wire or reader change. ## Validation - New tests in `shared/conversation-search.test.ts` (`pickSettled`) failed on the unchanged code (missing export; the old stop rule settles as soon as the later answer is read) and pass now: `bun test shared/conversation-search.test.ts server/conversation-search.test.ts`. - `bun run check fast`: pass at 095eaa4 (typecheck, build, unit tests and the rest of the fast set) - Skipped: `check full` and a browser run (no browser test drives the pick; the rule is covered at the shared helpers as in #2988).
This was referenced Nov 5, 2026
Merged
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.

Fixes #2958
What changes
A search pick looked for the row named exactly as its turn was when searched (
conversationTurnAnchor). An answer that went on, or whose first tool's output arrived after the search, has another name by then, so the pick read every earlier page and said "This message is no longer in the conversation".Now the pick reads earlier pages until a turn with that name is in, or back to the beginning, as before, and then settles on the rows shown at that moment (a refresh may have changed them meanwhile): it opens the first turn with the match's name, as main does, unless a turn of its time before it changed into the match; otherwise the one turn that changed since (turns without a time go by name only) (
pickedTurnandsearchedTurnChangedinshared/conversation-search.ts). A changed turn has the match's role and time, goes on from how the searched turn began (its first text grew, or its first tool is the same call up to its output), and still holds the match after the same words (a Markdown mark that streamed in and closed, such as a link, is ignored). When two turns qualify (Codex dates answers the user steered between by one time; an answer that grew while a later one took its old name), none opens and the line says the message is missing: never the wrong answer, which is why #2891's prefix fallback was reverted.Design reference: the Codex and Claude apps jump to a search hit by the message's own id; our transcripts have none for every agent, so this keeps the content name and adds a strict, unambiguous fallback instead of a per-agent id. The pick reads no more pages than before.
Review round
Codex (Terra, xhigh) on the first head: a refresh during the page reads could make the counted candidate differ from the row opened (HIGH), an exact name could name a later answer after the searched one grew (HIGH, pre-existing), and a closed Markdown link changed the words before the match (MEDIUM). All three are fixed here: the decision moved to the rows shown at the end, an exact name no longer wins over a second qualifying turn, and the comparison ignores link and emphasis marks. omo: CLEAR on the first head.
Second round (omo + Codex on dfc5be6): two answers of one time that both begin with the match counted each other, so neither opened (fixed: a changed turn must go on from the searched turn's opening; test added); a pick whose named row was renamed before it was drawn could stay loading (fixed: the pick settles on the first draw after its pages are in, since
loadOlderlays a page before it answers). Codex also reproduced a pre-existing case (main does the same): when the grown answer sits on an older page not loaded yet and a later answer of its time took its old name, the named one opens. Tracked in #2993.Third round (omo on 0a24b61): turns sharing a name (an untimed prompt sent twice) both counted and neither opened, where main opened the first (fixed: the first named turn opens unless a turn changed since also qualifies; test added). Also fixed: a changed turn's words after the match must go on as they did (a stale view with two same-opening tool answers no longer opens the other), and a pick with no list to show it in fails instead of waiting. Left as LOW: an answer that itself begins with "…" is read as a cut snippet and, once grown, is reported missing. Fourth round (omo on 70523f8): an unchanged answer followed by a longer answer of its time that begins with it was reported missing (fixed: only a changed turn before the named one blocks; test added); an image link's
!is ignored like a link's. Fifth round (omo on b747b5f): answers after the named turn (a task steered twice) and untimed turns that begin with the match blocked an unchanged message main opens (fixed: the named turn decides; only a changed turn of its time before it blocks; a missing time tells no turn apart); a link mark after the match is ignored too. Codex's last MEDIUM (a Codex image prompt searched between its inline and event records reads as missing, as on main) is noted on #2993.Validation
bun test shared/conversation-search.test.ts server/conversation-search.test.ts: 22 pass (with the steered-twice and untimed cases). New tests use the Codex transcript parser (tool output arriving after the search; two steered answers of one time), a grown answer, a closed link, andpickedTurn's none/one/several cases.bun scripts/conversation-search-demo-regression.ts(1280 and 390): pass. New case: the demo serves every match under its old (changed) name, and the pick still opens the answer. Without the fix this case times out with the "no longer in the conversation" line.bun run check faston the final head with origin/main merged (6193eec, unit 12150 pass, 0 fail): pass (typecheck, build, unit tests, generated files ok).changelog.d/chat-conversation-search.md).