Skip to content

fix(dashboard): blank agent-spawned terminals, inflated bridge count, Centrifugo reconnect flapping - #98

Merged
fkesheh merged 2 commits into
mainfrom
fix/terminal-watch-and-bridge-dedupe
Sep 5, 2026
Merged

fkesheh merged 2 commits into
mainfrom
fix/terminal-watch-and-bridge-dedupe

Conversation

@fkesheh

@fkesheh fkesheh commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Fixes three things the user hit in the cloud dashboard, all verified in a real browser.

1. Blank terminal for agent-spawned / re-run sessions

A session created or re-run by another agent (via the local API / fticket, not the panel) showed a blank pane with no output, on every device, while the session was clearly running.

Cause: the bridge served terminal output only when it considered the session "known", and that check (index.ts isKnownSession) counted only sessions with a PTY attached in the current process. A session alive in tmux but not attached in-process failed it, so the browser's terminal_watch was silently dropped and publish-router.ts published nothing.

Fix: isKnownSession also counts live tmux sessions (runner.hasTmuxSession), and the first watcher triggers a read-only reattach to the tmux session, reusing the existing resurrection path (no init keystroke, no agent restart), so output streams. Dropped watches are now logged. Defensively, the UI (useSessions.ts) preserves a known bridgeId across row merges so a partial update can never blank the pane.

2. "N bridges" too high (user saw 4 for one bridge)

useBridges.ts keyed the list by Centrifugo connection id, so one bridge that reconnected with a lingering old connection counted twice. It now tracks every connection per bridgeId and exposes one entry per bridge (newest connection wins); a bridge disappears only when its last connection leaves, so there is no transient "0 bridges" that would disable the + button.

3. Centrifugo reconnect flapping (the source of the duplicate connections)

The bridge subscribed to channels it only publishes to (sessions:updates, per-terminal terminal:), so Centrifugo echoed its entire output stream back; the app-level ping then queued behind that backlog and the client declared "no ping" and reconnected about once a minute, each reconnect re-publishing everything and feeding the loop.

Fixes: remove the publish-only subscriptions (publishing is permitted by allow_publish_for_client, verified against centrifugo/config.json); set maxServerPingDelay; throttle the reconnect re-sync and skip finished sessions; cache the tmux liveness probe so it stops spawning tmux has-session on every watch heartbeat; add explicit ping settings to the Solo hub config.

Verification

  • 772 bridge tests, UI hook tests, tsc and build all pass.
  • Isolated Docker run of this branch (nothing touched host ~/.ftown): an agent-spawned session created through the local API rendered its terminal and echoed a marker in a real browser — proving both the reattach fix and that terminal output still publishes after the subscriptions were removed. A panel-created session also round-tripped. Zero flapping keywords and zero publish permission errors over the run. Screenshot in the review notes.

Follow-ups noted, not in this PR

  • events: still uses subscribe-before-publish (minor residual echo).
  • ~/.ftown/bridge.json and loops.json are written from $HOME regardless of --data-dir, so a Solo instance clobbers a co-resident bridge's pointer file. This bit us during testing.
  • Reattach on watch doesn't retry a transient failure until the watcher TTL sweeps; terminal-log seeding (scrollback on first attach) deferred.

🤖 Generated with Claude Code

…ridge count, and Centrifugo reconnect flapping

Three user-visible problems, one shared cause in how the bridge and the
dashboard model sessions and connections.

Blank terminal pane. The bridge served terminal output only for sessions
whose PTY was attached in the current process, checked via an in-memory
map. A session alive in tmux but not attached in-process — the normal
state right after another agent spawns or re-runs one — failed that
check, so the browser's watch request was dropped and nothing was
published, on every device. The liveness check now also counts live tmux
sessions, and the first watcher reattaches to the tmux session (reusing
the existing resurrection path, read-only, no init keystroke) so output
streams. A missing bridgeId on a session row can no longer blank the
pane: the UI preserves the known bridgeId across merges.

"N bridges" too high. The bridge list was keyed by Centrifugo connection
id, so one bridge that reconnected showed as several. It now tracks all
connections per bridgeId and shows one entry per bridge (newest
connection wins), and a bridge disappears only when its last connection
leaves — no transient "0 bridges" that would disable the + button.

Reconnect flapping (the source of the duplicate connections). The bridge
subscribed to channels it only publishes to, so Centrifugo echoed its
whole output stream back; the application-level ping then queued behind
that backlog and the client declared "no ping" and reconnected, roughly
once a minute. The publish-only subscriptions are removed (publishing is
allowed by allow_publish_for_client, verified), a maxServerPingDelay is
set, the reconnect re-sync is throttled and skips finished sessions, the
tmux liveness probe is cached, and the Solo hub config carries explicit
ping settings.

Verified in an isolated Docker container running this branch: an
agent-spawned session (created via the local API, not the panel)
rendered its terminal and echoed a marker in a real browser, proving
both the reattach fix and that terminal output still publishes after the
subscriptions were removed; no flapping and no publish errors over the
run. 772 bridge tests and the UI hook tests pass.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@vercel

vercel Bot commented Sep 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
ftown Ready Ready Preview Sep 5, 2026 2:23pm UTC

Request Review

…d-bridge-dedupe

# Conflicts:
#	bridge/package-lock.json
#	bridge/package.json
@fkesheh
fkesheh merged commit cc4a3b6 into main Sep 5, 2026
6 checks passed
@fkesheh
fkesheh deleted the fix/terminal-watch-and-bridge-dedupe branch September 5, 2026 14:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant