Magnetron
Claude Code dings when it finishes — and can hum the whole time it's working.
Install it and it works. No wrapper command, no config to write. Because it
installs itself as Claude Code hooks, it covers the claude CLI in your
terminal too, not just the VS Code extension.
Windows, Linux and macOS.
Defaults
Out of the box you get only the completion sound, played three times. The
looping working sound is off, because a sound that plays for the entire length
of every turn is a bigger ask than a ding. Turn it on with
Magnetron: Choose Working Sound, or the magnetron.workingSound.enabled
setting.
Each of the two sounds is chosen independently and each can be turned off on its
own, with a master switch over both.
Built-in sounds
| While working (looped) |
When finished (one-shot) |
| Microwave running — magnetron hum, cooling fan, turntable rumble |
Ding — the classic end-of-cycle bell |
| Cooling fan — no mains buzz, easier for long sessions |
Triple beep — three flat piezo beeps, like most real microwaves |
| Ticking clock — one tick per second |
Chime — two soft descending notes |
Or point either at your own .wav.
Commands
| Command |
|
Magnetron: Choose Working Sound |
Pick the looping sound, or turn it off |
Magnetron: Choose Completion Sound |
Pick the finish sound, or turn it off |
Magnetron: Test Sounds |
Play both, now |
Magnetron: Turn On or Off |
Master switch (also the status bar item) |
Magnetron: Show Status |
What's configured, whether the daemon is up |
Magnetron: Reinstall Hooks |
If the hooks got clobbered |
Magnetron: Remove Hooks From Claude Code |
Clean uninstall |
Settings
| Setting |
Default |
|
magnetron.enabled |
true |
Master switch |
magnetron.workingSound.enabled |
false |
Loop a sound while working |
magnetron.workingSound.sound |
microwave |
microwave / fan / clock / custom |
magnetron.workingSound.customPath |
— |
Your own .wav |
magnetron.completionSound.enabled |
true |
Sound when finished |
magnetron.completionSound.sound |
ding |
ding / triple-beep / chime / custom |
magnetron.completionSound.repeat |
3 |
How many times it plays |
magnetron.completionSound.repeatIntervalMs |
420 |
Milliseconds between repeats |
magnetron.completionSound.customPath |
— |
Your own .wav |
magnetron.volume |
60 |
0–100 |
magnetron.maxSeconds |
900 |
Watchdog: the loop can never exceed this |
magnetron.showStatusBarItem |
true |
|
magnetron.manageHooks |
true |
Let the extension maintain the hooks |
Turning it off
Click the status bar item, or run Magnetron: Turn On or Off. Hooks stay
installed; Claude Code goes quiet.
To remove it completely, run Magnetron: Remove Hooks From Claude Code
before uninstalling the extension — VS Code gives extensions no uninstall
hook, so they cannot clean up after themselves. If you forget, delete the
magnetron entries from ~/.claude/settings.json by hand.
CLAUDE_MAGNETRON=0 silences a single shell.
How it works
Claude Code fires hooks at
points in its lifecycle. The extension writes five into ~/.claude/settings.json:
| Hook |
Does |
Why that one |
UserPromptSubmit |
start |
Fires the moment you submit, before any work |
Stop |
stop + completion sound |
Fires when the main agent hands control back. SubagentStop would fire once per subagent |
StopFailure |
stop, no sound |
The turn errored — don't celebrate it |
SessionStart |
pre-warm the daemon |
So the first turn is as fast as the rest |
SessionEnd |
shut the daemon down |
|
Nothing heavy runs per turn
A hook fires on every single turn, so what it costs and what it looks like
matter enormously. Two rules follow, and they drove the whole design:
Nothing on the hot path may be a console program. On Windows the hook is a
.vbs run by wscript.exe. wscript is a GUI-subsystem binary, so Windows
never allocates a console for it. powershell.exe is console-subsystem: the
OS creates — and briefly shows — a console window before PowerShell can hide
itself. That is the black box that flashes on every turn, and -WindowStyle Hidden cannot prevent it, because the window exists before your script runs.
Nothing on the hot path may start an interpreter. A long-lived daemon owns
playback, started once per session and pre-warmed by SessionStart. It decodes
the WAVs into memory up front, so starting a sound is a method call rather than
a process launch and a file read. The hook only drops a signal file and returns;
the daemon polls for them every 40 ms.
Measured on Windows:
|
per-turn hook |
signal → sound playing |
| PowerShell per hook |
~160 ms + console flash |
~500 ms |
| wscript + daemon |
~87 ms, no window |
~116 ms |
Hooks use Claude Code's args exec form, so the binary is spawned directly with
no shell in between — nothing depends on quoting or on which shell Claude Code
picked.
The daemon reads which sounds to play from a config file the extension writes,
so changing a sound takes effect on the next turn without rewriting
settings.json. The config is written twice — config.json for the PowerShell
side, config.env for the shell side — so neither script needs a JSON parser.
Why a stable folder (~/.claude/magnetron/) and not the extension's own
directory: that path changes on every version bump, so hooks pointing into it
would break on update.
Audio
- Windows — .NET
SoundPlayer, built into PowerShell. Gapless looping, no dependencies.
- Linux/macOS — the first of
mpv, ffplay, pw-play, paplay, aplay,
afplay, play found. mpv and ffplay loop natively and so are gapless;
the others restart each pass, leaving a small gap at the loop point. On a bare
Linux box sudo apt install pulseaudio-utils (or mpv) is enough.
Show Status reports which backend was found.
Volume is applied by rescaling the audio itself, because SoundPlayer has no
volume control. Custom files are only rescaled if they're 16-bit PCM WAV;
anything else passes through at full volume.
Repeats are mixed into the file at install time, not queued at playback. The
players on both platforms fire and forget, so scheduled repeats would need
timing state in the daemon's poll loop and would drift. The copies overlap
rather than being appended: a ding's decay is long and quiet, so three whole
copies end to end leave ~1.5 s between hits and take 4.5 s, which reads as
sluggish. Starting each while the previous still rings gives a 2.2 s triple that
lands as one gesture. Note triple-beep is already three beeps — set repeat
to 1 for that one.
It won't leave audio running
The daemon exits when any of these happen:
- it's told to quit (
SessionEnd);
- the Claude Code process that owns it disappears — crash, window closed —
checked continuously, on Windows with a process start-time match so a
recycled PID can't fool it;
- a long idle period with nothing playing.
A looping sound is additionally bounded by maxSeconds. Sessions are keyed by
CLAUDE_CODE_SESSION_ID and get one daemon each, so two Claude Code windows
never stop each other's audio.
Building
npm install
npm run compile
npm run sounds # regenerate the WAVs (needs python + numpy)
npx vsce package
Sounds are synthesised by tools/gen_sounds.py, not sampled. For a loop to be
seamless every tonal component must complete a whole number of cycles in the
buffer, and the noise layers are built in the frequency domain so they're
periodic by construction.
License
MIT