Skip to content

Instantly share code, notes, and snippets.

@jalehman
Created January 30, 2026 18:19
Show Gist options
  • Select an option

  • Save jalehman/17bce9c362d46110f7ce46debb391592 to your computer and use it in GitHub Desktop.

Select an option

Save jalehman/17bce9c362d46110f7ce46debb391592 to your computer and use it in GitHub Desktop.
Review: Per-Project Tmux Sessions (cic-345)
<!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