---
title: "How to Tell When Claude Code, Codex or OpenCode Is Waiting for You"
description: "Which Claude Code, Codex, OpenCode and Hermes hooks mean the agent is waiting for you, which to ignore, and how to write a hook that costs the agent little."
author: "Foxl Team"
date_published: "2026-10-23"
date_modified: "2026-10-23"
canonical_url: "https://foxl.ai/en/blog/coding-agent-waiting-for-input"
markdown_url: "https://foxl.ai/en/blog/coding-agent-waiting-for-input/index.md"
image: "https://foxl.ai/blog/coding-agent-waiting-for-input-cover.png"
social_image: "https://foxl.ai/blog/social/coding-agent-waiting-for-input.png"
content_type: "Deep dive"
topics: ["Claude Code","Codex","Hooks","Coding agents","Notifications"]
products: ["Foxl Code"]
---

<!-- Generated by scripts/blog-publishing.mjs; do not edit. -->

# 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.

- Author: Foxl Team
- Published: 2026-10-23
- Last reviewed: 2026-10-23
- Reading time: 12 minutes
- Canonical HTML: [https://foxl.ai/en/blog/coding-agent-waiting-for-input](<https://foxl.ai/en/blog/coding-agent-waiting-for-input>)

![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.](<https://foxl.ai/blog/coding-agent-waiting-for-input-cover.png>)

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.

| hook | what it means | we treat it as |
| --- | --- | --- |
| `PreToolUse`, matcher `AskUserQuestion` | the question dialog is about to open; `tool_input` carries the questions and their options | a question, with its choices |
| `Notification`, matcher `permission_prompt` | a permission dialog has been open for about six seconds with no keystroke (read from the package) | a permission request |
| `Stop` | the reply is finished; `last_assistant_message` is its text | a question if the text ends with `?`, otherwise the end of a turn |
| `UserPromptSubmit`, `PostToolUse`, `PostToolUseFailure`, `SessionEnd` | the person typed, the approved tool ran or failed, the CLI exited | answered |
| `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) | asks | answered | not reported |
| --- | --- | --- | --- |
| Codex 0.162.1, a system-level `hooks.json` | `PermissionRequest`; `Stop` whose `last_assistant_message` ends with `?` | `UserPromptSubmit`, `PostToolUse`, `Interrupt`, `SessionEnd` | the Plan mode question dialog |
| OpenCode 1.19.0, a server plugin | `question.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 plugin | `pre_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.1 | nothing | - | 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'.](<https://foxl.ai/blog/coding-agent-waiting-for-input-needs-you.webp>)

_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.](<https://foxl.ai/blog/coding-agent-waiting-for-input-flow.png>)

_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](<https://foxl.ai/blog/a-citation-is-not-a-measurement>) covers how typing reaches a remote agent at all.

## References and further reading

1. [Claude Code: Hooks reference](<https://code.claude.com/docs/en/hooks>) - Documentation
2. [Codex: Hooks](<https://learn.chatgpt.com/docs/hooks>) - Documentation
3. [OpenCode: Plugins](<https://opencode.ai/docs/plugins/>) - Documentation
4. [Hermes Agent (Nous Research)](<https://github.com/NousResearch/hermes-agent>) - Reference
5. [Foxl v0.9.3-beta.5 release](<https://github.com/foxl-ai/foxl/releases/tag/v0.9.3-beta.5>) - Release