# I used Claude Code's hooks to pause a terminal game the moment my agent finishes

> Source: <https://dev.to/kuldeepmahour/i-used-claude-codes-hooks-to-pause-a-terminal-game-the-moment-my-agent-finishes-53k2>
> Published: 2026-11-01 10:18:13+00:00

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.

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:

``` js
// 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.

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`.

Two small things make the pause feel right:

`\x1b[?1004h`). When you click away, the terminal sends `\x1b[O` and the game pauses. When you come back, it sends `\x1b[I`.
Keys typed in the first moment after a banner appears are ignored, because they were meant for the game, not the banner.

This was the fiddly part. `/arcade` tries, in order:

`tmux display-popup -E -w 70% -h 70% agent-arcade`, a floating window over your session` agent-arcade`
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:

``` js
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`.

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.

`/plugin install agent-arcade --marketplace Kuldeep-045/agent-arcade`, then `/arcade`
`npm install -g agent-arcade`, then `agent-arcade setup`
Code (MIT, no dependencies, no tracking): [https://github.com/Kuldeep-045/agent-arcade](https://github.com/Kuldeep-045/agent-arcade)

What game would you want next?
