Plays an old-school dial-up modem handshake while Claude is thinking or working. /modem toggles it.

A Claude Code mod that plays an old-school dial-up modem handshake while Claude is thinking or working, and hangs up when the turn ends.
/modem turns it on or off (/modem on, /modem off); the choice sticks across sessions.volume option goes from 0 (silent) to 4; the default is 0.6.Needs macOS (it plays through afplay) and Claude Code 2.1.287+ in the terminal, or 2.1.286+ in the desktop app's Code tab.
~/.claude/skills/, so you have ~/.claude/skills/dialup/.claude-plugin/plugin.json, then start a new session.claude --plugin-dir dialup.zipThe handshake is synthesized, not recorded. Edit scripts/synth.ts, then run bun scripts/synth.ts to rewrite sounds/handshake.wav.
hooks/register.ts 78 lines1import type { EngineInterface, Register } from 'claude-code'
2
3const HANDSHAKE = 'sounds/handshake.wav'
4const MUTED = 'isMuted'
5
6let gain = 0.6
7let isWorking = false
8let line: AbortController | undefined
9let hasFailed = false
10
11async function dial($: EngineInterface) {
12 const isMuted = gain === 0 || (await $.store.get(MUTED)) === true
13 if (isMuted || !isWorking || line !== undefined) return
14 const call = new AbortController()
15 line = call
16 $.audio
17 .play({ asset: HANDSHAKE }, { shouldLoop: true, gain, signal: call.signal })
18 .catch((error: unknown) => {
19 if (!hasFailed) $.ui.log(`dialup: no carrier, the handshake did not play (${String(error)})`)
20 hasFailed = true
21 })
22 .finally(() => {
23 if (line === call) line = undefined
24 })
25}
26
27function hangUp() {
28 line?.abort()
29 line = undefined
30}
31
32// The modem dials up when Claude starts a turn, redials for as long as it
33// works, and hangs up when the main loop's turn ends (answered, interrupted
34// or failed). `/modem` mutes it, across sessions.
35export const register: Register = (on, options) => {
36 const volume = Number(options.volume)
37 gain = Number.isFinite(volume) ? Math.min(4, Math.max(0, volume)) : 0.6
38
39 on('session.start', async ($, e, next) => {
40 await $.command.register({
41 name: 'modem',
42 description: 'Turn the dial-up modem sound on or off',
43 argumentHint: '[on|off]',
44 immediate: true,
45 })
46 return next(e)
47 })
48
49 on('turn.start', async ($, e, next) => {
50 isWorking = true
51 await dial($)
52 return next(e)
53 })
54
55 on('turn.complete', ($, e, next) => {
56 if (e.agentId === undefined) {
57 isWorking = false
58 hangUp()
59 }
60 return next(e)
61 })
62
63 on('session.end', ($, e, next) => {
64 isWorking = false
65 hangUp()
66 return next(e)
67 })
68
69 on('command.run', { command: 'modem' }, async ($, e) => {
70 const word = e.args.trim().toLowerCase()
71 const isMuted = word === 'off' || (word !== 'on' && (await $.store.get(MUTED)) !== true)
72 await $.store.set(MUTED, isMuted)
73 if (isMuted) hangUp()
74 else await dial($)
75 return { text: isMuted ? 'Modem off.' : 'Modem on: Claude dials up while it works.' }
76 })
77}
78