Claude Code and Codex often take a few minutes on a task. In that time I either stare at the spinner, or switch to another tab and forget to come back for ten minutes.
So I built Agent Arcade: tiny terminal games to play during the wait. The useful part isn't the game. It's that the game pauses and tells you the moment your agent is done or needs you, so the break never runs long.
This post is about how that works, because the same trick (an agent hook that writes a file, something else that watches it) is handy for a lot more than games.
The idea: hooks plus one file
Claude Code can run a command at certain moments of a session. Three of those moments matter here:
| Hook | Fires when | Agent Arcade says |
|---|---|---|
Stop |
Claude finishes its turn | "done" |
PermissionRequest |
Claude shows a permission prompt | "ask" |
Notification |
other "waiting for you" moments, like an idle prompt | "ask" |
Each hook runs one tiny command, agent-arcade notify or agent-arcade notify ask. All it does is write a word to a file:
// bin/agent-arcade.mjs
else if (cmd === 'notify') {
fs.mkdirSync(DIR, { recursive: true });
fs.writeFileSync(SIGNAL, arg === 'ask' ? 'ask\n' : 'done\n'); // ~/.agent-arcade/signal
}
No server, no socket, no daemon. The game, running in another terminal, checks that file's modified time on every tick. A new time means a new event:
// src/shell.mjs
const stamp = () => { try { return fs.statSync(SIGNAL).mtimeMs; } catch { return 0; } };
let seen = stamp();
function checkAgent() {
const t = stamp();
if (t === seen) return; // nothing new
seen = t;
let kind = 'done';
try { kind = fs.readFileSync(SIGNAL, 'utf8').trim() === 'ask' ? 'ask' : 'done'; } catch {}
agent = kind; // shows the banner and pauses the game
out.write('\x07'); // terminal bell
}
Using the file's modified time instead of its contents means two "done" events in a row still count as two events, and a game started after an event doesn't react to an old one.
The same file works for Codex, whose notify setting runs a program after each turn. Any agent that can run a command when it finishes can use it.
The bug a stranger found on Hacker News
My first version used Notification for "Claude needs you". On my Show HN post, a commenter pointed out something I'd missed in the docs: the permission-prompt notification only fires after you haven't typed for about six seconds. Mid-game, you're pressing keys constantly, so the "Claude is waiting" banner could arrive late.
PermissionRequest fires as soon as the prompt appears. The fix was one line in the setup, and the plugin now listens to it directly. The hook never answers the prompt: it only writes "ask", and you approve or deny as usual.
If you write hooks for "the agent needs me" alerts, use PermissionRequest, not Notification.
Not losing your game
Two small things make the pause feel right:
-
Focus reporting. The game turns on terminal focus events (
\x1b[?1004h). When you click away, the terminal sends\x1b[Oand the game pauses. When you come back, it sends\x1b[I. - A 3-2-1 countdown on every resume, so you don't crash into a wall the instant you return.
Keys typed in the first moment after a banner appears are ignored, because they were meant for the game, not the banner.
Opening it beside the agent
This was the fiddly part. /arcade tries, in order:
-
tmux:
tmux display-popup -E -w 70% -h 70% agent-arcade, a floating window over your session - Ghostty: its quick terminal (if it's set to open centered) or a split, via AppleScript
- iTerm2: a vertical split, via AppleScript
-
Anything else: a message telling you to open a new tab and run
agent-arcade
Making it a one-line Claude Code plugin
At first, setup was npm install -g agent-arcade and then agent-arcade setup, which edits ~/.claude/settings.json. That's two steps and a settings file edit.
Now it's a Claude Code plugin, installed from the GitHub repo, which doubles as a plugin marketplace:
/plugin install agent-arcade --marketplace Kuldeep-045/agent-arcade
The plugin registers /arcade as an immediate command, so it runs without a model turn. It opens the game instantly, even while Claude is in the middle of a task, and doesn't add anything to the conversation. It also listens to the same hook events, so there's no settings file to edit:
on('classic.Stop', async ($, e, next) => {
arcade($, ['notify'], 5000).catch(() => {}) // fire and forget
return next(e) // never change what Claude does
})
on('classic.PermissionRequest', async ($, e, next) => {
arcade($, ['notify', 'ask'], 5000).catch(() => {})
return next(e)
})
The repo only needs two extra files to be installable: the plugin's manifest and a .claude-plugin/marketplace.json listing it. If agent-arcade isn't installed globally, the plugin falls back to npx.
Adding a game is one small file
Each game is a plain object: start, tick, key, draw, score. The shell handles the screen, input, pausing, banners and best scores. Snake is about 60 lines. If you have a favourite game for a two-minute wait, I'd love a pull request.
Try it
- Claude Code:
/plugin install agent-arcade --marketplace Kuldeep-045/agent-arcade, then/arcade - Any agent:
npm install -g agent-arcade, thenagent-arcade setup
Code (MIT, no dependencies, no tracking): https://github.com/Kuldeep-045/agent-arcade
What game would you want next?

Top comments (0)