Skip to content

fix(tui): dedupe concurrent bootstraps to prevent first-run freeze - #110

Open
lorenzozanee wants to merge 1 commit into
CyberStrikeus:mainfrom
lorenzozanee:fix/tui-bootstrap-singleflight
Open

fix(tui): dedupe concurrent bootstraps to prevent first-run freeze#110
lorenzozanee wants to merge 1 commit into
CyberStrikeus:mainfrom
lorenzozanee:fix/tui-bootstrap-singleflight

Conversation

@lorenzozanee

Copy link
Copy Markdown

What does this PR do?

Fixes #66.

During first-time provider setup, two bootstrap triggers fire concurrently:

  • dialog-provider.tsx awaits sync.bootstrap() right after instance.dispose() (4 call sites)
  • the SSE server.instance.disposed handler calls bootstrap() (packages/cyberstrike/src/cli/cmd/tui/context/sync.tsx:301)

The disposed event is emitted before the dispose response returns, so both runs overlap on the first-run auth flow and race the global store transitions. This wraps bootstrap in a new singleflight helper (packages/cyberstrike/src/util/singleflight.ts) so concurrent calls share one in-flight promise. The guard clears on settle, so later disposed/reload events still trigger a fresh bootstrap. Single, non-overlapping bootstraps behave exactly as before.

Type of change

  • Bug fix
  • New feature / agent
  • Security tool / MCP server / Bolt plugin
  • Agent skill / knowledge base
  • UI / TUI improvement
  • Documentation
  • Refactor / performance
  • CI / infrastructure

Security impact

  • This PR adds or modifies tool execution (shell, file, network)
  • This PR changes agent permissions or scope
  • This PR modifies authentication / authorization logic
  • This PR has no security impact

How did you verify it works?

  • New unit tests in packages/cyberstrike/test/util/singleflight.test.ts cover the dedup contract: concurrent calls share one execution, re-execution after the promise settles, rejection propagates to all callers and clears the guard, and sequential calls each run. bun test test/util/ passes (28 tests) and bun test test/cli/tui/ passes.
  • bun run typecheck (tsgo) passes in packages/cyberstrike.

Checklist

  • bun turbo typecheck passes
  • Tested locally with at least one LLM provider
  • PR is focused on a single change
  • No secrets, credentials, or API keys in the diff
  • Breaking changes are documented (if any)

During first-time provider setup the disposed server instance fires
server.instance.disposed, which triggers sync.bootstrap() from the SSE
handler while the provider dialog awaits its own sync.bootstrap() right
after instance.dispose(). The two runs overlap and race the global store
transitions.

Wrap bootstrap in a singleflight helper so concurrent calls share one
in-flight promise; the guard clears on settle so later disposed/reload
events still re-bootstrap.
@lorenzozanee
lorenzozanee requested a review from badchars as a code owner August 28, 2026 21:16
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 freezes after first-time provider setup (model selection)

1 participant