Claude Code hooks for done and waiting status

Updated 2026-09-12

Make completion a signal you can check

When you leave an agent working in a terminal, you need to distinguish a finished response from a pause and a request for permission. Those states can look similar if you only watch whether text is moving. SpeakCode records terminal activity in the right panel's Activity tab and gives each terminal a phase indicator: idle, working, waiting, or exited.

You can improve that signal in a few minutes by adding a completion hook and an attention hook to your agent configuration. The examples below use inline commands, so you do not need a helper script. If you already have hooks, merge these entries into your existing configuration rather than replacing unrelated settings. The Claude Code workspace overview gives context for using the resulting signals alongside your terminals.

Understand the three detection tiers

SpeakCode listens at three levels, from specific signals to a general estimate based on quiet output:

  1. Explicit markers. Your tool emits a private terminal marker when its completion or notification hook runs. This directly identifies a finished turn or a request for input.
  2. Existing terminal signals. SpeakCode also recognizes standard sequences a tool or your shell may already emit. These provide tool-specific cues without adding the private marker. These include shell command-end markers, notifications, and progress sequences.
  3. Quiet-output settling. SpeakCode watches the visible terminal screen. After changing output becomes quiet, it reports that the terminal has settled. This works without configuration but does not establish why the output stopped.

The always-on fallback polls every 600 milliseconds and treats a screen that holds still for approximately 1.2 seconds after a change as settled. While the screen keeps changing, the phase is working; after settling, it becomes waiting. A settled event is suppressed when an explicit or standard marker has already covered the same lull.

That fallback is approximate because silence is the measurement. A tool can pause before it finishes. Hooks make the distinction exact at the event boundary: the agent reports completion or a notification instead of SpeakCode inferring meaning from stillness. A finished response still does not mean its code is correct or its tests passed.

The OSC 6969 marker

The private signal uses OSC 6969. Its structure is an escape byte, a closing bracket, the channel number, a semicolon, a token, and a bell byte:

ESC ] 6969 ; <token> BEL

Use these two tokens for the hook configuration:

TokenMeaning
doneThe agent finished its turn
awaitThe agent wants your input

The exact shell commands are:

printf '\033]6969;done\007' > /dev/tty
printf '\033]6969;await\007' > /dev/tty

Here printf converts \033 to the ESC byte and \007 to the BEL byte. The completion aliases are stop and finished; the input aliases are input, permission, ask, and notify. Using done and await keeps your configuration consistent with the examples.

Configure Claude Code Stop and Notification

Add this complete hooks block to ~/.claude/settings.json. It is valid JSON, including the doubled backslashes needed inside each command string:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "printf '\\033]6969;done\\007' > /dev/tty"
          }
        ]
      }
    ],
    "Notification": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "printf '\\033]6969;await\\007' > /dev/tty"
          }
        ]
      }
    ]
  }
}

Stop runs when Claude finishes responding, and Notification signals that it is waiting on you, such as at a permission prompt. SpeakCode records these as completion and awaiting-input activity respectively. This configuration forwards every invocation of the configured Notification hook as await; it does not inspect the notification payload to classify it further.

Keep existing hooks that serve other purposes. If a hooks, Stop, or Notification entry is already present, add the relevant command entry to that structure instead of creating duplicate JSON keys. The two commands are independent: you can verify completion first and then verify attention handling with a real notification.

Why write to /dev/tty?

A hook's standard output may be captured or routed somewhere other than the terminal displaying your agent. Writing the marker to /dev/tty sends it to that terminal's pseudoterminal, regardless of where the hook's standard output goes. This is what lets SpeakCode associate the activity with the terminal card that emitted it.

Keep the redirection in both hook commands. Removing it changes the delivery route and can leave you with a hook that runs successfully but never reaches the terminal signal listener. Run manual checks from the terminal card you want to test so that its own terminal receives the bytes.

Configure the Codex notify equivalent

Add this top-level setting to ~/.codex/config.toml, preserving your other configuration:

# Codex runs notify on turn completion and passes a JSON argument.
# This command ignores that argument.
notify = ["bash", "-lc", 'printf "\033]6969;done\007" > /dev/tty']

The single-quoted TOML string is intentional. It preserves the backslashes until printf turns them into terminal control bytes. If you already have a notify setting, reconcile the existing notification behavior before replacing it; this setting runs one completion command.

Codex's notify fires on turn completion only, so this line produces done events, not await. You can use it in a Codex workspace to separate an explicitly completed turn from a quiet-output settle.

Verify the route, then verify the hooks

  1. In a SpeakCode terminal with an ordinary shell prompt, run the manual done command shown above. Open the right panel's Activity tab and check that completion activity appears for that terminal.
  2. Run the manual await command in the same shell. Check for awaiting-input activity. The terminal's phase chip is the coarse status view; the Activity tab is where you distinguish the event that caused your attention.
  3. Let Claude Code finish a small response with the hooks configured. Confirm completion activity without manually printing a marker.
  4. When Claude emits a real input notification, check that awaiting-input activity reaches the same terminal. For Codex, finish a turn and check the completion event from notify.

Manual emission verifies the terminal route and marker parsing. It does not prove that your agent loaded or invoked its hook. Conversely, a waiting chip alone is not proof of hook delivery because quiet-output settling can produce that phase too.

Existing signals and optional usage reporting

SpeakCode also recognizes OSC 133;D shell command-end markers, OSC 777;notify;title;body notifications, OSC 9 one-shot notifications, and OSC 9;4 progress signals. Progress updates affect phase only. If you want per-command shell markers, add this zsh prompt function:

# ~/.zshrc — emit OSC 133;D with the exit code before each prompt
precmd() { printf '\033]133;D;%s\007' "$?" }

That example defines precmd; account for any existing prompt function before adopting it. A shell command-end signal describes the shell command boundary, while the agent hooks describe turns within an interactive agent session.

Optional token-usage reporting uses a separate OSC 6970 marker with semicolon-separated model, input, output, cache-read, cache-creation, and session-ID fields. It carries cumulative session totals, so the latest value is the running total, and it produces usage activity. Those counts come from Claude's transcript; the inline hooks above emit status only and require no usage script. Cost is not included in the marker.

Troubleshoot the signal you actually observed

  • Only a waiting phase appears: check Activity for a settle versus explicit awaiting-input activity. Screen quietness alone does not identify a permission request.
  • The hook runs but no activity arrives: preserve > /dev/tty so captured standard output cannot divert the marker.
  • Literal backslashes appear: preserve the JSON escaping and TOML literal quoting exactly. printf must receive the escape notation and convert it to bytes.
  • Completion appears but no Codex attention event does: the Codex notify command above sends done only.
  • No second settled event follows completion: SpeakCode suppresses settling when an explicit or standard marker already covered that lull.
  • You see a notification without a completion event: standard notification sequences and command-end sequences have different meanings; a notification is not automatically a finished turn.
  • No token counts appear: the inline status hooks do not emit OSC 6970. Usage reporting is optional and separate from completion detection.

Questions

Do I need hooks to see agent status?

No. SpeakCode detects quiet output without configuration, but a quiet terminal does not establish why an agent paused. Hooks explicitly report completion or a request for input.

Why does the Claude Code hook write to /dev/tty?

A hook may have its standard output captured elsewhere. Writing to /dev/tty sends the marker to the terminal card running the agent.

Does the Codex notify example report permission requests?

The example reports turn completion with the done token. It does not configure an awaiting-input notification.

Do these hooks require a token-usage script?

No. Both Claude Code hooks use inline printf commands; optional usage reporting uses the separate OSC 6970 channel.

Claude Code Desktop App for macOS | SpeakCode · All guides