Process-level "active session in this repo" tracking, with built-in cross-repo + multi-tmux collision guards

Resolved 💬 2 comments Opened May 19, 2026 by ivanemadrigal Closed Jul 9, 2026

Summary

Claude Code has no native concept of "this git repo has another Claude session active right now." The only state shared between sessions is the filesystem itself (git index, HEAD, working tree). As a result, every long-time user who runs more than one Claude session in adjacent contexts (multi-repo workflow, scheduled background agent, parallel tmux panes) eventually hits a collision where one session moves HEAD or stashes work in a repo another session is actively using.

I'm filing this after the third confirmed incident in 16 days. Each time I wrote a new behavioral rule in my project CLAUDE.md to prevent the next instance of that exact shape; each new rule was a band-aid that did not prevent the next collision. Behavioral rules in project config can't compose with arbitrary new agents (background, scheduled, third-party MCP, future-Claude-feature). The primitive needs to live in the platform.

Concrete failure modes (all from one user's machine over 16 days)

Incident 1 — 2026-05-03: two interactive tmux sessions, same repo, stash collision

Two tmux sessions (CLEO, CLE_ENG) were both attached to my CLEO repo. Session A had uncommitted edits to cleo_email_analyst.py. Session B opened the same repo in another pane, ran git stash to clear the working tree to checkout a branch, and never restored. Session A's edits appeared lost; recovery required walking the stash list + reflog. The two Claude sessions had no way to know about each other.

Band-aid I shipped: a ~/.claude/CLAUDE.md global rule "one tmux session per repo, period."

Incident 2 — 2026-05-14: cross-repo automation moved HEAD out from under a live session

During a multi-repo rollout, my CLEO session ran git checkout -b feat/cleo-event-emit against the primary working trees of LIT, MLOG, 2026-Budget, and synaptic-systems-growth. All four of those repos had live tmux sessions attached and uncommitted work in progress. My LIT session was on qa/leave-v7-defensive-refutation-2026-05-10; the cross-repo automation switched LIT's HEAD to main while I was mid-edit. Recovery required git reflog on every affected repo.

Band-aid I shipped: a ~/.claude/CLAUDE.md global rule "cross-repo automation must use isolated worktrees under /tmp/" and a local block-main-writes.sh hook extension to refuse cross-repo HEAD-moving ops without a # allow-cross-repo annotation.

Incident 3 — 2026-05-19: continuous operational tax

Articulated by me today: "no matter how tempting it is to have multiple windows open with the same repo I spend more time on cleanup even if they intend not to touch each other." The pain is no longer episodic — it's a continuous tax on doing multi-domain work, because every operation in any repo that has even one other session attached carries non-zero collision risk that I have to keep mentally tracked.

What's missing in the platform

A process-level lock that:

  1. Every Claude session boot CLAIMS a lock on its primary repo. The lock identifies which session it is (whatever stable identifier Claude can expose — session id, tmux pane id, etc.) and a timestamp.
  2. Every HEAD-moving git op CHECKS the lock before executing — both for the current repo (refuse if another session holds the lock) and for any -C <other-repo> / cross-repo target (refuse unless the operator has explicitly acknowledged).
  3. Locks expire on a reasonable TTL (30 min works well for me) so a crashed/forgotten session doesn't hold a lock forever.
  4. Locks are visible — a session can ask "which other sessions are in this repo right now?" and get a real answer instead of a tmux ls heuristic.

This is a ~50-line piece of platform code. I've implemented a local-only version (~/.claude/scripts/repo-session-lock.sh, called from the SessionStart hook + block-main-writes.sh) and it works for my topology. But it doesn't help anyone else, and it can't enforce against agents that don't call into my hooks.

Why the user-layer fix isn't sufficient

  • Background agents (scheduled tasks, MCP servers, CLI invocations from other tools) don't call my SessionStart hook.
  • Future Claude Code features (worktree auto-creation, multi-pane orchestration, etc.) won't know about my user-layer scripts.
  • Every new user has to discover the failure pattern, write their own band-aid, and live with the residual risk.
  • The most ergonomic place to put the lock is at the same layer that knows "I just spawned a Claude session in <dir>" — i.e., the Claude Code launcher itself.

Proposed minimal platform shape

~/.claude/sessions.lock  (or per-repo .git/.claude-active-session)
  Schema:  {session_id, repo_root, tmux_pane?, started_at, last_seen}
  TTL:     30 min (configurable via setting)

New env exposed to hooks:
  CLAUDE_SESSION_ID         (stable across that session)
  CLAUDE_PRIMARY_REPO       (resolved at boot)
  CLAUDE_OTHER_SESSIONS_IN_PRIMARY_REPO  (JSON list, refreshed on each tool call)

Hook lifecycle:
  - SessionStart: claim the lock for $CLAUDE_PRIMARY_REPO
  - SessionEnd:   release the lock
  - PreToolUse (Bash): inspect $CLAUDE_OTHER_SESSIONS_* before running HEAD-moving git ops

New CLI:
  claude-code locks list                  # show all active locks across all repos
  claude-code locks release <repo>        # admin release (cleanup)

The HEAD-mover guard could be the same shape as the existing block-main-writes.sh pattern — Claude Code already supports PreToolUse hooks and the override-annotation pattern works well for legitimate one-shot cross-repo ops.

Documentation references

If it helps to see what a user-layer band-aid looks like, here are the three layers I'm currently maintaining locally:

  • ~/.claude/scripts/check-parallel-tmux.sh — SessionStart sentinel that warns about parallel tmux sessions in the same repo
  • ~/.claude/scripts/repo-session-lock.sh — process-level lock primitive (claim/check/release/list)
  • ~/.claude/scripts/block-main-writes.sh — PreToolUse hook that refuses main-branch mutators AND cross-repo HEAD movers

Happy to share the source if useful as a reference implementation.

Severity / frequency

P1 from a single-user perspective. The blast radius is bounded (it's per-machine, not network-side) but every collision costs 30-90 min of recovery, and the residual mental load between collisions is non-zero.

Acceptance criteria for closing this issue

  1. A stable CLAUDE_SESSION_ID env var available to hooks
  2. A documented mechanism for hooks to ask "which other Claude sessions are in this repo right now?"
  3. Reference documentation for the recommended cross-repo-via-worktree pattern in the official docs (not just user-layer rules)

I do NOT expect Anthropic to ship the full guard policy — most users will be fine without it. But the primitive (session id + cross-session visibility) is what enables every user to write their own guard without inventing the wheel.

---

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗