cd /news/developer-tools/per-tab-claude-code-session-resume-i… · home topics developer-tools article
[ARTICLE · art-46611] src=gist.github.com ↗ pub= topic=developer-tools verified=true sentiment=· neutral

Per-tab Claude Code session resume in agterm (zsh): each tab resumes its own conversation after a terminal restart

A developer created a zsh function for agterm that enables per-tab Claude Code session persistence across terminal restarts. The solution uses the tab's unique AGTERM_SESSION_ID as the conversation identifier, allowing each tab to resume its own conversation automatically after a restart. The function is dependency-free and idempotent, but is specific to agterm and zsh.

read4 min views1 publishedJul 1, 2026

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 backthrough 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 liveclaude

processes in the same tab (a split/scratch pane shares oneAGTERM_SESSION_ID

) collide with "Session ID ... is already in use".It shadows the 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.claude

commandIt 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() {
  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 runsource ~/.zshrc

.Verify: open a tab, runclaude

, say a few words, restart the terminal - the tab should return to the same conversation.ps

will showclaude --resume <tab-id>

.Remove: delete the function block from~/.zshrc

.

── more in #developer-tools 4 stories · sorted by recency
── more on @claude code 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/per-tab-claude-code-…] indexed:0 read:4min 2026-07-01 ·