feat: terminal bell (BEL) when Claude Code is waiting for tool approval

Status Fixed / completed
Maintainer reply None cached
Activity 14 comments · opened Mar 20, 2026 · closed Aug 17, 2026

Summary

Ring the terminal bell (\a / BEL) when Claude Code pauses and is waiting for the user to approve a tool use. This is distinct from a completion bell — it's a "I need you right now" signal, not a "I'm done" notification.

Motivation

When Claude Code is running in a buried terminal or tmux pane, there's no way to know it's blocked on an approval prompt without switching to it and looking. The session silently waits, the user is doing something else, and minutes pass.

A terminal bell on the approval prompt solves this without any external tooling — it works in tmux, screen, iTerm2, Wezterm, Ghostty, and every terminal that supports the standard BEL character. Most terminals can translate BEL into a visual bell, a dock badge, or a desktop notification depending on user preference. The user controls what "bell" means to them.

Proposed behavior

When the approval prompt is displayed (the "Allow / Deny" dialog for tool use), emit a single \a (BEL, \x07) to stderr before or alongside the prompt text.

Optionally, make this configurable:

// ~/.claude/settings.json
{
  "bellOnApproval": true  // default: true
}

Why not just use the existing Notification hook?

The Notification hook fires for general notifications, not specifically for approval prompts. Wiring a bell through a hook requires the user to write and maintain a shell script for something that could be a one-liner in the core UI.

Related

  • #32610 — terminal bell on completion (different use case — "I'm done" vs "I need you")
  • #13922 — configurable idle_prompt notification hook

---

Drafted with AI assistance. AI-G — fully AI-generated, human-reviewed.

View original on GitHub ↗

13 Comments

github-actions[bot] · 5 months ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/34654
  2. https://github.com/anthropics/claude-code/issues/10532
  3. https://github.com/anthropics/claude-code/issues/652

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

VoxCore84 · 5 months ago

We solved this with a Notification hook that fires Windows toast notifications via BurntToast (PowerShell module). It classifies the event type and adjusts urgency:

  • Permission needed → sound + 30s toast
  • Task complete → silent 8s toast
  • Tool failure → sound + 15s toast

The hook is ~90 lines of Python that spawns a detached powershell.exe process with a BurntToast command, falling back to System.Windows.Forms.NotifyIcon if BurntToast isn't installed.

Works well for the "I need you right now" case since the Notification hook does fire for approval prompts (at least on Windows). That said, a native BEL character would be simpler and terminal-universal — our hook required writing and maintaining a Python script for what should be a one-character emit.

The distinction between "I need you" (approval) and "I'm done" (completion) is important. Our hook handles both but treats them differently. A built-in bellOnApproval setting would eliminate the need for custom hooks for the most common case.

For anyone on Windows wanting the notification hook approach in the meantime, the pattern is: Notification event → classify via hook_event_namesubprocess.Popen(["powershell.exe", ...], creationflags=DETACHED_PROCESS).

langdon · 5 months ago

These issues overlap in motivation but differ in proposed solution. #34654, #10532, and #652 request general notification or sound alerts — none proposes emitting \a (BEL) specifically. The terminal bell is zero-dependency, terminal-universal, and already user-configurable (visual bell, dock badge, desktop notification) — it's the primitive that hook-based workarounds like the one in the sibling comment are trying to approximate. This issue is specifically proposing BEL as the correct minimal implementation.

VoxCore84 · 5 months ago

@langdon Well put — BEL is the correct primitive here. Our hook is ~90 lines of Python + PowerShell + BurntToast just to approximate what \a does natively. We built it because there was no alternative, not because it's the right architecture.

To add to your point about the cited "duplicates": #34654 requests desktop notifications (OS-level API), #10532 requests sound alerts (audio file playback), and #652 is a general "notify me" feature request. None of them propose using the terminal's own signaling mechanism, which is the key insight here — BEL delegates the notification UX entirely to the terminal emulator, where the user already has preferences configured. Zero new dependencies, zero platform-specific code, one character.

If this lands, we'd likely keep our hook for the richer classification (different toast styles for approval vs. completion vs. failure) but remove the core notification logic and just rely on BEL for the "I need you" case.

yurukusa · 5 months ago

You can add a terminal bell on permission prompts with a Notification hook:

printf '\a' >&2
if [ -n "$TMUX" ]; then
    tmux display-message "Claude Code: approval needed" 2>/dev/null
fi
exit 0
{
  "hooks": {
    "Notification": [{
      "matcher": "permission_prompt",
      "hooks": [{ "type": "command", "command": "bash ~/.claude/hooks/bell-on-prompt.sh" }]
    }]
  }
}

The permission_prompt notification fires when Claude is about to show an approval dialog. The \a BEL character rings the terminal bell immediately — in tmux this flashes the pane, in most terminal emulators it triggers the system bell or taskbar notification.
For tmux users: make sure set-option -g visual-bell on is in your .tmux.conf so the bell triggers a visual indicator.

zoltanpetrik · 4 months ago

I've started using this to get notifications on everything requiring my attention:

"hooks": {
  "Notification": [
    {
      "hooks": [
        {
          "type": "command",
          "command": "printf '\\a' > /dev/tty 2>/dev/null || true"
        }
      ]
    }
  ],
  "Stop": [
    {
      "hooks": [
        {
          "type": "command",
          "command": "printf '\\a' > /dev/tty 2>/dev/null || true"
        }
      ]
    }
  ]
}

Two hooks emit BEL (\x07) to /dev/tty:

  • Notification: Fires when Claude needs attention (tool approval, idle-waiting). This is what this issue is specifically about.
  • Stop: Fires when Claude finishes a turn, so the tab lights up when a task is done.

_The || true presumably prevents hook errors if /dev/tty is unavailable (e.g., non-interactive sessions)._

Edit: been using this for 2 days straight, and working as expected.

BrendanC23 · 4 months ago

I got this working by editing Claude's config. Run /config and search for Local notifications. I changed it to Terminal Bell (\a) and it flashes the terminal icon when Claude asks for permission. There is a delay (around 5s) between the permission request showing up in the terminal and when the icon flashes.

I'm using Windows Terminal and PowerShell. You can edit the bell action by going to settings, the clicking the profile name, advanced, and edit the bell notification style option (audible, flash window, or flash taskbar). Depending on what shell you are using, you may need to edit the settings for "Windows PowerShell" or "PowerShell".

alex-lx · 3 months ago
I've started using this to get notifications on everything requiring my attention:[重试  错误原因](javascript:void(0)) "hooks": { "Notification": [ { "hooks": [ { "type": "command", "command": "printf '\\a' > /dev/tty 2>/dev/null || true" } ] } ], "Stop": [ { "hooks": [ { "type": "command", "command": "printf '\\a' > /dev/tty 2>/dev/null || true" } ] } ] } Two hooks emit BEL (\x07) to /dev/tty:[重试  错误原因](javascript:void(0)) Notification: Fires when Claude needs attention (tool approval, idle-waiting). This is what this issue is specifically about.[重试  错误原因](javascript:void(0)) Stop: Fires when Claude finishes a turn, so the tab lights up when a task is done.[重试  错误原因](javascript:void(0)) _The || true presumably prevents hook errors if /dev/tty is unavailable (e.g., non-interactive sessions).[重试  错误原因](javascript:void(0))_ Edit: been using this for 2 days straight, and working as expected.[重试  错误原因](javascript:void(0))

Does it work anymore recently? I used this way before, it works well, but recently, it always report "/dev/tty: Device not configured"

loudgas · 3 months ago
Does it work anymore recently? I used this way before, it works well, but recently, it always report "/dev/tty: Device not configured"

I ran into the same issue, then found this solution in the Claude Code docs:

The example below fires a desktop notification from a Notification hook. The escape sequence is built with printf octal escapes so the control bytes never appear on the shell command line, and jq -n --arg builds the JSON output so quotes, backslashes, and newlines in the notification message are escaped correctly: `` #!/bin/bash # Notification hook: ping the desktop when Claude Code needs attention. input=$(cat) title="Claude Code" body=$(jq -r '.message // "Needs your attention"' <<<"$input") seq=$(printf '\033]777;notify;%s;%s\007' "$title" "$body") jq -nc --arg seq "$seq" '{terminalSequence: $seq}' ` The { "terminalSequence": "..." } shape is the same from any shell or language. On Windows, build the escape string in PowerShell or a script and emit the same JSON object. terminalSequence is the supported replacement for hooks that previously wrote escape sequences directly to /dev/tty`. The allowlist is restricted to sequences that cannot move the cursor or alter colors, so a hook can never corrupt an on-screen prompt.

https://code.claude.com/docs/en/hooks#emit-terminal-notifications

fvogel · 3 months ago

Thanks for this!

Here is a simple macOS audio hook.

~/.claude/hooks/notify.sh

#!/bin/bash
afplay -v 0.5 /System/Library/Sounds/Tink.aiff 2>/dev/null
jq -nc '{terminalSequence: ""}'
chmod +x ~/.claude/hooks/notify.sh

~/.claude/settings.json

"hooks": {
  "PermissionRequest": [{ "matcher": "", "hooks": [{ "type": "command", "command": "~/.claude/hooks/notify.sh" }] }],
  "Stop":              [{ "matcher": "", "hooks": [{ "type": "command", "command": "~/.claude/hooks/notify.sh" }] }]
}

afplay needs no macOS permissions — unlike osascript which requires Notifications access in System Settings.
-v 0.5 sets volume to 50% — adjust as needed.
All macOS system sounds are in /System/Library/Sounds/ — swap Tink.aiff for any other.

design-and-deliver · 2 months ago

Hey my open source package claude-code-autoconfig has the Waiting indicator that you're asking for. Article here:

https://www.linkedin.com/pulse/waiting-state-indicator-youve-been-claude-code-andrew-ciccarelli-sc64e/

Install free ➜ npx claude-code-autoconfig

DaveAurionix · 2 months ago

I am using Claude Code v2.1.195 on Windows. It has started beeping loudly at multiple points, before it is fully done, but I use --permission-mode bypassPermissions so Claude is not waiting for permission. These beeps serve no purpose in bypass-mode, please could they be disabled unless Claude has actually stopped to ask for permission?

atlas-architect · 2 months ago

+1 — and one extension that makes this a lot more useful: fire the BEL on turn completion too (not just tool-approval), and make it focus-aware (only when the tab is unfocused).

Use case: running 5+ Claude Code instances across virtual desktops — the multi-instance / orchestrator workflow Claude Code is built for. The thing I need is "which session finished / needs me" at a glance. Codex already does this: a finished Codex turn shows a bell glyph on its tab, so I can scroll desktops (Stream Deck) and instantly see what's waiting.

I tried to DIY it on Windows Terminal and got most of the way, which suggests it's a small wiring job:

  • preferredNotifChannel: "terminal_bell" plays a sound but doesn't write the raw BEL to the tab's PTY → WT shows no tab glyph.
  • A Stop hook emitting a terminalSequence with a bare BEL does produce the glyph perfectly — CC writes it through its own terminal path (works great on Windows, no /dev/tty needed).
  • The blocker: Stop fires on every turn, and hooks receive no focus state — no focus/active field in hook input, no focus-aware Notification trigger, no "only when unfocused" setting. So the bell fires even when I'm staring right at the tab → unusable.

So the concrete ask — any one of these unlocks it:

  1. a preferredNotifChannel mode = "emit BEL on turn-complete, only when unfocused", or
  2. a focus-aware Notification trigger that fires on completion-while-unfocused, or
  3. (minimum) expose a focused/active field in hook input so we can gate it ourselves.

CC already tracks terminal focus (for its own notification gating) and already has the terminalSequence BEL path — so this is mostly wiring two existing pieces together. Parity with Codex here would be great given how central multi-instance work is to Claude Code.

Showing cached comments. Read the full discussion on GitHub ↗