Skip to content

fix(tui): rehydrate pending permissions & questions on reconnect#36603

Open
Mykhol wants to merge 3 commits into
anomalyco:devfrom
Mykhol:fix/tui-hydrate-pending-permissions-on-reconnect
Open

fix(tui): rehydrate pending permissions & questions on reconnect#36603
Mykhol wants to merge 3 commits into
anomalyco:devfrom
Mykhol:fix/tui-hydrate-pending-permissions-on-reconnect

Conversation

@Mykhol

@Mykhol Mykhol commented Jul 13, 2026

Copy link
Copy Markdown

Issue for this PR

Closes #36604

Type of change

  • Bug fix

What does this PR do?

Pending permission/question prompts only live in server memory (a Deferred the tool blocks on) and are announced with a single permission.asked/question.asked event at ask time. The SSE stream only forwards events emitted after a client subscribes and never replays what's already pending, and the TUI's bootstrap() hydrated everything except these two stores. So if you detach the TUI while a prompt is pending — or one is raised while you're detached — re-attaching shows no prompt even though the agent is still blocked, and the only way out is to fork the session.

This adds hydratePending() to bootstrap(), which fetches GET /permission and GET /question on connect and seeds the stores. Inserts go through small upsert helpers that mirror the existing *.asked event handlers (binary-search by id, skip if already present), so hydration and any live events that arrive during bootstrap interleave without duplicating or clobbering. Auto-approve mode is honoured the same way the live handler does it. Subagent prompts surface via the existing root-session aggregation, so no extra work there.

How did you verify your code works?

Added a test in sync-live-hydration.test.tsx that seeds a pending permission and question on the mock server, emits no live event, and asserts the store is hydrated on connect. It fails against the current sync.tsx (sync.data.permission[sessionID] is undefined) and passes with hydratePending(), so it pins the exact regression. Full packages/tui suite passes (192 pass / 0 fail), bun run typecheck (tsgo) is clean, and oxlint reports 0 errors on the changed files. Both list calls are wrapped in .catch() so an older server without these endpoints falls back to current behaviour.

Screenshots / recordings

N/A — no visual change; this only re-populates prompts that already render today.

Checklist

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

Pending permission/question prompts live only in server memory (a Deferred
the tool blocks on) and are announced via a single fire-and-forget
permission.asked/question.asked event. The SSE stream never replays them to
a client that connects after the ask, and the TUI's bootstrap() hydrated
everything except these. So detaching the TUI while a prompt was pending (or
one raised while detached) left the agent blocked with no prompt shown and
no way to answer short of forking the session.

bootstrap() now calls hydratePending(), which fetches GET /permission and
GET /question and seeds the stores via idempotent upserts that mirror the
live *.asked event handlers (binary-search by id, skip if already present,
honour auto-approve mode). Subagent prompts surface through the existing
root-session aggregation in routes/session/index.tsx.
@github-actions

Copy link
Copy Markdown
Contributor

Thanks for your contribution!

This PR doesn't have a linked issue. All PRs must reference an existing issue.

Please:

  1. Open an issue describing the bug/feature (if one doesn't exist)
  2. Add Fixes #<number> or Closes #<number> to this PR description

See CONTRIBUTING.md for details.

@github-actions github-actions Bot added needs:issue needs:compliance This means the issue will auto-close after 2 hours. labels Jul 13, 2026
Reproduces the detach/reattach bug: with a pending permission and question
on the server and no live *.asked event, the reattached TUI must fetch them
on connect. This test fails on the pre-fix sync.tsx (store stays undefined)
and passes with hydratePending(). Adds /permission and /question defaults to
the sync fetch fixture.
@github-actions github-actions Bot removed needs:compliance This means the issue will auto-close after 2 hours. needs:issue labels Jul 13, 2026
@github-actions

Copy link
Copy Markdown
Contributor

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

Trim the two added comments to the file's sentence-case, why-focused
convention: drop the server-internal Deferred detail and keep the
client-side framing.
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.

TUI: pending permission/question prompts lost after detach + reattach, wedging the session

1 participant