Skip to content

fix(tui): poll active sessions for external-writer changes (#31073)#31322

Open
lexlian wants to merge 2 commits into
anomalyco:devfrom
lexlian:fix/tui-external-session-sync
Open

fix(tui): poll active sessions for external-writer changes (#31073)#31322
lexlian wants to merge 2 commits into
anomalyco:devfrom
lexlian:fix/tui-external-session-sync

Conversation

@lexlian
Copy link
Copy Markdown

@lexlian lexlian commented Jun 8, 2026

Issue for this PR

Closes #31073

Type of change

  • Bug fix

What does this PR do?

After the first successful hydration of a session, sync.session.sync() short-circuits via a fullSyncedSessions set, so updates produced by another opencode client, a remote agent, or opencode serve running the same session never reach this TUI unless they happen to land on its own SSE stream. The session view stays frozen until the user navigates away and back.

The fix:

  • Adds an opt-in force?: boolean option to sync.session.sync() (packages/tui/src/context/sync.tsx) that bypasses the once-only cache while still de-duping concurrent in-flight calls via syncingSessions. Default behavior is unchanged for every existing caller.
  • Adds a new external_sync_interval_ms key (packages/tui/src/config/index.tsx, range 0-60000, default 2000, 0 disables) so the polling cadence is configurable in tui.json.
  • Adds a createEffect in the session route (packages/tui/src/routes/session/index.tsx) that polls sync.session.sync(id, { force: true }) at the configured interval only while the session's status is working or compacting, and uses onCleanup to clear the interval on route change, idle transition, and unmount.

Why it works: the force flag lets the polling effect re-fetch the active session's state on a timer, bypassing the once-only cache that previously made sync.session.sync() a no-op forever after first hydration. The poll is bounded — it only runs while a session is being driven (working / compacting) and stops the moment the session goes idle, the route changes, or the interval is set to 0. When the SSE stream is healthy the local store already has the latest data and the reconcile is incremental, so the poll is effectively a no-op. It exists purely as a safety net for external writers and dropped events.

How did you verify your code works?

  • bun test in packages/tui: 190 pass, 1 skip, 0 fail (full suite, no regressions)
  • bun test test/cli/cmd/tui/sync.test.tsx test/config.test.tsx: 15 pass, 0 fail (4 new sync tests + 3 new config tests)
  • bun run typecheck in packages/tui: clean
  • bun typecheck in packages/opencode: clean
  • Push triggered monorepo-wide typecheck via Turbo cache: 23/23 tasks successful

The 4 new tests in test/cli/cmd/tui/sync.test.tsx cover the regression directly:

  • a second sync(id) call is a no-op (cache hit)
  • a sync(id, { force: true }) issues a new round of fetches
  • two parallel force: true calls share a single underlying fetch (de-dup)
  • a force re-sync after a message.updated SSE event still issues 4 fetches instead of 0

Screenshots / recordings

Not applicable. The bug requires two opencode clients (or one client + opencode serve) writing to the same session, and the fix is invisible to a single TUI running standalone — a screenshot of one TUI cannot show the regression or the fix in action. The change is verified by the 4 new automated tests above.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

…#31073)

Closes anomalyco#31073

After the first hydration, `sync.session.sync()` short-circuits via
`fullSyncedSessions`, so updates produced by another opencode client /
remote runner / `opencode serve` never reach this TUI unless they
arrive over the local SSE stream.

Add an opt-in `force` option to `sync.session.sync()` that bypasses
the once-only cache while still de-duping in-flight calls, and add a
bounded polling effect in the session route that re-syncs the active
session while its status is `working` or `compacting`. The interval is
configurable via the new `external_sync_interval_ms` tui.json key
(default 2000ms, range 0-60000, 0 disables). The poll is cleaned up on
route change, idle transition, and unmount.
@github-actions github-actions Bot added the needs:compliance This means the issue will auto-close after 2 hours. label Jun 8, 2026
@github-actions github-actions Bot removed the needs:compliance This means the issue will auto-close after 2 hours. label Jun 8, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 8, 2026

Thanks for updating your PR! It now meets our contributing guidelines. 👍

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.

[BUG] TUI session view doesn't reflect external updates to an active session

1 participant