Skip to content

Answer agent prompts from the chat, and recognize Claude Code 2.1 / Codex 0.156 prompts - #6

Merged
devswha merged 12 commits into
devswha:mainfrom
Yoonwoo-Ha:feat/chat-answers-prompts
Sep 25, 2026
Merged

devswha merged 12 commits into
devswha:mainfrom
Yoonwoo-Ha:feat/chat-answers-prompts

Conversation

@Yoonwoo-Ha

@Yoonwoo-Ha Yoonwoo-Ha commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  1. Prompts the parser missed (fix(prompt)). I checked live Claude Code 2.1.280 and Codex 0.156.0 panes. Each of these used to leave the chat at INPUT with no card, and now gets one:

    • Claude with several questions (tabs), plus its review step
    • Claude tool approvals, which no longer print the old "requires approval" and "Dangerous rm" markers
    • Codex's last question of several
    • Codex's folder trust prompt
  2. Answering from the composer (feat(chat)). While a card shows, a message answers it: an option's number, its label, or its bound letter. Any other text becomes the prompt's own reply when the prompt takes one. A prompt that takes only its options keeps the text in the box and says how to choose.

  3. Review follow-ups (fix(prompt)). Typed picks on menus (Claude's "Review your answers", Codex's continue menus) now wait for Confirm as well:

    • A Claude multiple choice always ends with → alone. That moves to the next question or to the review card, which has its own Submit. I checked this live with one question and with two.
    • The tab bar is found however far up a long question wraps, including a bar cut off by a narrow pane. The wrapped question lines are joined back into one question.
    • A typed pick on an approval or a plan card doesn't send by itself. The card highlights the pick and asks Confirm / Cancel. Tapping an option still sends right away.
    • An approval's title comes from the panel under the tool call, not from a rule inside a file preview. An option label wrapped over several lines reads whole.
  4. Codex's queued questions (feat(prompt)). Codex 0.156 can ask with request_user_input_async without stopping. The questions then wait in a queue above its main prompt, collapsed to "? 2 questions / alt+↑ to answer", so the chat showed no card. herdr's own alt+↑ binding also keeps the keyboard from opening the queue.

    • The collapsed queue now shows as a card. The screen gives only a count, so the question and its options come from the pane's rollout: the newest unanswered questions (a skip leaves no record), read incrementally.
    • Codex keeps working while its questions wait, so only the card answers them, with its option buttons or its own text field. While the queue is collapsed (queued: "collapsed"), the message box keeps talking to Codex. While it is open in the terminal (queued: "open"), it holds the input, so the chat sends nothing and says why. The chat never leaves the queue open itself.
    • Answering sends alt+↑ to the pane to open the queue, but only while the questions' count is still on screen. It answers there only if the question shown is the card's; otherwise the queue stays open and the chat shows the question actually waiting. After the answer the queue closes again (alt+↓).
    • Open questions: 1 of 2 becomes the title, a wrapped title reads whole, an own answer is typed into the last row, and a free-form question with no options takes text alone.
    • The chat shows an answer as the text chosen, not the <send_user_message_question_reply> JSON that Codex sends the model.

Testing

  • bun run typecheck passes and bun test passes 281/281 (rebased onto main). Parser tests are built from screens captured live, including a question wrapped over seven lines, a cut-off tab bar, a file preview containing a rule, and a label wrapped over three lines.

  • End to end on a live Claude Code pane in a headless Chrome chat view:

    • Two questions: answered 2, then answered with a reply of my own, then the review submitted by click. Claude recorded both answers.
    • rm -rf junk approval:
      • a stray word is refused
      • a typed 1 waits for Confirm, and then Cancel sends nothing, so the directory stays
      • 4 then Confirm answers No
  • End to end on a live Codex 0.156.1 pane: two queued questions, one choice and one free-form. The collapsed queue showed as a "Question 1 of 2" card. 2 answered the choice, and a typed reply answered the free-form question. Codex recorded both answers ("YCB-V selected. Note: keep the logs"), and the chat shows only YCB-V and keep the logs.

Rebased onto main now that #5 is merged.

🤖 Generated with Claude Code

@devswha devswha left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. The stale-prompt guard (re-read the screen, 409 on an id mismatch) is solid. Two things before merge:

  1. Multi-question Claude prompts can answer the next question. claudeTabs (server/prompt.ts) only looks for the tab bar within 8 lines above the first option, and it requires the bar to end in Submit →. On a narrow pane the question wraps or the bar gets cut off, so tabbed comes out false. The answer then sends → + Enter, and that Enter picks option 1 of the next question. I reproduced this with a question that wraps to about 7 lines. Suggested fix: send only → and let the review card handle the submit step.
  2. Short chat replies approve prompts. While a card is showing, a composer message like "yes" or "1" is matched against the option labels and approves whatever is on screen, which could be an rm -rf approval. Please add an explicit confirm step for approval cards, or only treat text as an answer when the user taps an option.

Minor: a ──── line inside a file preview can confuse the approval title, and a label that wraps onto more than two lines is still cut off. Both fail safe. Please also add parser tests for wrapped questions and a cut-off tab bar.

@Yoonwoo-Ha
Yoonwoo-Ha force-pushed the feat/chat-answers-prompts branch from 8150887 to 9ef780b Compare September 23, 2026 08:08
@Yoonwoo-Ha

Copy link
Copy Markdown
Contributor Author

Thanks. Both points are fixed:

  1. Next question answered. A Claude multiple choice always ends with → alone. I checked on Claude Code 2.1.280 that with one question and with two, → leads to the next question or to the "Review your answers" step, which gets its own card with Submit. The tab bar is now found anywhere up the panel, whole or cut off, and a question wrapped over several lines is joined back together. There are new tests for a question wrapped over seven lines and for a cut-off bar.
  2. Short replies approving. A typed pick on an approval or a plan card now highlights the option and shows Send "1. Yes"? Confirm / Cancel. Nothing is sent until Confirm is tapped. Tapping an option in the card still sends immediately. I verified this live with rm -rf junk: a typed 1 followed by Cancel left the directory in place.

Minor: the approval title now comes from the rule under the ● tool line, not from a rule inside a file preview. A label wrapped over several lines is joined whole. Both have tests.

@devswha devswha left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Both earlier points are fixed: a Claude multiple choice now always ends with → alone (checked with a 9-line wrapped question and a missing, cut-off or whole tab bar), and typed picks on approval and plan cards wait for Confirm. The new Codex queued-question feature has the same kind of problem as the second point, though:

  1. Queued Codex questions take over the composer. Codex keeps working while questions sit in its queue, and busy is forced off whenever a card shows (PaneTerminal.tsx ~470–495). So a message meant for Codex, like "stop, don't touch prod", goes out at once as the typed answer to the queued question: alt+↑, the text, then Enter. There is no confirm step. For a question with no options, any text counts as the answer. And the user has no way left to message Codex normally from the chat. Please make typed text answer a queued question only when the user opts in, for example by tapping the card first, and send ordinary composer text as a normal message.
  2. Some typed picks still skip confirmation. Claude's "Review your answers" card is a question, so a typed 1 submits every answer at once (claude-submit). A Codex menu card behaves the same way. Please put submit-all and menu picks behind the same Confirm.
  3. alt+↑ can open the wrong queue. If Codex also has queued user messages, alt+↑ may open those instead of the questions (openQueuedQuestion). The 409 guard catches it, but only after the key has been sent.

Minor: parseClaudeApproval takes the first rule after the last ● line, and Claude also prefixes its own text with ●. A rule inside that text gives an rm -rf approval the title "Results table follows". It fails safe. Also, unansweredCodexQuestions reads up to 16MB synchronously on a poll.

@Yoonwoo-Ha
Yoonwoo-Ha force-pushed the feat/chat-answers-prompts branch from 6c8f0de to bab593f Compare September 24, 2026 12:14
@Yoonwoo-Ha

Copy link
Copy Markdown
Contributor Author

Thanks. Addressed in bab593f, on top of a rebase onto main (#11 and #18 are merged):

  1. Queued questions no longer take over the composer. The prompt carries queued: true. While it shows, the message box keeps "Message Codex…", and a typed message goes to Codex as a message. The card alone answers the question, with its option buttons or its own text field, and it says so. After an answer from the chat, the queue closes again (alt+↓), so the next question waits collapsed and the main prompt has the input back. An open queue (focused in the terminal) still reads as a normal prompt, since the TUI's input is there.
  2. Menus wait for Confirm. Claude's "Review your answers" is now kind menu, and a typed pick on any menu waits for Confirm, like approvals and plans. So do Codex's continue menus.
  3. alt+↑ and queued messages. I checked this on Codex 0.156.1. A queued message of the user's own replaces the questions' block ("Messages to be submitted after next tool call / ↳ …"), and then neither alt+↑ nor shift+← opens anything. When that message goes in, Codex drops the waiting question from the TUI, although the rollout keeps it unanswered. So the count line is gone in that state and no card shows. The handler also re-checks that the count is still on screen right before it sends the key.

Minor.

  • A Claude approval's panel is now found under a tool call (● Name(), not under Claude's own text that starts with ●. There is a test with a table rule in that text.
  • The queue scan reads asynchronously, and its first read is the last 4MB instead of 16MB.

Checked live on Codex 0.156.1:

  • The card showed while the box still read "Message Codex…".
  • A tap on "2. YCB-V" answered, and the TUI folded back to "? 1 question".
  • The free-form question was answered from the card.
  • With a question queued, a message from the box reached Codex as a normal user message.

bun test passes.

Yoonwoo-Ha added a commit to Yoonwoo-Ha/herdr-web-ui that referenced this pull request Sep 24, 2026
… queue

herdr reports Codex blocked while questions asked with
request_user_input_async wait collapsed above its main prompt, so
agent.prompt refused a message the TUI would take (Codex then drops the
questions). When agent.prompt answers agent_blocked and the screen shows
only that collapsed queue, the message goes through send_text and its own
Enter. An open question, whose "enter submit … skip" hint means it holds
the input, is still refused.

Found while checking this with devswha#6's queue cards on a live Codex 0.156.1
pane. A contract test runs a stand-in `codex` reported blocked that shows
the collapsed queue, and fails without this change.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Yoonwoo-Ha

Copy link
Copy Markdown
Contributor Author

Note on merge order: #6 and #15 both touch server/prompt.ts. #6 adds a codexHome option to handlePromptRequest, #15 adds serialize, and each has its own check for "Codex's question queue is collapsed". Whichever you merge first, I'll rebase the other right after. If #6 goes first, #15 will use #6's queuedQuestionCount instead of its own codexQuestionsCollapsed, so the queue is detected in one place.

@devswha devswha left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. The three points are fixed: queued cards answer only from the card, menus wait for Confirm, and the count line is re-checked before alt+↑. Two new issues bring back the first problem:

  1. The queue can be left open, and then the composer answers again (prompt.ts ~700–707, PaneTerminal.tsx ~472–488). If openQueuedQuestion finds a different question, or nothing within 2s, it returns 409 and leaves the queue open. The next poll shows a codex-async-question without queued, so the composer takes over again, and "stop, don't touch prod" goes out as the free-form answer while Codex keeps working. Please send alt+↓ on the 409 path. Also close an open queue before any composer send, or treat an open queue as queued as well.
  2. Concurrent polls corrupt the async scan cache (codex.ts ~210–253). Two calls share one scan object across the await, so both push the same questions and both add to size. Reproduced: ["First?","Second?","Second?"]. With a count of 2, slice(-count) gives [Second, Second], so the card shows the wrong question, the answer gets a 409, and the queue stays open (see 1). Two viewers, or a poll overlapping an answer, are enough to trigger it. Please share one in-flight scan per file, or build the result locally and commit it once.

Lower priority:

  • Composer sends go straight to the terminal and aren't ordered with a card answer. A send between alt+↑ and the answer keys, or between the next question opening and alt+↓, becomes that question's answer. This can share #15's per-pane queue.
  • closeQueue can read a stale screen at 100ms and send alt+↓ after Codex has returned to its main prompt or put up an approval. Please check what alt+↓ does there.
  • MCP calls (● srv - tool (MCP)() don't match the new approval regex. It still fails safe.

Yoonwoo-Ha added a commit to Yoonwoo-Ha/herdr-web-ui that referenced this pull request Sep 24, 2026
… queue

herdr reports Codex blocked while questions asked with
request_user_input_async wait collapsed above its main prompt, so
agent.prompt refused a message the TUI would take (Codex then drops the
questions). When agent.prompt answers agent_blocked and the screen shows
only that collapsed queue, the message goes through send_text and its own
Enter. An open question, whose "enter submit … skip" hint means it holds
the input, is still refused.

Found while checking this with devswha#6's queue cards on a live Codex 0.156.1
pane. A contract test runs a stand-in `codex` reported blocked that shows
the collapsed queue, and fails without this change.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Yoonwoo-Ha
Yoonwoo-Ha force-pushed the feat/chat-answers-prompts branch from bab593f to 44973b0 Compare September 24, 2026 14:31
@Yoonwoo-Ha

Copy link
Copy Markdown
Contributor Author

Thanks. Addressed in 44973b0, rebased onto main (#16, #17 and #19 are merged):

  1. The queue is never left open by the chat. If it opens on another question or doesn't open within 2s, it is closed again (alt+↓), and the question it did open on is remembered. The next card then shows that question instead of the newest-first guess. An open queue is also card-only now (queued: "open"): while it shows, the composer sends nothing and says why ("Codex has a question open in the terminal: answer it above, or close it there (alt+↓)"). A collapsed queue still leaves the composer talking to Codex.
  2. Scan cache. Concurrent calls for one rollout now share a single in-flight scan, and the scan is built on a copy and kept only once complete. The test with two simultaneous polls fails with the old scanner.

Lower priority.

  • Ordering with composer sends: that needs Submit a composer message with its own Enter, after the text #15's per-pane queue. With both in, handlePromptRequest runs the whole answer (open, keys, close) in the pane's turn, so a submit waits behind it. That is how my local build runs.
  • closeQueue: it now waits for a different question to show, so the stale screen of the question just answered is ignored. I also checked alt+↓ on Codex 0.156.1 in the other states: on the main prompt (with a draft) and on a command approval it changes nothing.
  • MCP: ● server - tool (MCP)(…) now anchors the panel, with a test.

Checked live on Codex 0.156.1:

  • With the queue opened in the terminal, a chat message was held with the reason, and nothing reached the question.
  • With the second of two questions skipped, tapping the guess answered nothing, the queue closed, and the next card showed the right question, which the card then answered.
  • The earlier flows (tap, free-form, the message box going to Codex) still pass.

bun test passes.

@devswha devswha left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Both fixes check out. With a fake Codex pane that swaps screens as keys arrive, a wrong question now gives a 409, alt+↓ and the remembered question on the next card. A late-opening queue becomes queued:"open", card-only. The two-poll test fails on the old scanner. #6 on top of #15 passes 306 tests, and the ordering holds: alt+up, enter, alt+down, SUBMIT.

#15 and #20 are now merged, so please rebase onto main. The resolution needs care:

  1. Put the whole answer flow inside #15's serialize: read, open the queue, keys, close the queue. A naive resolution leaves open and close outside the pane's turn, and then a composer submit can land between them, which is the ordering this depends on.
  2. Composer.tsx: after the merge, both PRs declare const [sending, setSending], and typecheck fails until one is removed.
  3. server/codex.test.ts: the node:fs import line conflicts with #20 (appendFileSync vs renameSync). Keep both.

Lower priority, fine as a follow-up:

  • "Send now" skips the open-queue check. PaneTerminal.tsx ~628 calls sendComposerText directly, and the refusal lives only in composerSend. With #15 merged the server refuses it, but please route it through the same check.
  • Remembered question with a repeated title. The card matches the remembered question by title only (prompt.ts ~340), taking the oldest match. If an earlier skipped question has the same title but different options, every tap gives a 409 plus alt+↑/alt+↓. It fails safe, but the chat can never answer it. Please match on the options too, and clear the remembered question when the rollout changes.
  • Consecutive questions with the same title. closeQueue takes the next one for the question just answered and leaves the queue open. Fails safe.

Yoonwoo-Ha and others added 7 commits September 25, 2026 01:24
Checked against live Claude Code 2.1.280 and Codex 0.156.0 panes, several
prompts left the chat at INPUT with no card:

- Claude with several questions: the hint reads "Tab/Arrow keys to
  navigate" and the answers are reviewed before sending. Each question is now
  a card titled with its tab ("Route · 1 of 2"), the review is its own card,
  and a multiple choice among several questions moves on with → instead of
  → enter, which picked the next question's first option.
- Claude tool approvals no longer print "This command requires approval" or
  "Dangerous rm operation": the panel under a rule opens with the tool ("Bash
  command", "Create file") and asks "Do you want to …?". Options end at the key
  hint, so a label wrapped by a narrow pane reads whole.
- Codex's last question submits them all ("enter to submit all"), and its
  folder trust now asks "Trust this folder?" with "enter continue · esc back".

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
While a prompt card shows, a message from the composer answers it instead
of being typed into the agent's menu: an option's number as the agent shows
it, its label, or the letter it binds ("y"); several numbers for a multiple
choice. Anything else is the prompt's own reply when it takes one; a prompt
that takes only its options keeps the text and says how to choose. The
placeholder tells which, the card numbers its options, and a message is
never queued behind a prompt.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Review follow-ups:

- A Claude multiple choice ended with → + enter when its question looked
  like the only one. With a long question wrapping the tab bar out of reach,
  or a narrow pane cutting the bar off, that enter picked the next question's
  first option. It now always ends with → alone: Claude moves on to the next
  question, or to the review of the answers, which has its own card (checked
  live on Claude Code 2.1.280 with one and with two questions).
- The tab bar is looked for up the panel however far the question wraps,
  and a bar cut off at the pane's edge still counts; the question's wrapped
  lines are joined back into one.
- A typed "1" or "yes" no longer answers an approval or a plan by itself: the
  card highlights the pick and asks to Confirm or Cancel. A tap on an option
  in the card, and a question's typed answer, still send at once.
- An approval's title comes from the panel under the tool call, not from a
  rule inside a file preview, and an option wrapped over several lines reads
  whole.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Codex 0.156 asks with request_user_input_async without stopping: the
questions wait in a queue above the main prompt, collapsed to "? 2
questions / alt+↑ to answer". The chat saw no prompt there, and herdr's own
alt+↑ binding keeps the keyboard from opening it (shift+← does).

- The collapsed queue now shows as a card. The screen gives only a count,
  so the questions come from the pane's rollout: the newest unanswered
  request_user_input_async questions (a skip leaves no record), read
  incrementally from what the rollout appends.
- Answering it opens the queue (alt+↑ sent to the pane) and answers there
  only if the question shown is the card's (title and options, compared
  across wrapping). Otherwise the queue stays open and the chat re-reads
  the question actually waiting.
- An open question: the title may wrap, "1 of 2" becomes the card's title,
  the last row takes a typed answer (it is typed into directly, no enter),
  and a free-form question with no options is answered with text alone.
- The chat shows an answer as the text chosen, not the
  <send_user_message_question_reply> JSON Codex sends the model, and the
  question tool's summary lists the questions asked.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…enu picks

Review follow-ups:

- A question in Codex's queue no longer takes over the composer. Codex
  keeps working while it waits, so a message like "stop, don't touch
  prod" goes to Codex as a message, and the card alone answers the
  question (its option buttons, or its own text field). The prompt carries
  `queued: true`, and the card says so. After an answer from the chat,
  the queue closes again (alt+↓), so the next question waits collapsed and
  the main prompt has the input back.
- A typed pick on a menu now waits for Confirm, like approvals and plans.
  Claude's "Review your answers" (now kind "menu") would submit every
  answer at once, and Codex's continue menus act at once.
- alt+↑ is sent only while the screen still shows the questions' count.
  On Codex 0.156.1 a queued message of the user's own replaces the
  questions' block (and alt+↑ or shift+← then open nothing), so no card
  shows then.
- A Claude approval's panel is found under the tool call (`● Name(`), not
  under Claude's own text, which opens with ● too. A rule in its table no
  longer titles an rm -rf approval.
- The rollout scan for queued questions reads without blocking and starts
  with the last 4MB rather than 16MB.

Checked live on Codex 0.156.1: the card shows while the message box
still reads "Message Codex…". A tap on an option answered it and the
queue folded back to "? 1 question". The free-form answer was sent from
the card. With a question queued, a message from the box reached Codex
as a message.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…a time

Review follow-ups:

- The queue is never left open by the chat. When it opens on another
  question than the card's, or does not open within 2s, it is closed
  again (alt+↓), and the question it opened on is remembered, so the next
  card shows that one instead of the rollout's newest-first guess (a
  skipped question leaves that guess wrong). After an answer, the queue
  closes only once a different question shows, never on the stale screen
  of the one just answered. Checked on Codex 0.156.1: alt+↓ on the main
  prompt or an approval changes nothing.
- An open queue is card-only too (`queued: "open"`). It holds the
  terminal's input, so the composer sends nothing while it shows and
  says why, rather than typing the message into the question. A
  collapsed queue (`queued: "collapsed"`) still leaves the composer
  talking to Codex.
- Concurrent polls of one rollout share a single scan, and the scan is
  built on a copy and kept only once complete. Two viewers, or a poll
  overlapping an answer, no longer push the same questions twice. There is
  a test that fails with the old scanner.
- An MCP call ("● server - tool (MCP)(…)") anchors a Claude approval's
  panel like any tool call.
- Text typed in the composer while a message was on its way stays: only
  what was sent leaves the box (as in devswha#15).

Checked live on Codex 0.156.1:
- With the queue opened in the terminal, a chat message was held with the
  reason and nothing reached the question. The card answered it.
- With the second of two questions skipped, the card's guess (Beta?)
  answered nothing, the queue closed, and the next card showed Alpha?,
  which the card then answered.
- The earlier flows (tap, free-form, message box to Codex) still pass.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Rebased onto main (devswha#15 and devswha#20 merged). The whole answer runs inside
devswha#15's per-pane serialize: read, open the queue, keys, close. A composer
submit can't land between them.

Review follow-ups:
- "Send now" on a held message goes through the same check as the
  composer. It is disabled, with the reason as its title, while Codex's
  queue is open in the terminal.
- The remembered question (the one the queue really opened on) is
  matched by title and options, the newest such one. It is dropped when
  the pane's rollout changes. An older skipped question with the same
  title no longer takes its place.
- closeQueue tells the question just answered from its twin by prompt id,
  and after 600ms treats an identical question still open as the next one
  (Codex takes the Enter at once). Two questions with the same title and
  options no longer leave the queue open.

Checked live on Codex 0.156.1: two identical queued questions, each
answered from its card, with the queue folded back to "? 1 question" in
between. The earlier queue flows still pass.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Yoonwoo-Ha
Yoonwoo-Ha force-pushed the feat/chat-answers-prompts branch from 44973b0 to e44244d Compare September 24, 2026 16:29
@Yoonwoo-Ha

Copy link
Copy Markdown
Contributor Author

Thanks. Rebased onto main (#15, #20 and #22 are merged), resolved as you described:

  1. Serialize. The whole answer runs inside Submit a composer message with its own Enter, after the text #15's serialize: read, open the queue, keys, close.
  2. Composer.tsx. A single sending declaration. The settle code is main's own, so Answer agent prompts from the chat, and recognize Claude Code 2.1 / Codex 0.156 prompts #6 no longer touches it.
  3. codex.test.ts. The node:fs import keeps both appendFileSync and renameSync.

The lower-priority items are in e44244d:

  • "Send now" goes through the same open-queue check as the composer. It is disabled, with the reason as its title, while the queue is open in the terminal.
  • Remembered question. It is matched by title and options, taking the newest match, and it is dropped when the pane's rollout changes. There is a unit case where an older skipped question has the same title but different options.
  • Twin questions. closeQueue compares by prompt id. After 600ms, an identical question still open is taken as the next one, since Codex takes the Enter at once.

Checked live on Codex 0.156.1, on top of #15 and #20 (the same build as main plus this branch):

  • Two identical queued questions were answered from their cards, and the queue folded back to "? 1 question" in between.
  • The open-queue hold, the wrong-guess recovery, tap and free-form answers, and the message box going to Codex all still pass.

bun test: 308 pass.

The prompt card numbers its options ("1. Yes, continue") so a typed "1"
in the composer can be matched to a card button. The UI regression still
looked for the bare label and timed out on the startup-prompt step.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Yoonwoo-Ha

Copy link
Copy Markdown
Contributor Author

Pushed 098e959: scripts/ui-regression.ts still looked for the bare "Yes, continue" button on the startup prompt, but this PR numbers the card's options ("1. Yes, continue"). That step timed out. With the fix, the UI regression passes all 7 steps on this branch merged onto current main (0.3.4). bun test passes too (318).

@devswha devswha left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this is close. All three earlier asks are done:

  • The whole answer runs inside serialize: read, open the queue, keys, close.
  • Composer.tsx has a single sending declaration and main's settle code.
  • The codex.test.ts imports are fixed.

I also merged it into current main (0.3.4). There are no conflicts, bun test passes 316/316, and scripts/ui-regression.ts passes 7/7. I found no path that types into the wrong pane or answers a stale prompt without a tap.

Two small fixes before merge:

1. A typed pick waiting for Confirm outlives its prompt.

pendingAnswer (PaneTerminal.tsx:109) is cleared only by these:

  • Confirm or Cancel
  • a later composer send that answers directly (:501)

Three things leave it set:

  • a tap on an option in the card
  • answering in the terminal
  • the prompt going away

The card shows it again whenever pendingAnswer.promptId === prompt.id, and that id is a hash of the prompt's content.

Scenario:

  1. The user types 1 on a Claude approval for rm -rf build, and "Send 1. Yes? Confirm" appears.
  2. They approve in the terminal instead.
  3. Later Claude asks for the same command again.
  4. The new card opens with "Send 1. Yes?" already in place, and the composer says "Confirm your answer in the card above". One tap approves.

It still needs a tap, but it's an easy accidental approval. Please drop pendingAnswer whenever the pane's prompt changes or goes away, in onChatPrompt for example, and after any successful answer.

2. An invalid answer can leave Codex's queue open.

In handlePromptRequest (server/prompt.ts ~808–829), a codex-queued-question card sends alt+↑ (openQueuedQuestion) first and validates the answer afterwards. If answerKeys throws, the request returns 400 without alt+↓. The same happens if a key send throws partway through. It fails safe, because the next poll sees queued: "open" and submits are refused. Still, it breaks the PR's own promise that the chat never leaves the queue open.

Please validate the answer against the collapsed prompt before opening the queue, or close the queue in a try/finally once it has been opened.

Nits, optional:

  • codexQuestionsCollapsed (main, used by submitText) and the new queuedQuestionCount still both detect a collapsed queue. If they disagree, the card and the server's send refusal tell different stories.
  • closeQueue (~705) and queuedPrompt (~182) each have two JSDoc blocks stacked.

Yoonwoo-Ha and others added 2 commits September 25, 2026 16:02
A pick typed in the composer ("1") waits in the card for Confirm. It was
cleared only by Confirm, Cancel or a later direct answer. Answered some
other way (a tap in the card, or the terminal), it stayed, and the same
prompt asked again (same content, so same id) opened with "Send 1. Yes?"
already waiting one tap from Confirm.

It is now dropped when:
- the pane's prompt changes or goes away,
- the card answers,
- the agent goes back to work (the same prompt asked again between two
  reads),
- the page is hidden (prompts are not read then).

The UI regression types "1" on the startup menu, answers in the terminal,
paints the same menu again and checks no Confirm waits on it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…detector

- An answer to a collapsed-queue card is checked against the card before
  alt+up opens the queue, so an invalid one returns 400 with the queue
  still closed. Once this request has opened the queue, a failure on the
  way (invalid keys for the opened question, a key send that throws) closes
  it again in a finally.
- codexQuestionsCollapsed (the send path's fallback) now reads the same
  queuedQuestionCount as the card. That count requires the "to answer"
  hint with the main prompt right under it, so the card and the send
  refusal cannot disagree.
- One JSDoc block each for closeQueue and queuedPrompt.

Checked on a live Codex 0.157.0 pane, with a question queued while the
turn runs:
- before: three invalid answers each returned 400 and left the queue open
- after: all three returned 400 and '? 1 question' stayed collapsed; a
  valid answer was recorded as the question's reply

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Yoonwoo-Ha

Copy link
Copy Markdown
Contributor Author

Thanks. Both fixes and both nits are in 52c0bab and e8e30e0.

1. A typed pick outlives its prompt (52c0bab). pendingAnswer is now dropped when:

  • the pane's prompt changes or goes away (in onChatPrompt, as you suggested),
  • the card answers (onAnswered),
  • the agent goes back to working. This covers the same prompt being asked again between two reads.
  • the page is hidden. Prompts are not read then, so the prompt could be answered and asked again unseen.

scripts/ui-regression.ts has a new step for your scenario: type 1 on the startup menu, answer in the terminal, paint the same menu again, and check that no Confirm waits on it. It fails without the fix (1 Confirm row) and passes with it.

2. An invalid answer leaves the queue open (e8e30e0). The answer is checked against the collapsed card before alt+↑. Once the request has opened the queue, any later failure (keys for the opened question, a key send that throws) closes it again in a finally. On a live Codex pane with a question queued while the turn ran, I sent {option_index: 99}, {option_indices: [0]} and {option_index: 0, custom_text}:

  • before: each returned 400 and left the queue open
  • after: each returned 400 and ? 1 question stayed collapsed
  • a valid answer afterwards was recorded as the question's reply

Nits:

  • codexQuestionsCollapsed is now parsePrompt === null && queuedQuestionCount > 0. The single count requires the "to answer" hint with the main prompt right under it, as the send path did. A new test checks that the card and the send path agree on five screens. It fails on the old pair.
  • closeQueue and queuedPrompt each have one JSDoc block now.

bun test 309/309, ui-regression 8/8.

One thing I noticed on the way: Codex 0.157.0 changed async questions. The question also shows as an agent message, and its queue is dropped when the turn ends, leaving no record. On 0.157 the collapsed card only appears while Codex is still working. After that, the question is simply gone and a normal message answers it. The live check above ran on 0.157.0. Answering from the card during the turn works, and Codex takes the reply at its next step.

Yoonwoo-Ha and others added 2 commits September 25, 2026 16:05
…mpts

Conflicts:
- server/prompt.ts: codexQuestionsCollapsed reads queuedQuestionCount now;
  main's "not a numbered menu row" check (devswha#23) moves into that one count.
- src/components/ChatView.tsx: main's memo wrapper, with this branch's
  prompt props.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Yoonwoo-Ha

Copy link
Copy Markdown
Contributor Author

Merged current main (0.3.6) into the branch (1d48e5c). There were two conflicts. In server/prompt.ts, main's "not a numbered menu row" check from #23 now lives in the one queuedQuestionCount, and 797ed70 adds that case to the agreement test. In ChatView.tsx, I kept main's memo wrapper and added this branch's prompt props. The props passed from PaneTerminal are stable callbacks and the same pendingAnswer object between changes. bun test 327/327, ui-regression 8/8, and the live Codex invalid-answer check passes again on the merged branch.

@devswha devswha left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, both fixes are exactly right. A typed pick is dropped whenever its prompt changes, is answered, the agent goes back to work or the page is hidden, and the new ui-regression step covers the scenario. An answer to a queued card is now checked before alt+↑, and the finally closes a queue this request opened. I merged this together with #40–#42 onto current main: bun test passes 330/330, test:ui passes 8/8 and chat-browser-qa passes 5/5. I'll add the CHANGELOG entry in the release commit.

@devswha
devswha merged commit 7ef1bcd into devswha:main Sep 25, 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