Skip to content

Name each Codex conversation, and draw every one that is working - #331

Merged
vinzdg merged 1 commit into
vinzdg:mainfrom
Splinter0000:paulo/codex-thread-names
Sep 23, 2026
Merged

vinzdg merged 1 commit into
vinzdg:mainfrom
Splinter0000:paulo/codex-thread-names

Conversation

@Splinter0000

Copy link
Copy Markdown
Contributor

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 threads row the rollout path
was already being read from. Independent of #321; the two touch no file in
common.

What a row is called

  1. Codex's own threads.name — every VS Code thread on the machine this was
    built on has one, a moment after its first turn.
  2. Otherwise the request itself, first line, capped at 48 characters. codex exec never names a thread, so this is permanent there. When Codex has put
    attached files or in-app browser state ahead of the request, it marks
    where the request begins with a ## My request line; the label is taken
    from under that marker (or from after a leading <tag>…</tag> block), so a
    row never reads "# Files mentioned by the user:". Numeric character
    references (&#x20;) are decoded. A request that opens with a #hashtag of
    the user's own is left alone — only Codex's own markers are taken off.
  3. Then the folder, as a Claude session falls back to.
  4. Then "Codex", as before.

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.source is {"subagent":{"other":"guardian"}} — no parent at
all — 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_meta line of the guardian's rollout, so it is read
from 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_complete is not
the request finishing.

Also

  • The desktop app's copy of a conversation. local_thread_catalog shares
    thread_id with threads.id when 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.
  • since means what AgentSession says: when the row entered its state.
    A rollout's last write moves every second while a conversation works; as
    since it made two busy rows swap places every few seconds and made every
    tick look like a change. The monitor now keeps the first sighting of a row in
    a state until the state changes (settled(_:entered:)).
  • A rollout is parsed again only when its (mtime, size) moves. The parse
    walks up to a megabyte backwards on the main actor, and it now runs per live
    conversation rather than once per tick.
  • Older stores. Columns are asked for only when pragma_table_info says the
    table has them, so a store from before name existed reads exactly as before.
  • Ids in queries. A parent id comes out of JSON another program writes; it
    is checked (isPlainID) before it is put in a query.
  • A debug line on change, as the Claude monitor has, so what the notch thinks is
    running can be seen with log stream.

Notes for review

  • CodexStore.newestRollout and CodexStoreCache.newestRollout no longer have
    a 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.
  • A new codex exec thread can read as its folder for a second or two before
    Codex writes its preview; it then takes the request's first line.
  • I did not pre-filter the per-tick stats by updated_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 ~/.codex

194 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, the
request 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

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>
@vinzdg
vinzdg merged commit 2a75ca3 into vinzdg:main Sep 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants