Created
January 30, 2026 18:19
-
-
Save jalehman/17bce9c362d46110f7ce46debb391592 to your computer and use it in GitHub Desktop.
Review: Per-Project Tmux Sessions (cic-345)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Review: Per-Project Tmux Sessions (cic-345)</title> | |
| <style> | |
| * { margin: 0; padding: 0; box-sizing: border-box; } | |
| body { | |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; | |
| background: #0d1117; | |
| color: #c9d1d9; | |
| padding: 24px; | |
| max-width: 960px; | |
| margin: 0 auto; | |
| } | |
| h1 { font-size: 1.5em; color: #58a6ff; margin-bottom: 4px; } | |
| .subtitle { color: #8b949e; font-size: 0.9em; margin-bottom: 24px; } | |
| h2 { | |
| font-size: 1.1em; color: #f0f6fc; | |
| margin: 24px 0 12px; | |
| border-bottom: 1px solid #21262d; | |
| padding-bottom: 6px; | |
| } | |
| h3 { font-size: 0.95em; color: #d2a8ff; margin: 16px 0 8px; } | |
| .stats { | |
| display: flex; gap: 10px; flex-wrap: wrap; margin: 14px 0; | |
| } | |
| .stat { | |
| background: #161b22; border: 1px solid #30363d; border-radius: 8px; | |
| padding: 10px 16px; text-align: center; flex: 1; min-width: 90px; | |
| } | |
| .stat .num { font-size: 1.4em; font-weight: 700; color: #58a6ff; } | |
| .stat .num.green { color: #3fb950; } | |
| .stat .num.red { color: #f85149; } | |
| .stat .lbl { font-size: 0.72em; color: #8b949e; margin-top: 2px; } | |
| .card { | |
| background: #161b22; border: 1px solid #30363d; border-radius: 10px; | |
| padding: 16px 20px; margin: 12px 0; | |
| } | |
| .card-title { | |
| font-size: 0.78em; text-transform: uppercase; letter-spacing: 0.8px; | |
| color: #8b949e; margin-bottom: 10px; | |
| } | |
| code, .mono { | |
| font-family: 'SF Mono', 'Fira Code', monospace; | |
| font-size: 0.85em; | |
| } | |
| code { | |
| background: #1c2128; padding: 2px 6px; border-radius: 4px; color: #79c0ff; | |
| } | |
| .flow { | |
| display: flex; align-items: center; justify-content: center; | |
| gap: 0; margin: 16px 0; flex-wrap: wrap; | |
| } | |
| .flow-box { | |
| background: #161b22; border: 2px solid #30363d; border-radius: 10px; | |
| padding: 12px 16px; text-align: center; min-width: 120px; | |
| } | |
| .flow-box.old { border-color: #f8514966; background: #f8514911; } | |
| .flow-box.new { border-color: #3fb95066; background: #3fb95011; } | |
| .flow-box .label { font-size: 0.75em; color: #8b949e; text-transform: uppercase; letter-spacing: 0.5px; } | |
| .flow-box .value { font-size: 1em; font-weight: 600; margin-top: 3px; } | |
| .flow-box.old .value { color: #f85149; } | |
| .flow-box.new .value { color: #3fb950; } | |
| .flow-arrow { font-size: 1.3em; color: #484f58; padding: 0 10px; } | |
| .file-list { list-style: none; } | |
| .file-list li { | |
| padding: 8px 12px; border-bottom: 1px solid #21262d; | |
| display: flex; align-items: center; gap: 10px; | |
| } | |
| .file-list li:last-child { border-bottom: none; } | |
| .file-path { color: #79c0ff; font-family: 'SF Mono', 'Fira Code', monospace; font-size: 0.82em; flex: 1; } | |
| .file-stat { font-size: 0.78em; white-space: nowrap; } | |
| .file-stat .add { color: #3fb950; } | |
| .file-stat .del { color: #f85149; } | |
| .diff-block { | |
| background: #0d1117; border: 1px solid #30363d; border-radius: 6px; | |
| padding: 14px 16px; margin: 10px 0; overflow-x: auto; | |
| font-family: 'SF Mono', 'Fira Code', monospace; font-size: 0.8em; line-height: 1.6; | |
| } | |
| .diff-block .add { color: #3fb950; } | |
| .diff-block .del { color: #f85149; text-decoration: line-through; opacity: 0.7; } | |
| .diff-block .ctx { color: #8b949e; } | |
| .diff-block .hdr { color: #d2a8ff; font-weight: 600; } | |
| .checklist { list-style: none; margin: 8px 0; } | |
| .checklist li { padding: 6px 0; display: flex; gap: 8px; align-items: flex-start; } | |
| .checklist li::before { content: '☐'; color: #484f58; font-size: 1.1em; } | |
| .note { | |
| background: #1f6feb22; border-left: 3px solid #1f6feb; | |
| padding: 10px 14px; border-radius: 0 6px 6px 0; | |
| margin: 12px 0; font-size: 0.9em; | |
| } | |
| .note.warn { | |
| background: #d2992222; border-left-color: #d29922; | |
| } | |
| .tag { | |
| display: inline-block; font-size: 0.7em; padding: 2px 8px; | |
| border-radius: 4px; font-weight: 600; letter-spacing: 0.3px; | |
| } | |
| .tag.scope { background: #1f6feb33; color: #58a6ff; } | |
| .tag.note-tag { background: #d2992233; color: #d29922; } | |
| .footer { | |
| margin-top: 28px; padding-top: 14px; border-top: 1px solid #21262d; | |
| color: #484f58; font-size: 0.78em; text-align: center; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <h1>🔍 Review: Per-Project Tmux Sessions</h1> | |
| <p class="subtitle">cic-345 · Worker: Napoleon · 4 files changed</p> | |
| <div class="stats"> | |
| <div class="stat"><div class="num green">+107</div><div class="lbl">Additions</div></div> | |
| <div class="stat"><div class="num red">-48</div><div class="lbl">Deletions</div></div> | |
| <div class="stat"><div class="num">4</div><div class="lbl">Files</div></div> | |
| <div class="stat"><div class="num">3</div><div class="lbl">New Functions</div></div> | |
| </div> | |
| <h2>What Changed</h2> | |
| <div class="flow"> | |
| <div class="flow-box old"> | |
| <div class="label">Before</div> | |
| <div class="value">Single "claude-team" session</div> | |
| </div> | |
| <div class="flow-arrow">→</div> | |
| <div class="flow-box new"> | |
| <div class="label">After</div> | |
| <div class="value">Per-project sessions</div> | |
| </div> | |
| </div> | |
| <div class="card"> | |
| <div class="card-title">Session Naming Scheme</div> | |
| <div class="diff-block"> | |
| <span class="del">TMUX_SESSION_NAME = "claude-team"</span><br> | |
| <span class="add">TMUX_SESSION_PREFIX = "claude-team"</span><br> | |
| <span class="add">Format: claude-team-{slug}-{sha1_hash[:8]}</span><br><br> | |
| <span class="ctx">Example:</span><br> | |
| <span class="add">claude-team-claude-team-a1b2c3d4</span><br> | |
| <span class="add">claude-team-my-project-f9e8d7c6</span> | |
| </div> | |
| </div> | |
| <h2>New Functions</h2> | |
| <div class="card"> | |
| <div class="card-title">tmux.py — 3 new module-level functions</div> | |
| <div class="diff-block"> | |
| <span class="hdr">_tmux_safe_slug(value: str) → str</span><br> | |
| <span class="ctx">Normalizes project name into tmux-safe slug. Strips non-alphanumeric chars,</span><br> | |
| <span class="ctx">truncates to 32 chars, falls back to "project".</span><br><br> | |
| <span class="hdr">project_name_from_path(project_path: str | None) → str | None</span><br> | |
| <span class="ctx">Extracts display name from path. Handles .worktrees/ paths by returning</span><br> | |
| <span class="ctx">the parent dir name (the actual project). Moved from instance method.</span><br><br> | |
| <span class="hdr">tmux_session_name_for_project(project_path: str | None) → str</span><br> | |
| <span class="ctx">Computes the per-project session name:</span><br> | |
| <span class="add"> prefix + slug(project_name) + sha1(project_path)[:8]</span><br><br> | |
| <span class="hdr">_is_managed_session_name(session_name: str) → bool</span><br> | |
| <span class="ctx">Checks if a tmux session is claude-team-managed (starts with "claude-team-").</span> | |
| </div> | |
| </div> | |
| <h2>Touch Points Modified</h2> | |
| <div class="card"> | |
| <div class="card-title">Files Changed</div> | |
| <ul class="file-list"> | |
| <li> | |
| <span class="file-path">src/claude_team_mcp/terminal_backends/tmux.py</span> | |
| <span class="file-stat"><span class="add">+57</span> <span class="del">-22</span></span> | |
| </li> | |
| <li> | |
| <span class="file-path">tests/test_tmux_backend.py</span> | |
| <span class="file-stat"><span class="add">+48</span> <span class="del">-24</span></span> | |
| </li> | |
| <li> | |
| <span class="file-path">src/claude_team_mcp/tools/discover_workers.py</span> | |
| <span class="file-stat"><span class="add">+1</span> <span class="del">-1</span></span> | |
| </li> | |
| <li> | |
| <span class="file-path">src/claude_team_mcp/tools/spawn_workers.py</span> | |
| <span class="file-stat"><span class="add">+1</span> <span class="del">-1</span></span> | |
| </li> | |
| </ul> | |
| </div> | |
| <h2>Key Design Decisions</h2> | |
| <div class="card"> | |
| <div class="card-title">Session Discovery</div> | |
| <div class="diff-block"> | |
| <span class="del">list-panes -t claude-team</span> <span class="ctx">(target single session)</span><br> | |
| <span class="add">list-panes -a</span> <span class="ctx">(scan all, filter by prefix)</span> | |
| </div> | |
| <p style="margin-top: 10px; font-size: 0.88em; color: #c9d1d9;"> | |
| <code>list_sessions()</code> and <code>_find_available_window()</code> now scan all tmux panes | |
| and filter with <code>_is_managed_session_name()</code>. This ensures discovery works across | |
| multiple project sessions. | |
| </p> | |
| </div> | |
| <div class="card"> | |
| <div class="card-title">Instance Method → Module Function</div> | |
| <p style="font-size: 0.88em;"> | |
| <code>_project_name_from_path()</code> moved from instance method to module-level | |
| <code>project_name_from_path()</code> — now reusable by <code>tmux_session_name_for_project()</code>. | |
| </p> | |
| </div> | |
| <h2>Potential Concerns</h2> | |
| <div class="note warn"> | |
| <strong>⚠️ Scanning all panes:</strong> <code>list-panes -a</code> scans every tmux session on the system, | |
| not just claude-team ones. The prefix filter keeps it safe, but on machines with hundreds of tmux | |
| sessions this could be slightly slower than targeting a specific session. | |
| </div> | |
| <div class="note warn"> | |
| <strong>⚠️ Hash collisions:</strong> SHA1 truncated to 8 hex chars (32 bits) means collision risk | |
| across ~65K projects. Fine in practice, but worth noting. | |
| </div> | |
| <div class="note warn"> | |
| <strong>⚠️ Asked to scope, implemented instead:</strong> Napoleon was asked to scope only but went ahead | |
| and implemented. Changes look reasonable but weren't reviewed before coding. | |
| </div> | |
| <h2>Test Coverage</h2> | |
| <div class="card"> | |
| <div class="card-title">Updated Tests</div> | |
| <p style="font-size: 0.88em; margin-bottom: 10px;"> | |
| All existing tmux tests updated to use per-project session names via | |
| <code>tmux_session_name_for_project()</code>. Tests now verify: | |
| </p> | |
| <ul class="checklist"> | |
| <li>Pane listing filters by managed session prefix</li> | |
| <li>Unrelated tmux sessions are excluded from results</li> | |
| <li>Session creation uses computed per-project name</li> | |
| <li>Window finding works across multiple managed sessions</li> | |
| <li>Full filter respects managed-only constraint</li> | |
| </ul> | |
| </div> | |
| <h2>Review Checklist</h2> | |
| <ul class="checklist"> | |
| <li>Naming scheme makes sense for local monitoring?</li> | |
| <li>OK with <code>list-panes -a</code> scanning approach?</li> | |
| <li>Hash length (8 chars) sufficient?</li> | |
| <li>Run tests: <code>uv run pytest tests/test_tmux_backend.py -v</code></li> | |
| <li>Manual test: spawn workers for 2 different projects, verify separate sessions</li> | |
| </ul> | |
| <div class="footer"> | |
| Worker: Napoleon (08fed2c0) · cic-345 · claude-team | |
| </div> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment