{"slug": "per-tab-claude-code-session-resume-in-agterm-zsh-each-tab-resumes-its-own-after", "title": "Per-tab Claude Code session resume in agterm (zsh): each tab resumes its own conversation after a terminal restart", "summary": "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.", "body_md": "Keep several Claude Code sessions open at once? With this, after the terminal\nrestarts each tab resumes **its own** conversation instead of a shared\n\"most recent\" one.\n\n- Solves a concrete pain: multiple parallel Claude Code sessions survive a restart, each reopening exactly its own conversation.\n- Zero state: the conversation id\n**is** the tab's uuid (`AGTERM_SESSION_ID`\n\n). No mapping file to keep in sync or let go stale. - Idempotent: the first launch pins the conversation to the tab\n(\n`--session-id`\n\n); later launches continue it (`--resume`\n\n) - it flips automatically. - Stays out of the way:\n`claude mcp`\n\n,`-p`\n\n, an explicit`claude --resume <other-id>`\n\n, and any launch outside agterm pass through untouched. - Small, dependency-free, pure zsh; options are function-local (\n`emulate -L`\n\n), so nothing leaks into your interactive shell.\n\n**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`claude`\n\nprocesses in the same tab (a split/scratch pane shares one`AGTERM_SESSION_ID`\n\n) 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`\n\ncommand**It knows Claude Code's on-disk layout**(`~/.claude/projects/*/<id>.jsonl`\n\n). 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}`\n\n, the`(N)`\n\nglob qualifier,`emulate`\n\n). 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.\n\nagterm can remember the command running in a tab and re-run it on restart. The\ncatch: it re-runs the command verbatim, and `claude`\n\nwith no arguments is a\n*new* conversation, not a continuation.\n\nThe trick rests on two facts. Every agterm tab has a stable identifier\n(`AGTERM_SESSION_ID`\n\n) that survives a restart. And Claude Code lets you supply a\nsession id from the outside (`--session-id`\n\n). So you can use the tab's id as the\nconversation id - and the tab permanently \"owns\" one conversation.\n\nThe function then wraps `claude`\n\n: on the first launch in a tab it pins the\nconversation to the tab id (`--session-id`\n\n); if that conversation's file already\nexists on disk it continues it (`--resume`\n\n). The whole decision is one check: is\n`<tab-id>.jsonl`\n\npresent under `~/.claude/projects/`\n\n?\n\nOn 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.\n\n```\n# Claude Code: per-agterm-tab session resume.\n# Binds each tab's Claude Code session id to the tab's own uuid (AGTERM_SESSION_ID),\n# so restoring the terminal resumes that tab's own conversation instead of starting fresh.\n# Requires: agterm (exports AGTERM_SESSION_ID, restores running commands) + zsh.\nclaude() {\n  emulate -L zsh\n  local sid=${AGTERM_SESSION_ID:l}\n  [[ -z $sid ]] && { command claude \"$@\"; return; }             # not in an agterm tab -> passthrough\n\n  if [[ \"$1\" == (--session-id|-r|--resume) && \"${2:l}\" == $sid ]]; then\n    shift 2                                                      # restore replayed our own flag -> re-decide below\n  else\n    local a\n    for a in \"$@\"; do                                           # user steering a session/subcommand/headless?\n      case $a in\n        -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)\n          command claude \"$@\"; return ;;                        # -> hand off untouched\n      esac\n    done\n  fi\n\n  local -a f=(~/.claude/projects/*/$sid.jsonl(N))               # does this tab's session already exist?\n  if (( $#f )); then command claude --resume \"$sid\" \"$@\"        # yes -> continue it\n  else command claude --session-id \"$sid\" \"$@\"; fi              # no  -> create it with this fixed id\n}\n```\n\n**File:**`~/.zshrc`\n\n- 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`\n\n/`.zprofile`\n\n).**Requirements:** zsh; agterm with \"Restore running commands on restart\" enabled (Settings); Claude Code sessions stored in the default`~/.claude/projects/`\n\n.**Activate:** new tabs/shells pick it up automatically; in an already-open shell run`source ~/.zshrc`\n\n.**Verify:** open a tab, run`claude`\n\n, say a few words, restart the terminal - the tab should return to the same conversation.`ps`\n\nwill show`claude --resume <tab-id>`\n\n.**Remove:** delete the function block from`~/.zshrc`\n\n.", "url": "https://wpnews.pro/news/per-tab-claude-code-session-resume-in-agterm-zsh-each-tab-resumes-its-own-after", "canonical_source": "https://gist.github.com/ssgreg/874f4cd6ea943badb417dbd8b06f959b", "published_at": "2026-07-01 10:41:05+00:00", "updated_at": "2026-07-01 12:48:39.223204+00:00", "lang": "en", "topics": ["developer-tools", "large-language-models", "ai-tools"], "entities": ["Claude Code", "agterm", "zsh"], "alternates": {"html": "https://wpnews.pro/news/per-tab-claude-code-session-resume-in-agterm-zsh-each-tab-resumes-its-own-after", "markdown": "https://wpnews.pro/news/per-tab-claude-code-session-resume-in-agterm-zsh-each-tab-resumes-its-own-after.md", "text": "https://wpnews.pro/news/per-tab-claude-code-session-resume-in-agterm-zsh-each-tab-resumes-its-own-after.txt", "jsonld": "https://wpnews.pro/news/per-tab-claude-code-session-resume-in-agterm-zsh-each-tab-resumes-its-own-after.jsonld"}}