# tmux-claude-hatch: a popup per project, and a picker that knows which agent needs you

> The plugin runs each Claude Code session in its own nested tmux session so closing the popup never kills the work, reads live status out of the session files Claude Code already writes, and rings a bell that highlights the window you launched from.

**craftzdog/tmux-claude-hatch** — tmux plugin that runs Claude Code sessions in popups per project directory, with an fzf picker showing each session's status and a bell that highlights the window that needs you.

- Repository: https://github.com/craftzdog/tmux-claude-hatch
- Website: https://www.devas.life/i-made-a-claude-code-session-manager-for-tmux/
- Stars: 393 · Forks: 53
- Language: Shell
- License: MIT
- Published: 2026-09-17 · Updated: 2026-09-17 · Language: en
- Canonical page: https://hysenlabs.com/projects/craftzdog-tmux-claude-hatch

## Each session gets a nested tmux session, which is why closing the popup is safe

The central mechanism is nesting. When you launch a session for a project directory, the plugin creates a separate tmux session for that agent and shows it to you as a popup over your current window. The popup is only a view of that session. Closing it with `prefix` + `d` returns you to your window and the Claude session keeps running, because it was never a child of the pane you were looking at.

That is the whole design in one sentence, and it is what separates this from a wrapper that starts Claude in a pane. A pane dies with its window; a session does not.

The launcher itself is `prefix` + `y`, and it opens or re-attaches a session for the current directory, so running it twice in one project returns you to the agent already working there rather than starting a second one. The name of the underlying session is prefixed `claude-` by default.

The project is explicit about its size. It describes itself as a few shell scripts plus an optional Claude Code plugin, and the repository top level is small: `claude_hatch.tmux` as the entry point, a `scripts/` directory, a `plugins/` directory for the Claude Code side, and `docs/`.

For installation through tpm, one line goes into the tmux configuration:

```tmux
set -g @plugin 'craftzdog/tmux-claude-hatch'
```

Then `prefix` + <kbd>I</kbd> installs it. A manual path exists for anyone not using tpm, which is a clone plus a `run-shell` line pointing at `claude_hatch.tmux`.

## Status comes from Claude Code's own session files, parsed with jq

The picker shows three states per agent: `working`, `waiting`, and `idle`. Those are not inferred from the pane contents or from a heuristic on scrollback. They are read straight out of the session files Claude Code already writes, with `jq` doing the parsing, and the project states plainly that no setup is required to get it.

That design has a visible consequence, which is the fallback path. The prerequisite is Claude Code 2.1.139 or newer, and the reason is the `claude agents` command. When the session files cannot be read, that command is what the plugin falls back to, so the version floor is a compatibility requirement rather than a formality.

The picker is `fzf`, and it does more than list. It renders a live preview of each agent's screen inside the picker itself, so you can see what an agent is doing before you commit to jumping into it. Agents in a `waiting` or `idle` state sort to the top by default, which puts the ones needing a human first.

The sort order is a single option. Setting `@claude_sort 'recent'` switches to last activity instead of status, so a long-running agent that keeps working will not be buried under a finished one. Default sorting is by need, which is the opposite trade.

One more detail about identity: the picker identifies each agent by its process rather than by its tmux session, which is why several agents in one project appear as separate rows, and why a Claude you started by hand in an ordinary pane also shows up in the list.

## Jumping in switches the client to the right window before reopening the popup

Selecting an agent in the picker does something more specific than attaching to a session. The described sequence is that the client switches to the window the agent was launched from, and only then resumes it in a popup over that window. The distinction is small in text and large in practice, because an agent attached over the wrong window looks like a bug: the session is live, but it appears to be running somewhere you are not.

Two picker keys handle cleanup rather than navigation. `ctrl-x` kills the highlighted agent, which is aimed at sessions that have finished. `ctrl-y` copies the agent's location onto the clipboard and closes the picker, and the format is a tmux target such as `claude-88074b0e:0.0`, ready to be handed to `tmux send-keys -t` or anything else that addresses panes.

Navigation is otherwise fzf's: the arrow keys to move and typing to filter. The filtering matters once several agents are running, since the list is not capped and a project with a handful of parallel agents plus a stray manual session is a list long enough to need narrowing.

The clipboard path is the escape hatch. Rather than adding a feature for every case, the plugin hands you the address of the pane and lets you script the interaction yourself, which is a reasonable position for a tool whose stated preference is to stay small.

## Bell forwarding relays a signal Claude Code has to emit first

The notification story has a hard dependency and the documentation is careful about it. Forwarding relays a bell; it cannot create one. Claude Code has to emit the bell in the first place, and that is what the optional Claude Code plugin does.

That plugin is installed separately from the tmux side, through Claude Code's own plugin commands:

```
/plugin marketplace add craftzdog/tmux-claude-hatch
/plugin install tmux-claude-hatch@tmux-claude-hatch
```

What it registers is a hook configuration in `~/.claude/settings.json`, so you do not have to hand-edit that file. The hook rings the terminal bell on the three events the picker treats as needing you: an agent ending a turn, asking for permission, and asking a question. Those map onto the `idle` and `waiting` states.

A bell raised inside a dedicated session is not something you would notice from elsewhere, so the plugin's forwarding relays it as a highlight of the window you launched the agent from. The point is that you learn where to go without opening the picker at all.

The second job of the same hook set is the picker cache. It refreshes on those events plus session start, session end, and prompt submit. The picker paints its first frame from that cache, and the cache can be turned off by setting `@claude_picker_cache` to `'off'`. Without the hook, the first frame is stale, and the documented symptom is an agent shown as `working` when it has in fact been waiting on you.

## The keybinding collision is the one installation problem worth planning for

The plugin binds two prefix combinations by default, and the documentation calls out the collision explicitly. `prefix` + `y` launches or opens a session for the current directory, and `prefix` + `u` opens the picker. If your own configuration already uses either, you have two ways out and the choice matters.

You can change the keys through the options, or you can make sure the plugin loads after your own bindings so the one you want wins. The second route is a load order problem, not a key problem, and the documentation gives the concrete fix: put `run '~/.tmux/plugins/tpm/tpm'` after your own bindings. A plugin that loads later overrides an earlier binding for the same combination.

The options themselves are set before the plugin loads, and the defaults are shown in the configuration file:

```tmux
set -g @claude_launch_key     'y'        # prefix key: launch/open for current dir
set -g @claude_list_key       'u'        # prefix key: open the picker
set -g @claude_command        'claude'   # command run in new sessions
set -g @claude_args           ''         # extra args appended to the command
set -g @claude_session_prefix 'claude-'  # tmux session name prefix
set -g @claude_popup_width     '90%'     # popup width
set -g @claude_popup_height    '90%'     # popup height
set -g @claude_fzf_options    ''         # extra options passed to the fzf picker
set -g @claude_picker_cache
```

Two of those deserve a second look. `@claude_command` changes the binary the launcher runs, so the plugin is not hard-wired to the name `claude` even though it is Claude Code only. And `@claude_args` takes arbitrary extra arguments, which the documentation demonstrates with a value that skips permission prompts in launched sessions. That is a real convenience for a workflow where you are handing over a scoped task, and also the setting to think hardest about before enabling.

## The requirements list is the real compatibility contract

Five prerequisites, and each one maps to a feature rather than being a formality. tmux 3.2 or newer is needed for `display-popup`, which is the popup mechanism the whole interaction rests on; on an older tmux the launch path has nothing to build on.

fzf is the picker interface, and jq is what parses the session files behind the status column. Neither is bundled. Claude Code at 2.1.139 or newer supplies the `claude agents` fallback, and `claude --version` is the stated way to check what you have. The last line of the list is bash on macOS or Linux, which is the honest boundary: this is a shell plugin and it will not run in a native Windows shell.

The project also states who it is not for, which is unusual in a README and saves people time. It is aimed at people who want to stay in their editor, whether NeoVim or Emacs, in the main tmux session, and at people who still read, write, and review the code themselves. It is explicitly not for people whose focus is the agent's chat interface rather than the code, not for anyone wanting one tool across several AI providers, and not for people running a swarm of agents they do not themselves read.

The three exclusions are worth taking at face value. The Claude Code only limit means the picker, the session file parsing, and the hooks are all coupled to one specific client, and the plugin cannot be pointed at a different one without changes.

## Conclusion

This suits someone who lives in an editor inside tmux, hands scoped work to Claude Code, and wants to close the popup without losing the session. It is the wrong tool if your focus is the chat rather than the code, if you want one manager across several AI providers, since this is Claude Code only, or if you do not read the resulting code yourself. Before installing, check three things: that your tmux is 3.2 or newer, because the popup depends on `display-popup`; that `fzf` and `jq` are present; and that the plugin loads after your own bindings, since it takes `prefix` + `y` and `prefix` + `u` by default and will lose to a later binding. The last push was 2026-09-28, the same day as the v1.5.0 tag, and the repository also ships a separate Claude Code plugin that is what makes the bell and the picker cache work.

## FAQ

### What does tmux-claude-hatch actually do when I close the popup?

Nothing to the session. Each Claude Code session runs in its own nested tmux session and the popup is only a view over it, so prefix plus d closes the popup and returns you to your window while the agent keeps running.

### How does the tmux-claude-hatch picker know an agent is waiting for me?

It reads the session files Claude Code already writes, with jq parsing them, and reports working, waiting, or idle per agent. Claude Code 2.1.139 or newer is required because the claude agents command is the fallback when those files cannot be read.

### Why do I need the separate Claude Code plugin for tmux-claude-hatch to ring the bell?

Bell forwarding relays a signal rather than creating one, so Claude Code has to emit it. The optional plugin installs a hook in ~/.claude/settings.json that rings the bell when an agent ends a turn, asks for permission, or asks a question, and that also refreshes the picker's cache.

### Can I change the default prefix keys in tmux-claude-hatch?

Yes. prefix plus y launches a session for the current directory and prefix plus u opens the picker by default, and both are configurable with @claude_launch_key and @claude_list_key set before the plugin loads. If your config already uses those combinations, load the plugin after your own bindings so it wins.

## Sources

- [craftzdog/tmux-claude-hatch on GitHub](https://github.com/craftzdog/tmux-claude-hatch)
- [License: MIT](https://github.com/craftzdog/tmux-claude-hatch/blob/main/LICENSE)
- [Project website](https://www.devas.life/i-made-a-claude-code-session-manager-for-tmux/)
- [README](https://github.com/craftzdog/tmux-claude-hatch/blob/main/README.md)
- [Releases](https://github.com/craftzdog/tmux-claude-hatch/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/craftzdog-tmux-claude-hatch
