Keep several Claude Code sessions open at once? With this, after the terminal restarts each tab resumes its own conversation instead of a shared "most recent" one.
- Solves a concrete pain: multiple parallel Claude Code sessions survive a restart, each reopening exactly its own conversation.
- Zero state: the conversation id is the tab's uuid (
AGTERM_SESSION_ID). No mapping file to keep in sync or let go stale. - Idempotent: the first launch pins the conversation to the tab
(
--session-id); later launches continue it (--resume) - it flips automatically. - Stays out of the way:
claude mcp,-p, an explicitclaude --resume <other-id>, and any launch outside agterm pass through untouched. - Small, dependency-free, pure zsh; options are function-local (
emulate -L), so nothing leaks into your interactive shell.
- agterm-specific. It needs a stable per-tab identifier and relies on agterm feeding the restored command back through the login shell (that is how the function intercepts it). It won't work as-is in another terminal - you'd adapt it to that terminal's equivalent.
- Rests on restore behavior that is verified empirically, not documented - it could change between agterm versions.
- One conversation per tab. Since conversation id = tab id, two live
claudeprocesses in the same tab (a split/scratch pane shares oneAGTERM_SESSION_ID) collide with "Session ID ... is already in use". - It shadows the
claudecommand with a shell function. Passthrough is handled, but the list of subcommands/flags that must not be touched has to be kept current if the CLI grows new ones. - It knows Claude Code's on-disk layout (
~/.claude/projects/*/<id>.jsonl). If that storage location changes, the "does this conversation exist" check breaks (it would always create, then hit "already in use" on restart). A one-line fix, but worth knowing. - zsh only (
${:l}, the(N)glob qualifier,emulate). Bash needs a rewrite. - Existing conversations aren't bound to tabs. After you install this, the first launch in a tab starts a new conversation pinned to that tab - it does not adopt an arbitrary earlier one.
agterm can remember the command running in a tab and re-run it on restart. The
catch: it re-runs the command verbatim, and claude with no arguments is a
new conversation, not a continuation.
The trick rests on two facts. Every agterm tab has a stable identifier
(AGTERM_SESSION_ID) that survives a restart. And Claude Code lets you supply a
session id from the outside (--session-id). So you can use the tab's id as the
conversation id - and the tab permanently "owns" one conversation.
The function then wraps claude: on the first launch in a tab it pins the
conversation to the tab id (--session-id); if that conversation's file already
exists on disk it continues it (--resume). The whole decision is one check: is
<tab-id>.jsonl present under ~/.claude/projects/?
On restart agterm replays the remembered command, the function recognizes "its own" id, sees the conversation already exists, and reopens exactly that one. Each tab, its own.
# Claude Code: per-agterm-tab session resume.
# Binds each tab's Claude Code session id to the tab's own uuid (AGTERM_SESSION_ID),
# so restoring the terminal resumes that tab's own conversation instead of starting fresh.
# Requires: agterm (exports AGTERM_SESSION_ID, restores running commands) + zsh.
claude() {
emulate -L zsh
local sid=${AGTERM_SESSION_ID:l}
[[ -z $sid ]] && { command claude "$@"; return; } # not in an agterm tab -> passthrough
if [[ "$1" == (--session-id|-r|--resume) && "${2:l}" == $sid ]]; then
shift 2 # restore replayed our own flag -> re-decide below
else
local a
for a in "$@"; do # user steering a session/subcommand/headless?
case $a in
-r|--resume|--session-id|--resume=*|--session-id=*|-r=*|-c|--continue|-p|--print|-v|--version|-h|--help|mcp|update|doctor|config|install|migrate-installer|setup-token)
command claude "$@"; return ;; # -> hand off untouched
esac
done
fi
local -a f=(~/.claude/projects/*/$sid.jsonl(N)) # does this tab's session already exist?
if (( $#f )); then command claude --resume "$sid" "$@" # yes -> continue it
else command claude --session-id "$sid" "$@"; fi # no -> create it with this fixed id
}- File:
~/.zshrc- the config your interactive login zsh reads. That matters: agterm feeds the restored command into that shell, so the function must be defined there (not in.zshenv/.zprofile). - Requirements: zsh; agterm with "Restore running commands on restart"
enabled (Settings); Claude Code sessions stored in the default
~/.claude/projects/. - Activate: new tabs/shells pick it up automatically; in an already-open
shell run
source ~/.zshrc. - Verify: open a tab, run
claude, say a few words, restart the terminal - the tab should return to the same conversation.pswill showclaude --resume <tab-id>. - Remove: delete the function block from
~/.zshrc.