Skip to content

Count a Codex thread as new only once Codex reports its session - #47

Merged
aaroncoville merged 2 commits into
theme/sixth-historyfrom
fix/codex-thread-switch-confirm
Oct 10, 2026
Merged

aaroncoville merged 2 commits into
theme/sixth-historyfrom
fix/codex-thread-switch-confirm

Conversation

@aaroncoville

@aaroncoville aaroncoville commented Oct 10, 2026 •

Copy link
Copy Markdown
Owner

What & why

When a Codex worker is moved to a new thread, the app types /new, then the worker's brief and its inbox nudge. Until now the thread record moved as soon as those keystrokes reached the terminal. But Codex drops a /new it refuses (one typed while a turn is running) without leaving it in the input box. The brief and nudge pasted after it then join the turn already running, in the same session. The record then described a thread that did not exist. The worker's earlier follow-ups looked like new work, and the mail it had just been handed belonged to no thread.

The new thread's first prompt reports its new session id through the hooks. So the move is now recorded only once that id shows: about ten seconds after the paste is submitted, the worker's registry session is compared with the one it had before.

  • Changed: the new thread is recorded.
  • Unchanged: the mail is recorded as handed to the thread it actually went to.

No other nudge is typed into the worker until that is decided. The codex-new-thread log line now carries switched.

Type of change

  • Bug fix
  • New feature
  • Refactor / cleanup
  • Docs
  • Build / CI

Evidence

Before

On Codex 0.162.0, I replayed the exact keystroke plan into a pseudo-terminal while a turn was running (a 400-line output task). These are the hook payloads Codex sent:

{"e":"SessionStart","s":"efc63f"}
{"e":"UserPromptSubmit","s":"efc63f","p":"Write the integers from 1 to 400, one per line, with no other text."}
{"e":"UserPromptSubmit","s":"efc63f","p":"You are \"Probe\", a test agent.\nLine two of the brief.\nReply to anythin…"}
{"e":"Stop","s":"efc63f"}

No new session started, and nothing was left in the composer: the second prompt starts with the brief, not with /new. Even so, the code before this change recorded a new thread. Here is the new test, run against that code:

not ok 19 - a /new Codex refused leaves the mail with the thread it went to

After

ok 18 - the renderer’s nudge for a new request becomes /new, the brief and the nudge
ok 19 - a /new Codex refused leaves the mail with the thread it went to
  • A confirmed move logs switched: true. Its record starts over, so a late fix round for the previous review is new work.
  • A refused one logs switched: false, and the worker keeps its thread.

Full suite: 1757 of 1759 pass, 2 skipped, 0 failing.

How I tested it

  • OS: macOS (Darwin 25.6, arm64), Codex CLI 0.162.0, Node 22
  • Steps:
    • Wrote the refusal test first and saw it fail. I also extended the existing move test so that it checks the session id moved and that the old thread's follow-ups are left behind.
    • Broke the code on purpose: forced switched true, forced it false, dropped the fallback record, recorded a confirmed move as a fallback, and never released the in-flight guard (which would swallow every later nudge to that worker). Each one turned the suite red.
    • Replayed the plan into a running turn on the real TUI, shown above.
    • npm run typecheck: 0 errors. npm run build: succeeds.
  • Known degradation, by design: a worker whose hooks never reported a session id cannot be confirmed, so a real move is recorded as a fallback and its follow-ups continue. That only happens when the hook wiring is already broken. A SessionStart that arrives after the ten-second wait behaves the same way.

Credit (optional)

Discord:

X:

Checklist

  • Before and after evidence is attached above, under both headings.
  • npm run typecheck passes.
  • npm run test:focused passes.
  • npm run build succeeds.
  • This PR is one change. Unrelated fixes belong in their own PR.
  • I read the diff myself before opening this, and there is no debug output,
    commented-out code, or unrelated formatting churn in it.
  • Any new UI derives from DESIGN.md / tokens.ts — no ad-hoc colors,
    spacing, or fonts. (No UI.)
  • If I added art, it's my own or compatibly licensed, and listed in
    ATTRIBUTION.md. (No art.)

A /new that Codex did not act on was still recorded as a new thread. The
worker's earlier follow-ups then looked like new work, and the mail it
had just been handed belonged to no thread.

The record moved as soon as the keystrokes reached the terminal. But
Codex drops a /new it refuses (one typed while a turn is running, on
0.162.0) without leaving it in the input box, and the brief and nudge
pasted after it then join the turn already running, in the same session.

The new thread's first prompt reports its new session id through the
hooks. A few seconds after the paste is submitted, the worker's
registry session is compared with the one before. Only a changed id
records the new thread. Otherwise the mail is recorded as handed to the
thread it actually went to. No nudge is typed into the worker until that
is decided.
@github-actions

Copy link
Copy Markdown

🚫 This PR is missing its before/after evidence

Every pull request here has to show its work. Screenshots or a short screen recording, before the change and after it.

  • Before — no image or video under that heading
  • After — no image or video under that heading

How to fix it: edit the description, keep the ### Before and ### After headings from the template, and drag an image or video under each. GitHub uploads it inline. This check re-runs the moment you save.

A bug fix with no visible surface still needs it: show the failing behaviour, then the same steps passing. A terminal recording is fine.

Genuinely nothing to show — a CI tweak, a typo, a dependency bump? A maintainer can apply the no-visual-change label. Please don't ask unless it truly has no observable effect.

📖 CONTRIBUTING.md → Evidence is mandatory

…low-ups behind

The test of a confirmed move only checked that a follow-up to the new
thread stays in it, which holds whether the record starts over or keeps
the old thread's messages. Keeping them would continue a late fix round
for the previous review into a thread that no longer has its context.
Assert that such a fix round is new work.
@aaroncoville
aaroncoville merged commit edee6d3 into theme/sixth-history Oct 10, 2026
0 of 2 checks passed
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.

1 participant