← На главную

Шипящий звук уведомлений в VSCode: как я нашёл настоящую причину.

Шипящий звук уведомлений в VSCode: как я нашёл настоящую причину

Расширение Claude Code для VSCode должно проигрывать приятный звонок при уведомлении «Claude is waiting for input». Вместо этого - короткое шипение, похожее на белый шум. Ниже - история расследования на Linux Mint/Cinnamon: три ложных следа, метод поимки реального виновника без root и strace, и неожиданно простая причина.

Проблема

Окружение: Linux Mint, рабочий стол Cinnamon, VSCode с расширением anthropic.claude-code. При каждом уведомлении о готовности к вводу звучит не звонок, а шипение - как будто играет случайный шум вместо звука.

Три ложных следа

Первая реакция - искать в настройках темы оформления. Все три гипотезы звучали правдоподобно, и ни одна не подтвердилась:

  1. Тема звуков Cinnamon. У Cinnamon есть свой ключ org.cinnamon.sounds notification-file, который указывает на файл звука уведомлений (по умолчанию message.oga из темы Yaru). Поменял его через gsettings set - звук не изменился. Как выяснилось позже, этот ключ используется для OSD-всплывашек (громкость, батарея), а не для сторонних приложений.

  2. Встроенные аудиосигналы VSCode. У VSCode есть система accessibility.signals.* - в том числе chatUserActionRequired, который логически подходит под «ждёт действия пользователя». У сигнала оказался собственный mp3-файл прямо в установке VSCode (.../accessibilitySignal/browser/media/chatUserActionRequired.mp3). Заменил файл, полностью перезапустил VSCode - шипение осталось. Значит, за это уведомление отвечает не встроенный сигнал VSCode.

  3. Системный X11 bell. Ещё один классический источник «пиканья» в Linux - звонок X-сервера (xset b), полностью независимый от тем оформления и звуковых файлов приложений. Отключил через xset b off - не помогло.

Три гипотезы, три мимо. Пора было переставать гадать по косвенным признакам и ловить реальный процесс, который проигрывает звук.

Как поймать реального виновника без root

Первая попытка - подписаться на события аудиосервера (pactl subscribe / pw-mon) и посмотреть, какое приложение создаёт новый аудиопоток в момент уведомления. Способ логичный, но не сработал: pactl не был установлен, а pw-mon (PipeWire) оказался слишком шумным - тысячи строк служебной информации об оборудовании, среди которых сложно найти нужное короткое событие.

Рабочим оказался куда более простой метод - опрос /proc без всякого root или strace:

while true; do
  for pid in $(pgrep -x aplay 2>/dev/null); do
    echo "cmdline: $(tr '\0' ' ' < /proc/$pid/cmdline)"
    ppid=$(awk '/^PPid:/{print $2}' /proc/$pid/status)
    echo "parent: $(tr '\0' ' ' < /proc/$ppid/cmdline)"
    # и так далее вверх по цепочке PPid
  done
  sleep 0.02
done

Идея простая: раз есть подозрение, что где-то в системе коротко живёт процесс с определённым именем, достаточно опрашивать pgrep каждые 20 мс и, как только процесс появится, сразу прочитать его /proc/<pid>/cmdline и подняться по цепочке PPid до самого начала. Никакого root, никакого strace -f по всему дереву процессов - просто дешёвый polling ровно на то время, пока копится доказательство.

Через несколько секунд после срабатывания уведомления лог показал точную цепочку:

aplay /usr/share/sounds/freedesktop/stereo/bell.oga
  ← sh -c 'paplay .../bell.oga 2>/dev/null || aplay .../bell.oga 2>/dev/null || true'
  ← code --type=utility --utility-sub-type=node.mojom.NodeService ...
  ← code (сам VSCode)

А непосредственным родителем шелл-команды оказался не абстрактный «VSCode», а сам бинарник Claude Code CLI, встроенный внутрь расширения (resources/native-binary/claude) - то есть источник звука находится не в теме оформления и не в самом VSCode, а в логике уведомлений CLI, когда он работает без настоящего терминала (TTY) внутри расширения.

Настоящая причина

Команда, которую выполняет CLI:

sh -c 'paplay /usr/share/sounds/freedesktop/stereo/bell.oga 2>/dev/null || aplay /usr/share/sounds/freedesktop/stereo/bell.oga 2>/dev/null || true'

Логика понятная: сначала пробуем paplay (проигрыватель PulseAudio/PipeWire), если не вышло - падаем на aplay (низкоуровневый плеер ALSA). Проблема была в том, что пакет pulseaudio-utils, который предоставляет paplay, не был установлен. Команда всегда проваливалась в aplay.

А у aplay есть неочевидное ограничение: он не умеет декодировать Ogg Vorbis (.oga/.ogg). При запуске aplay file.oga он не распознаёт формат контейнера и по умолчанию трактует файл как сырой PCM-поток:

$ aplay bell.oga
Воспроизведение Сырые данные 'bell.oga': Unsigned 8 bit, Частота 8000 Гц, Моно

То есть aplay буквально проигрывает сжатые байты Ogg-файла как будто это необработанные 8-битные аудиосэмплы. Компрессированные данные, интерпретированные как сырой звук, на слух неотличимы от белого шума - это и было «шипение». Причём это объясняет, почему ни одна из первых трёх гипотез не могла сработать в принципе: какой бы .oga-файл ни лежал по этому пути, результат был бы один и тот же - потому что дело не в содержимом файла, а в том, что программа-плеер вообще не умела его читать.

Решение

Всего одна команда:

sudo apt install -y pulseaudio-utils

После установки paplay полностью декодирует Vorbis, срабатывает первым в цепочке ||, и до сломанного aplay-фолбэка дело не доходит вообще. Дополнительно можно проверить финальный результат ровно той же командой, что использует CLI:

sh -c 'paplay /usr/share/sounds/freedesktop/stereo/bell.oga || aplay /usr/share/sounds/freedesktop/stereo/bell.oga || true'

Теперь звук можно свободно менять на любой другой - например, конвертировать произвольный mp3 в Ogg Vorbis и подставить вместо системного (файл системный, root:root, нужен sudo, стоит сохранить бэкап оригинала):

sudo cp /usr/share/sounds/freedesktop/stereo/bell.oga /usr/share/sounds/freedesktop/stereo/bell.oga.bak
ffmpeg -i мой_звук.mp3 -c:a libvorbis -qscale:a 5 /tmp/new_bell.oga
sudo cp /tmp/new_bell.oga /usr/share/sounds/freedesktop/stereo/bell.oga

Выводы

  • Не гадайте по косвенным признакам, когда можно поймать процесс напрямую. Три правдоподобные гипотезы (тема оформления, встроенные сигналы приложения, системный bell) потребовали трёх раундов проверки и трёх «не помогло», пока не появилось прямое доказательство - реальная командная строка процесса и цепочка родителей.
  • Опрос /proc - недооценённый инструмент. Для короткоживущих процессов не всегда нужен strace, auditd или root: pgrep в цикле с шагом 10–50 мс плюс чтение /proc/<pid>/cmdline и /proc/<pid>/status (поле PPid) достаточно, чтобы поймать процесс и восстановить всю цепочку вызовов.
  • Проверяйте базовые системные зависимости раньше специфичных гипотез. Отсутствие одного пакета (pulseaudio-utils) выглядело куда менее интересной причиной, чем «неправильная тема оформления», поэтому проверялось в последнюю очередь - а стоило бы в первую: команда с ||-фолбэком на менее функциональный инструмент - тревожный звоночек сам по себе.
  • Плеер без явной ошибки - не значит правильно отработавший. aplay не выдал ошибку и не отказался играть Ogg-файл - он молча проинтерпретировал его неправильно. Отсутствие сообщения об ошибке не гарантирует корректный результат.

💬 Обсуждение и вопросы

Комментарии и обсуждение — на интерактивной версии статьи.

Перейти к обсуждению →