Conversation
… panes a3a0ef2 added reap_stale_clients to free --max-clients slots from abandoned tabs, keyed on tmux client_activity past CLIENT_IDLE_MAX (1h). But client_activity only advances on terminal I/O, NOT on the WebSocket keepalive pings (app.py ping_interval=25) that keep a quiet pane's socket warm. A wall tile watching an agent that produces no output for an hour — the normal case — looked idle and got force-detached, dropping a live pane to ttyd's reconnect screen (observed on the ccbot wall, disconnecting ~1h in). tmux can't tell an abandoned tab from a live-but-quiet one, so any activity-based reaper kills real sessions. Remove the reaper, its call, and CLIENT_IDLE_MAX; keep --max-clients=10. Dead sockets are already reclaimed by the keepalive path (a failed ping raises in the proxy pumps, ttyd drops the client, tmux frees the slot); the cap absorbs the rest. Documented in a WHY-NO-REAPER note above the poll loop. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Follow-up to dropping the activity-based reaper. Grouped tmux sessions share one real window with a single size; under `window-size largest`, a wall left open in a backgrounded tab keeps its ttyd WebSocket alive forever (keepalive pings auto-pong while hidden) and pins every shared window to that ghost's dimensions — so the wall you're actively viewing gets its rows/cols cropped to the stale tab's size (and a horizontal scrollbar appears). Terminal activity can't distinguish a quiet-but-watched pane from an abandoned tab, which is exactly why the server-side reaper was wrong. Page Visibility can: it reports when THIS tab is hidden. On visibilitychange to hidden, after a 45s grace, blank the ttyd iframes (src=about:blank) so their sockets close and the backing tmux clients drop, freeing window-size; on return, restore each iframe's src in place so it reconnects sized to the now-visible tile. termTick and _absorbFreshTerminals are guarded so nothing reconnects an iframe while suspended; _restoreTermFrames re-ticks to reconcile any adds/drops missed while hidden. A quick tab flip stays under the grace, so there's no needless reload. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…0s lag) The sidebar agent dots only re-rendered on the global 30s refresh() loop, while the wall pane dots recolour every 4s from termTick — both off the same /api/agents session_status. So an agent that went idle (e.g. carmel) stayed "busy" green in the sidebar for up to 30s after the wall had already gone grey. Add syncSidebarDots(agents): recolour the sidebar .health-dot elements in place (no list rebuild — rows are name-sorted, so status never reorders them) and call it from termTick off the same poll that refreshes the wall dots. The sidebar now tracks the wall in lockstep; row add/remove still rides the 30s refreshSidebar. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ion gate) The visibility teardown blanked every ttyd iframe when the tab went hidden, which needlessly churned the connection for the common case: a single wall watching its agents, no second viewer. Blanking a sole viewer has no upside — a tmux window has one size shared by all its clients, so a lone client always fits; cropping only happens when 2+ differently-sized clients share a window. Gate teardown on actual contention. New GET /api/term/clients returns per-wid ttyd client counts (one `tmux list-clients` call, counted by grouped-session name, mirroring scripts/agent-terminals.sh). On hide, the wall fetches it and blanks only panes whose window has >1 viewer; sole-viewer panes stay connected, so a backgrounded single wall keeps its terminals and returns with no reconnect. It enters the suspended state only if it actually released something, and aborts if the tab became visible mid-fetch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Devail1
added a commit
that referenced
this pull request
Jun 22, 2026
webterm: fix wall session disconnects, stale-tab cropping, and sidebar status lag
Devail1
added a commit
that referenced
this pull request
Jun 23, 2026
webterm: fix wall session disconnects, stale-tab cropping, and sidebar status lag
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Four related fixes to the agent terminal wall, all stemming from how grouped tmux sessions share windows and how status is polled.
Commits
e251b74— drop the activity-based client reaper that disconnected live panes.The reaper (
a3a0ef2) freed--max-clientsslots by detaching wall clients idle past a timeout, keyed on tmuxclient_activity. But that only advances on terminal I/O, not on the WebSocket keepalive pings that keep a quiet pane's socket warm — so an agent terminal that simply produced no output for an hour got force-detached, dropping a live pane to ttyd's reconnect screen (observed on the ccbot wall ~1h in). tmux can't tell an abandoned tab from a live-but-quiet one, so any activity-based reaper kills real sessions. Removed it; genuinely dead sockets are already reclaimed by the keepalive path, and--max-clients=10absorbs the rest.5ffb43d— release ttyd clients when the wall tab is backgrounded.Grouped tmux sessions share one real window with a single size; under
window-size largest, a wall left open in a backgrounded tab keeps its WebSocket alive forever (keepalive pings auto-pong while hidden) and pins every shared window to that ghost's dimensions — cropping the wall you're actively viewing. Page Visibility can distinguish a hidden tab from a quiet-but-watched one: onvisibilitychangeto hidden, after a 45s grace, blank the ttyd iframes (closing their sockets so the tmux clients drop) and reconnect on return. Fixes the cropping without reintroducing the disconnect.a2f2ecb— sync sidebar status dots to the wall's 4s tick.Sidebar dots only re-rendered on the global 30s
refresh()loop while the wall pane dots recolour every 4s, both off the same/api/agentssession_status. So an agent that went idle stayed green "busy" in the sidebar for up to 30s after the wall went grey. AddedsyncSidebarDots()(in-place recolour, no list rebuild) called fromtermTickoff the same poll. Now they track in lockstep.c81e481— only release backgrounded panes that have >1 viewer (contention gate).Refines the teardown so it never disturbs a sole viewer. Blanking the only client of a window has no upside (a tmux window has one size shared by all its clients, so a lone client always fits — cropping needs 2+ differently-sized clients). New
GET /api/term/clientsreturns per-wid ttyd client counts (onetmux list-clients, counted by grouped-session name, mirroringagent-terminals.sh); the wall blanks only panes whose window has >1 viewer. So a backgrounded single wall keeps its terminals and returns with no reconnect; teardown fires only when a second tab/device is also viewing the pane.Notes
/api/term/clients); the rest is client-side JS + the terminal supervisor shell script.claude agents --jsonreports a session with a live background shell asbusy, not a dashboard bug.🤖 Generated with Claude Code