Skip to main contentSkip to content
Blog
Deep diveClaude Code / Codex / Hooks / Coding agents / Notifications

How to Tell When Claude Code, Codex or OpenCode Is Waiting for You

Claude Code tells you it is waiting through three hooks: PreToolUse on AskUserQuestion, a permission_prompt Notification, and a Stop whose last message ends with a question. Ignore idle_prompt, which arrives after any reply nobody answers. Here is the map Foxl Code uses for remote Claude Code, Codex, OpenCode and Hermes terminals, what each CLI cannot report, and how to write a hook that costs the agent little and never talks to the model by accident.

Foxl Team12 min read

A hand-drawn terminal window whose cursor sits under a question mark; a line from the window ends in a hook that has caught a phone showing a bell.
On this page
Reviewed against the shipped implementation and retained tests on October 23, 2026.

Claude Code's hooks run a command you configure when a given event happens, and three of them tell you that it is waiting for you. A PreToolUse hook on the AskUserQuestion tool fires when it opens its question dialog. A Notification hook of type permission_prompt fires when a permission dialog has been open for a few seconds. A Stop hook fires at the end of every reply and carries the reply's text in last_assistant_message, so you can check whether it ended with a question. Ignore idle_prompt: it arrives about a minute after any reply nobody has typed after, not only after a question.

Foxl Code, which runs coding agents in remote terminals, uses exactly these three to mark a remote Claude Code terminal as waiting on its user. Since v0.9.3-beta.5 the same reporting command also reads Codex, OpenCode and Hermes through their own hooks and plugins. Below is the map for each CLI, the traps each one has, and how to write the hook so that it costs the agent as little as possible and never leaks text into the model's context. No live cloud terminal has reported a prompt yet; the last section says what is still unverified.

Why can't you see that an agent in a terminal is waiting?

A person running agents in remote terminals can have several open at once and look at none of them. On 2026-10-21, a remote Claude Code terminal asked a yes or no question and sat at its prompt while Foxl's own API said that no task was waiting on anyone. Foxl Code's sidebar, its "Needs you" list of tasks waiting on a person, and the phone app all stayed quiet. Before this change, nothing in Foxl Code listened for that moment.

Reading the screen looks like the fix, but it breaks easily. To an outside reader, the thinking, finished and asking screens differ by a few characters of the CLI's own drawing, and any release can redraw them. When we once upgraded the CLI versions we ship, the new builds drew a different first screen from the one our input check waited for, and remote Codex and OpenCode terminals silently ignored typing for ten days. A hook is the CLI's own statement of what just happened, with a documented payload, so that is what we listen to.

Which Claude Code hooks mean "I am waiting for you"?

Our cloud images pin Claude Code 2.1.296. Where a detail below is marked as read from the package, it comes from that version's npm package, not from a running terminal.

hookwhat it meanswe treat it as
PreToolUse, matcher AskUserQuestionthe question dialog is about to open; tool_input carries the questions and their optionsa question, with its choices
Notification, matcher permission_prompta permission dialog has been open for about six seconds with no keystroke (read from the package)a permission request
Stopthe reply is finished; last_assistant_message is its texta question if the text ends with ?, otherwise the end of a turn
UserPromptSubmit, PostToolUse, PostToolUseFailure, SessionEndthe person typed, the approved tool ran or failed, the CLI exitedanswered
Notification, type idle_prompt"Claude finished responding about 60 seconds ago and you haven't typed since"ignored

The idle_prompt row is where most setups go wrong. Its message reads like exactly what you want, but it follows any reply nobody typed after, so treating it as a question raises an alert after nearly every turn (in Foxl's case, a "Needs you" entry and a phone notification). We register the Notification hook with the permission_prompt matcher only, and the script checks the type again. For the Stop test, we strip trailing whitespace, Markdown emphasis, backticks, closing brackets and quotes before looking, and accept the full-width ? that CJK input methods type.

Claude Code's current documentation also lists a dedicated PermissionRequest event and more notification types than these. We did not check whether 2.1.296 has the newer event. permission_prompt is still in the current list, and its six-second wait filters out a dialog that someone answers at the screen. Add the hooks to Claude Code's user-level settings file, after any hooks already configured for the same event, so existing hooks keep running:

{
  "hooks": {
    "PreToolUse": [{ "matcher": "AskUserQuestion",
      "hooks": [{ "type": "command", "command": "report-prompt", "timeout": 10 }] }],
    "Notification": [{ "matcher": "permission_prompt",
      "hooks": [{ "type": "command", "command": "report-prompt", "timeout": 10 }] }],
    "Stop": [{ "hooks": [{ "type": "command", "command": "report-prompt", "timeout": 10 }] }]
  }
}

All of these call one small script, the report command (report-prompt above), which reads the task's credential from its environment. The events that mean the person answered, UserPromptSubmit and the rest (the close events), are registered the same way, behind a guard described below.

How do you write a hook that costs the agent as little as possible?

A reporting hook runs inside the agent's own loop, so every mistake in it is the agent's problem. We ended up with five rules, each taken from the documentation or from the 2.1.296 package:

  • Print nothing on stdout. For most events Claude Code only logs a hook's stdout, but for UserPromptSubmit and a few others "Claude Code adds plain-text stdout as context that Claude can see and act on." A status line from a reporting script would become part of the prompt.
  • Always exit 0. Exit 2 blocks the action on events that can block, and any other non-zero code shows an error notice. In our script, a network failure costs one line on stderr and nothing else.
  • Run synchronously, with a short timeout. Hooks can run with "async": true, but the pinned build prints a line in the terminal each time an async hook finishes, which becomes noise when hooks run after every tool call. Our hooks are synchronous with timeout: 10, and the script gives its request three seconds and exits on its own a moment later. On Claude Code the real cost is one Node.js start and one request at the end of each reply, at most about three seconds when Foxl's server is unreachable.
  • Gate the frequent events in the shell. PostToolUse fires on every tool call. The report command creates a marker file when it reports an open prompt. The close hooks start Node.js only when that file exists, and otherwise drain stdin so the CLI never sees a broken pipe:
    if [ -e /tmp/foxl-agent-prompt/open ]; then exec report-prompt; else cat >/dev/null; fi
    While nothing is open, that is one test -e per tool call.
  • Keep the credential out of the command line. The hook inherits the CLI's environment, so the token is read from an environment variable and never appears in ps.

How do Codex, OpenCode and Hermes say it?

Each CLI has its own extension point; we read each one from the release our images pin. Codex has a hooks engine that accepts Claude Code's hook format. OpenCode and Hermes (Nous Research's agent CLI) run no shell hooks in their terminal UI; they load plugins, and our plugins hand the same report command a normalized event. Kiro, AWS's coding CLI, has no event to listen to.

CLI (pin)asksanswerednot reported
Codex 0.162.1, a system-level hooks.jsonPermissionRequest; Stop whose last_assistant_message ends with ?UserPromptSubmit, PostToolUse, Interrupt, SessionEndthe Plan mode question dialog
OpenCode 1.19.0, a server pluginquestion.asked, permission.asked; session.status idle with a reply ending in ?question.replied or rejected, permission.replied, session.status busy-
Hermes 0.19.0, a Python pluginpre_tool_call for clarify, pre_approval_request, post_llm_call ending in ?post_tool_call for clarify, post_approval_response, pre_llm_call, an interrupted on_session_end-
Kiro 2.29.1nothing-everything: no question or approval event, and its Stop carries no reply text

Codex. Codex reviews hooks from your own config before running them: new or changed hooks "are marked for review and skipped until trusted." A managed hook, set in the system-level config, is trusted by policy, so we install ours that way. The report command itself is installed only in terminals where Foxl Code asked for prompt reports. Measured on 0.162.1, the non-interactive codex exec also runs managed hooks and prints two lines about them per tool call into its output, so we turn hooks off for the runs Foxl Code starts with codex exec. Read from the 0.162.1 source, Codex's terminal UI displays any hook that runs longer than 300 ms, so the report command decides whether there is a prompt, writes its marker file, and hands the network request to a detached copy of itself.

Codex's Plan mode question dialog is deliberately not reported, because its PreToolUse hook fires before Codex decides whether the question is allowed, and outside Plan mode Codex refuses it. Measured on 0.162.1, the hook fired with the question and then Codex answered "request_user_input is unavailable in Default mode". Foxl Code's terminals start in Default mode, so reporting that hook would push to your phone a question nobody was ever asked. The trade-off is that a Codex terminal in Plan mode does not get its question dialog into "Needs you".

OpenCode. Our plugin listens to OpenCode's event bus. The question events are not in the event list of OpenCode's plugin documentation; they are in the v1.19.0 schema source, which is what we read. An idle status ends a turn, and the turn counts as a question when its last reply ends with ?. While a question or permission dialog is open, status changes are ignored, so the agent going busy or idle around the dialog does not close it. Events from a subagent's session are skipped.

Hermes. Despite its name, Hermes' on_session_end fires at the end of every turn on 0.19.0, right after the reply. When our plugin treated it as a close, it withdrew the question the turn had just asked: 7 of 12 questions were closed at once, and in 1 of 12 the question reached the server after its own close, so it stayed open with nothing left to close it. Now only an interrupted session end counts as a close, and reports go out one at a time in the order Hermes raised them.

Kiro is not covered, because its hooks have no question or approval event, and its stop payload carries no reply text. A hook could neither open a prompt nor tell a question from an answer. The only signal is an internal session file in an unpublished format, and we chose not to parse it.

What happens once Foxl knows?

Every report goes to one endpoint on Foxl Code's server, authenticated with that task's own credential, and opens or closes an entry in the same "Needs you" list that holds the agents' other questions. The task is marked as waiting for your answer, as in the figure.

The Foxl Code Work page. In the Needs you column, one card reads 'Claude Code is asking' with a yes or no question about a job that fails on purpose and an answer box with Send. A second card reads 'Claude Code is asking' with a question and three choices listed, followed by 'Answer this in the terminal.' and a Show terminal button. In the In flight column, two tasks say 'Waiting for your answer'.
Figure 1. The Foxl Code Work page from the pull request's build, with sample questions. A question in a reply can be answered here; a dialog is answered in the terminal.

A question at the end of a reply can be answered from "Needs you" or the phone. Foxl types the answer into that task's terminal as one line, then sends the Enter key separately a second later, because a single chunk that includes the Enter is treated as a paste and never submitted. For a question or permission dialog, Foxl never guesses the keystrokes the terminal UI expects. Instead its card in "Needs you" lists the choices, says to answer in the terminal, and has a button that opens the terminal. Answering in the terminal closes the card through the same hooks.

The phone is notified only about prompts that can go unseen. A question or permission dialog still open 15 seconds after Foxl hears about it sends a notification, since a person watching the terminal answers within seconds. A Claude Code permission dialog is reported about six seconds after it opens, so its notification comes about 21 seconds in. A reply that ends with a question is shown in "Needs you" but not announced, since many replies end with one. The 2026-10-21 question was that kind, so it would now appear in "Needs you" and on the task, and would still not ring the phone.

A flow from left to right. Four CLIs, Claude Code, Codex, OpenCode and Hermes, each report through their own hook or plugin to one small command inside the agent's machine. The command opens or closes a prompt on Foxl Code's server with the task's credential. The server shows it in Needs you and on the task, and notifies the phone only if a dialog is still open 15 seconds after the report.
Figure 2. Four CLIs report through one command; the phone hears about a dialog only when nobody has answered it 15 seconds after the report.

What is still unverified?

In our tests, the real report command ran on each pinned CLI's payloads, the plugins ran under Node.js and Python on the pinned event formats, and the server endpoint ran in a local copy of Cloudflare's runtime. What has not happened yet:

  • A live question from a real cloud terminal. As of 2026-10-23 the production table holds no prompt from one, so the end-to-end check on a live terminal is still owed.
  • Claude Code running these hooks. The Claude Code details, including the six-second delay and the async-hook line, were read from the 2.1.296 package, not seen on a running terminal. We have a test that runs that build against these hooks; it has not been run yet.
  • The other CLIs' terminals. Several Codex, OpenCode and Hermes details were read from source, including that OpenCode delivers the question events under those exact names; only codex exec was run.

If you run agents in terminals of your own, the Claude Code hook table is the part to copy. Wire the three hooks to whatever reaches you, ignore idle_prompt, print nothing, exit 0, and wait a few seconds before you send an alert. For the terminal side of Foxl Code, our post on why every keystroke once went to the wrong machine covers how typing reaches a remote agent at all.

References and further reading

  1. Claude Code: Hooks referenceDocumentation
  2. Codex: HooksDocumentation
  3. OpenCode: PluginsDocumentation
  4. Hermes Agent (Nous Research)Reference
  5. Foxl v0.9.3-beta.5 releaseRelease