Skip to content

codex: let the agent ask the user a question - #12

Merged
tdwd merged 1 commit into
mainfrom
codex-questions
Sep 18, 2026
Merged

tdwd merged 1 commit into
mainfrom
codex-questions

Conversation

@tdwd

@tdwd tdwd commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

item/tool/requestUserInput is codex's analogue of claude's AskUserQuestion.
It was answered with -32601, so codex could not ask the user anything — the
picker existed but nothing on that backend could reach it.

The request and response shapes come from ToolRequestUserInputParams /
ToolRequestUserInputResponse in the app-server generated schema (codex-cli
0.153.4), not from a probe. The method name and its params type were both
confirmed against ServerRequest.json.

What the two backends do differently

  • codex keys each answer by a question id, claude answers positionally. So
    the id rides out on askQuestion.ID and back on approvalReply.answers.
    claude has only the deny-message channel and ignores that field.
  • A codex question may offer no options, or be marked isSecret. The picker
    chooses between options and cannot take typed input, so those questions are
    left out of the answer map — which is how codex reads "unanswered", the same
    as a dismissed question. A line says why, because a question that never
    appears reads as a bug in the pane rather than a limit of it.
  • isOther is narrowed away for the same reason, matching what the picker
    already does with claude's questions. isBlocking is deliberately unread:
    answering is correct either way, and it is not answering that stops a turn.

One refactor

Every path to the approval pane now goes through codexEngine.ask. It owns the
two rules that hang a turn when one is missed — the wait runs off the reader
goroutine, and one request holds the pane at a time. askUser had both inline,
so a third request kind was free to duplicate them or forget them.

Tests

Five new tests. Three were confirmed discriminating by actually reverting the
code they cover:

  • removing the routing case → TestCodexQuestionRoundTrip times out with no
    answer on the wire, which is what a hung turn looks like;
  • dropping answers: from the reply → TestAnsweringSendsBothFormsOfTheAnswer
    fails with {"answers":{}};
  • moving the build-mode short-circuit above the question check →
    TestCodexQuestionIsAskedEvenInBuildMode fails.

go build, go vet and the full suite are green.

Not verified: this has not been driven against the live codex CLI. Getting
a real agent to ask a question on demand needs a turn, and plan item 1.7 (a
human at a terminal) is still open.

item/tool/requestUserInput is codex's analogue of AskUserQuestion. It was
answered with -32601, so codex could not ask anything.

The shape comes from ToolRequestUserInputParams in the generated schema, not
from a probe. Two differences from claude decide the code. codex keys each
answer by a question id rather than by position, so the id rides out on the
question and back on the reply; claude has only the deny-message channel and
ignores that field. And a codex question may offer no options, or be marked
secret. The picker chooses between options and cannot take typed input, so
those are left out of the answer map, which codex reads as unanswered.

Every path to the pane now goes through codexEngine.ask, which owns the two
rules that hang a turn when one is missed: the wait runs off the reader
goroutine, and one request holds the pane at a time. askUser duplicated both,
and a third request kind was free to duplicate them again or forget them.
@tdwd
tdwd merged commit cee6cfe into main Sep 18, 2026
1 check passed
@tdwd
tdwd deleted the codex-questions branch September 18, 2026 13:49
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