Silencing the Ghostty bell when Claude Code asks a question

Silencing the Ghostty bell when Claude Code asks a question

👁7views

If you run Claude Code in Ghostty, you’ve probably heard it: a small chime every time Claude finishes a task or stops to ask you something. Useful the first day, maddening by the third, especially in a shared office or on a call.

Here’s what’s actually happening, and two ways to fix it depending on how much silence you want.

1. Why the bell fires

Claude Code doesn’t play a sound itself. When it needs your attention, it emits a terminal bell, the ancient BEL control character (0x07) that once rang an actual bell on a teletype. Your terminal decides what to do with it.

Ghostty 1.2 gave the bell real teeth. It can now play a sound, bounce the dock icon, add a 🔔 to the window title, and flash a border, all controlled by one config key. So the noise is a handshake between two programs, and you can cut the wire at either end.

2. Fix one: tell Claude Code to stop ringing

Claude Code has a notification channel setting in ~/.claude/settings.json:

{ "preferredNotifChannel": "notifications_disabled" }

That kills both the bell and desktop notifications. If you’d rather keep a native macOS notification and lose only the sound, use "ghostty" instead; Claude Code will hand the notification to Ghostty’s notification integration rather than ringing the bell.

Pick this option if you still want the bell working for other tools. make finishing a long build, a background rsync, a shell script that pings you, all keep their bells.

3. Fix two: tell Ghostty to ignore the bell

The blunter instrument lives in ~/.config/ghostty/config:

bell-features =

An empty value disables every bell behaviour. The key takes a comma separated list, so you can keep the quiet ones and drop the sound:

bell-features = title

Available features are system, audio, attention (dock bounce on macOS), title (the 🔔 marker) and border. A few are GTK/Linux only. title is a nice middle ground: you get a visual nudge on the tab, no noise.

Reload the config with Cmd+Shift+,. No restart needed.

4. A small script to flip it

I change this depending on where I’m working: silent in the office, dock bounce at home. So it lives in a script rather than my fingers.

A script that edits config should tell you what it changed in words you can check at a glance, not echo back the raw value you just typed. bell-features = <none> is technically correct and tells you nothing. So this version reports the before and after in plain English:

cat > ghostty-bell <<'EOF'
#!/usr/bin/env bash
# ghostty-bell — switch Ghostty's bell behaviour on or off.
# Usage: ghostty-bell [off|<feature,feature,...>]
#        features: audio, attention, title, system, border
set -euo pipefail

cfg="${GHOSTTY_CONFIG:-$HOME/.config/ghostty/config}"
val="${1-off}"
[ "$val" = "off" ] && val=""

describe() {
  local d="" p label
  for p in $(printf '%s' "$1" | tr ',' ' '); do
    case "$p" in
      audio)     label="sound" ;;
      attention) label="dock bounce" ;;
      title)     label="title marker" ;;
      system)    label="system notification" ;;
      border)    label="border flash" ;;
      *)         label="$p" ;;
    esac
    d="${d:+$d, }$label"
  done
  printf '%s' "${d:-nothing (fully silent)}"
}

mkdir -p "$(dirname "$cfg")"; touch "$cfg"
was=$(grep -m1 '^[[:space:]]*bell-features' "$cfg" | sed 's/^[^=]*=[[:space:]]*//' || true)

if grep -q '^[[:space:]]*bell-features' "$cfg"; then
  sed -i.bak "s|^[[:space:]]*bell-features.*|bell-features = $val|" "$cfg"
else
  cp "$cfg" "$cfg.bak"
  printf 'bell-features = %s\n' "$val" >> "$cfg"
fi

if [ -t 1 ]; then b=$(tput bold); dim=$(tput dim); g=$(tput setaf 2); r=$(tput sgr0)
else b=""; dim=""; g=""; r=""; fi

echo
if [ "$was" = "$val" ]; then
  echo "  ${b}Ghostty bell unchanged${r}"
else
  echo "  ${g}${b}Ghostty bell updated${r}"
fi
printf '  %swas%s     %s\n' "$dim" "$r" "$(describe "$was")"
printf '  %snow%s     %s%s%s\n' "$dim" "$r" "$b" "$(describe "$val")" "$r"
echo
printf '  %sconfig%s  %s\n' "$dim" "$r" "$cfg"
printf '  %sbackup%s  %s\n' "$dim" "$r" "$cfg.bak"
printf '  %sline%s    bell-features = %s\n' "$dim" "$r" "$val"
echo
echo "  Reload Ghostty with Cmd+Shift+, for this to take effect."
echo
EOF
chmod +x ghostty-bell

It rewrites the bell-features line if it exists and appends it if it doesn’t, keeps a .bak each time, says unchanged when nothing moved, and strips the colour codes when output isn’t a terminal so piping it stays clean.

5. Calling it

Run it from the directory you saved it in. The argument is whatever you want bell-features to become; off is a friendlier alias for “empty”.

./ghostty-bell off              # total silence: no sound, no bounce, no title marker
./ghostty-bell                  # same thing — off is the default
./ghostty-bell title            # quiet, but the tab gets a 🔔 so you notice on return
./ghostty-bell attention        # no sound, dock icon bounces once (macOS)
./ghostty-bell title,attention  # both visual cues, still no audio
./ghostty-bell audio,title      # sound back on, plus the title marker

Two more useful invocations:

GHOSTTY_CONFIG=~/work/ghostty.conf ./ghostty-bell off   # target a different config file
./ghostty-bell title && open -a Ghostty                 # apply, then jump back to Ghostty

Each run prints the before and after, so you can confirm without opening the file:

$ ./ghostty-bell off

  Ghostty bell updated
  was     sound, title marker, dock bounce
  now     nothing (fully silent)

  config  /Users/you/.config/ghostty/config
  backup  /Users/you/.config/ghostty/config.bak
  line    bell-features =

  Reload Ghostty with Cmd+Shift+, for this to take effect.

Run it twice and the second run says Ghostty bell unchanged, which is the tell that you already did this and forgot.

6. Verifying it worked

The bell is easy to test directly; you don’t need to wait for Claude Code to ask you something:

printf '\a'

Silence means you’re done. If you still hear a chime, check that you reloaded the Ghostty config, and that you edited the config file Ghostty is actually reading. On macOS it also looks at ~/Library/Application Support/com.mitchellh.ghostty/config, and if both exist it’s easy to spend ten minutes editing the wrong one.

7. The bell was never the whole problem

Muting Ghostty stops Claude Code’s own noise, but it doesn’t touch the rest of macOS. Slack, Mail, Messages, Calendar and every other app with a notification sound keep going, and none of them read ~/.config/ghostty/config. That noise comes from a different layer entirely: Notification Center, not the terminal.

macOS splits this into two halves, and only one of them is safely scriptable.

The global half, the “Play sound effects” toggle, the volume change feedback beep, and the system alert volume, lives in NSGlobalDomain and defaults write can flip it without asking permission.

The per app half, whether Mail or Slack plays a sound for its own notifications, deliberately isn’t scriptable. Apple stores that in a TCC protected database and only lets you change it by clicking in System Settings. That’s not an oversight; it’s the same protection that stops any process from silently disabling your alerts.

The same lesson from the ghostty-bell script applies here: don’t just say “done,” say what changed. defaults write gives no feedback of its own, so this version reads each setting before it writes, and reports was → now for every one, the same way ghostty-bell reports its before and after:

cat > mute_notifications.sh <<'EOF'
#!/bin/bash
#
# mute_notifications.sh
# Review and silence notification sounds on macOS.
#
# What this script CAN safely automate:
#   - Mute system UI sound effects (the "Play sound effects" toggle)
#   - Mute the volume-change feedback sound
#   - Mute the system alert/beep volume
#
# What macOS does NOT let a script change (Apple restricts this for
# privacy/security — per-app notification permissions require a human
# clicking in System Settings):
#   - Per-app "Allow sound" toggles for Mail, Messages, Slack, etc.
#
# For that part, this script opens the right settings pane and prints
# a checklist so you can knock it out in under a minute.
#
# It reports what it actually changed (was -> now for each setting),
# not just that it ran.

set -euo pipefail

if [ -t 1 ]; then b=$(tput bold); dim=$(tput dim); g=$(tput setaf 2); r=$(tput sgr0)
else b=""; dim=""; g=""; r=""; fi

onoff() { [ "$1" = "0" ] && echo "off" || echo "on"; }

read_default() {
  defaults read NSGlobalDomain "$1" 2>/dev/null || echo "unset"
}

echo
echo "  ${b}macOS Notification Sound Cleanup${r}"
echo

# --- capture "was" state before changing anything ---
was_ui=$(read_default com.apple.sound.uiaudio.enabled)
was_feedback=$(read_default com.apple.sound.beep.feedback)
was_volume=$(read_default com.apple.sound.beep.volume)

# --- apply changes ---
defaults write NSGlobalDomain com.apple.sound.uiaudio.enabled -int 0
defaults write NSGlobalDomain com.apple.sound.beep.feedback -int 0
defaults write NSGlobalDomain com.apple.sound.beep.volume -float 0.0

# Restart the settings daemon so changes take effect without a full logout
killall SystemUIServer 2>/dev/null || true

# --- capture "now" state and report was -> now for each ---
now_ui=$(read_default com.apple.sound.uiaudio.enabled)
now_feedback=$(read_default com.apple.sound.beep.feedback)
now_volume=$(read_default com.apple.sound.beep.volume)

report_bool() {
  local label="$1" was="$2" now="$3"
  local was_l now_l
  [ "$was" = "unset" ] && was_l="on (default)" || was_l="$(onoff "$was")"
  now_l="$(onoff "$now")"
  if [ "$was_l" = "$now_l" ]; then
    printf '  %s%-28s%s %sunchanged%s (%s)\n' "$dim" "$label" "$r" "$dim" "$r" "$now_l"
  else
    printf '  %s%-28s%s %s%s%s -> %s%s%s\n' "$dim" "$label" "$r" "$dim" "$was_l" "$r" "$g$b" "$now_l" "$r"
  fi
}

echo "  ${b}${g}Changes applied${r}"
report_bool "UI sound effects"          "$was_ui" "$now_ui"
report_bool "Volume-change feedback"    "$was_feedback" "$now_feedback"
echo "  ${dim}Alert/beep volume${r}        ${dim}${was_volume:-unset}${r} -> ${g}${b}0.0 (muted)${r}"
echo
echo "  ${dim}These persist until you change them back — no restart needed.${r}"
echo

# Open Notification settings for the manual per-app review
echo "  Opening System Settings > Notifications for the manual per-app review..."
open "x-apple.systempreferences:com.apple.preference.notifications"

cat <<INNER

------------------------------------------------------------
Manual checklist (Apple requires a click here, not a script):
------------------------------------------------------------
In System Settings > Notifications, click each app you want to
quiet and turn OFF "Play sound for notifications". Common noisy
ones to check first:

  [ ] Mail
  [ ] Messages
  [ ] Calendar
  [ ] Reminders
  [ ] FaceTime
  [ ] Slack / Teams / Zoom (if installed)
  [ ] App Store
  [ ] News

Tip: You can also select an app and toggle "Allow Notifications"
off entirely if you don't want banners either, not just sound.

For a temporary blanket mute instead of per-app changes, turn on
a Focus mode (Control Center > Focus) — it silences all sounds
and banners until you turn it off.
------------------------------------------------------------

To verify the global mute worked, trigger a volume change and
listen for the beep:

  osascript -e 'set volume output volume 50'

No sound = feedback beep is off.
INNER
EOF
chmod +x mute_notifications.sh

No sudo, no restart. Run it:

./mute_notifications.sh

Each run tells you exactly what moved:

$ ./mute_notifications.sh

  macOS Notification Sound Cleanup

  Changes applied
  UI sound effects            on (default) -> off
  Volume-change feedback      on (default) -> off
  Alert/beep volume           unset -> 0.0 (muted)

  These persist until you change them back — no restart needed.

  Opening System Settings > Notifications for the manual per-app review...

Run it a second time and the settings that already changed report unchanged (off) instead of pretending to flip them again; the same tell as ghostty-bell saying “unchanged.”

It mutes the three global sound settings immediately, then hands you off to System Settings already open on the right pane, with the checklist printed right there in the terminal so you’re not hunting for what to click next.

Verifying it worked

The global part is quick to check; trigger a volume change with the keyboard and listen for the beep:

osascript -e 'set volume output volume 50'

No sound means the feedback beep is off. The per app part you verify the obvious way: send yourself a test message in each app you just muted and confirm it arrives silently.

Combined with the Ghostty fix above, that’s the full stack quiet: Claude Code’s own bell, the terminal’s handling of it, and the rest of the OS underneath.


Sources: Claude Code terminal configuration · Claude Code settings reference · Ghostty 1.2.0 release notes