Name each Codex conversation, and draw every one that is working - #331
Merged
Merged
Conversation
Every Codex row said "Codex": one row at most -- the newest rollout or the newest desktop thread, whichever moved last -- while the Claude rows beside it named every session. Two conversations running at once drew as one, and nothing on the notch said what either was about. Codex names its conversations, in the same threads row the rollout path was already read from. So each working conversation is now its own row, under Codex's own name for it. A codex exec thread, which Codex never names, falls back to the request itself -- taken from under Codex's own "## My request" marker when Codex has put attached files or browser state ahead of it -- then the folder, then "Codex" as before. Sub-agents are folded into the conversation they work for rather than drawn as rows of their own: Codex can run half a dozen for one request, and the parent, waiting on them, may write nothing for minutes. That includes guardians, the helpers auto-review runs to vet each action, whose threads row names no parent at all; the parent is read from the opening session_meta line of their rollout. A helper whose conversation cannot be found is not drawn. A row's state is only ever its root's, so a helper writing task_complete -- a guardian does after every review -- no longer announces "Complete" for a request still under way. `since` now means what AgentSession says it means, when the row entered its state, rather than the rollout's last write; as the last write it made two busy rows swap places every few seconds. A rollout is parsed again only when its (mtime, size) moves, since the parse walks up to a megabyte on the main actor and now runs per live conversation. The desktop app shares thread ids with the threads table when the same conversation is open in both (ten of forty-three on the machine this was checked on), which the old newest-of-two rule hid. It is drawn once. Columns are only asked for when the table has them, so a store from before `name` existed still reads. Checked against a real ~/.codex: 194 threads, 133 of them helpers, all 133 folded into one of 61 conversations, no row labelled with injected context. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.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.
Name each Codex conversation, and draw every one that is working
Every Codex row said "Codex". The monitor drew one row at most — the newest
rollout or the newest desktop thread, whichever moved last — while the Claude
rows beside it named every session. Two conversations running at once drew as
one, and nothing on the notch said what either was about.
Codex does name its conversations, in the same
threadsrow the rollout pathwas already being read from. Independent of #321; the two touch no file in
common.
What a row is called
threads.name— every VS Code thread on the machine this wasbuilt on has one, a moment after its first turn.
codex execnever names a thread, so this is permanent there. When Codex has putattached files or in-app browser state ahead of the request, it marks
where the request begins with a
## My requestline; the label is takenfrom under that marker (or from after a leading
<tag>…</tag>block), so arow never reads "# Files mentioned by the user:". Numeric character
references (
 ) are decoded. A request that opens with a#hashtagofthe user's own is left alone — only Codex's own markers are taken off.
One row per conversation — not per thread
Sub-agents are folded into the conversation they work for. Codex can run half a
dozen for one request, each with a rollout of its own, and the parent, waiting
on them, may write nothing for minutes. The row keeps its root's name and spins
for as long as any of it is moving.
That includes guardians, the helpers auto-review runs to vet each action.
Their
threads.sourceis{"subagent":{"other":"guardian"}}— no parent atall — and taken for a conversation, each review drew a row of its own, named
after the guardian prompt, that chimed "Complete" while the real request was
mid-turn (six false chimes in forty minutes in one real session). The parent is
in the opening
session_metaline of the guardian's rollout, so it is readfrom there, once per change of the store and only for helpers the store has no
parent for. A helper whose conversation cannot be found is not drawn at all.
A row's state is only ever its root's. A helper writing
task_completeis notthe request finishing.
Also
local_thread_catalogsharesthread_idwiththreads.idwhen the same conversation is open in both —ten of forty-three here. The old newest-of-two rule hid that; it is now drawn
once.
sincemeans whatAgentSessionsays: when the row entered its state.A rollout's last write moves every second while a conversation works; as
sinceit made two busy rows swap places every few seconds and made everytick look like a change. The monitor now keeps the first sighting of a row in
a state until the state changes (
settled(_:entered:)).(mtime, size)moves. The parsewalks up to a megabyte backwards on the main actor, and it now runs per live
conversation rather than once per tick.
pragma_table_infosays thetable has them, so a store from before
nameexisted reads exactly as before.is checked (
isPlainID) before it is put in a query.running can be seen with
log stream.Notes for review
CodexStore.newestRolloutandCodexStoreCache.newestRolloutno longer havea caller in the app. I left them and their tests in place rather than delete
your tests in the same change — happy to remove them here if you prefer.
codex execthread can read as its folder for a second or two beforeCodex writes its preview; it then takes the request's first line.
stats byupdated_at_ms. It would cut~30 stats a tick to a handful, but it makes liveness depend on Codex updating
that column during a long turn, and a stat is microseconds.
Checked against a real
~/.codex194 threads, 133 of them helpers: all 133 folded into one of 61 conversations,
none dropped for want of a parent, no row labelled with injected context. Live,
with the app running: two conversations working at once drew as two named rows,
and the one that finished went busy → success.
Tests: 25 new in
CodexConversationTests— naming and its fallbacks, therequest under Codex's markers, one row per conversation, sub-agents and
guardians folded, a helper finishing not finishing the row, an orphaned helper
not drawn, the desktop copy drawn once, an older schema, the stable order and
clock, and a rollout not parsed again while unchanged. Full suite 1759 / 0
failures on this branch alone.
🤖 Generated with Claude Code