main: serialize IPC-server swaps so the repo bind wins (T-352)
The 2.3.1 fix (re-fetch the pql sidebars on ProjectOpened) only helped
the picker-first path, where the project opens after the window is up.
With sticky-startup the project opens during boot, before the panes
mount and subscribe, so they never received the event — the sidebars
stayed broken.
Root cause is a race in the IPC-server lifecycle. The boot factory fires
swapIpcServer(launchCwd) with unawaited(); the project-open flow then
fires swapIpcServer(repo). Each swap stops the live server, binds a new
one, and reconnects the daemon client. Unserialized, the two interleave
and the late-finishing boot swap can clobber the repo bind, reconnecting
the client to the launch-CWD (HOME) socket. The daemon's PqlClient (and
git/files) then run against the wrong workspace, so the first
pql.tickets.list hits a stale/global pql.db and errors
("ticket_deps.blocker_record_id missing — pql.db is from an earlier
schema"). A manual refresh worked because by then things had settled.
Chain every swap on a serialization Future so they apply in call order;
the repo swap is issued last and therefore wins. Kept the pane re-fetch
from 2.3.1 — it still covers genuine mid-session project switches.
Verified app boot is unaffected (test/app_test.dart green).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,18 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Ticket and decision sidebars reliably load on first open (real fix).** The
|
||||
2.3.1 re-fetch-on-open helped only when a project is picked *after* the window
|
||||
is up; with sticky-startup the project opens during boot, before the panes
|
||||
mount, so they never saw the event. The underlying cause was a race: the boot
|
||||
IPC-server swap (to the launch CWD) and the project-open swap (to the repo)
|
||||
ran concurrently, and the late-finishing boot swap could clobber the repo
|
||||
bind — leaving the daemon's pql/git/files pointed at the launch directory
|
||||
(HOME) and the sidebars erroring on a stale/global pql.db. Swaps are now
|
||||
serialized so the repo bind always wins. (T-352)
|
||||
|
||||
## [2.3.1] — 2026-06-10
|
||||
|
||||
### Fixed
|
||||
|
||||
+21
-1
@@ -116,7 +116,11 @@ Future<void> main() async {
|
||||
McpServer? mcpServer;
|
||||
final ipcLog = Logger();
|
||||
|
||||
Future<void> swapIpcServer(DaemonDispatcher dispatcher, Directory workRoot) async {
|
||||
// IPC-server swaps must run one-at-a-time — see the swapIpcServer wrapper
|
||||
// below doSwapIpcServer for why. (T-352)
|
||||
Future<void> swapChain = Future<void>.value();
|
||||
|
||||
Future<void> doSwapIpcServer(DaemonDispatcher dispatcher, Directory workRoot) async {
|
||||
if (kIsWeb) return;
|
||||
// Already serving this exact workspace? Reuse the live server.
|
||||
// The startup factory binds the launch CWD, then the project-open
|
||||
@@ -173,6 +177,22 @@ Future<void> main() async {
|
||||
}
|
||||
}
|
||||
|
||||
// Serialize IPC-server swaps. The boot factory fires a swap to the launch
|
||||
// CWD with unawaited(); the project-open flow then fires another to the
|
||||
// real repo. Unserialized, the two interleave and the late-finishing boot
|
||||
// swap can clobber the repo bind — reconnecting the daemon client to the
|
||||
// launch-CWD (HOME) socket, so pql/git/files run against the wrong
|
||||
// workspace. That surfaced as the ticket/decision sidebars failing on first
|
||||
// load (stale/global pql.db) yet working after a manual refresh. Chaining
|
||||
// every swap makes them apply in call order; the repo swap is issued last
|
||||
// and therefore wins. (T-352)
|
||||
Future<void> swapIpcServer(DaemonDispatcher dispatcher, Directory workRoot) {
|
||||
final next = swapChain.then((_) => doSwapIpcServer(dispatcher, workRoot));
|
||||
// A failed swap must not break the chain for the next one.
|
||||
swapChain = next.catchError((Object _) {});
|
||||
return next;
|
||||
}
|
||||
|
||||
DaemonDispatcher buildDispatcher(
|
||||
DaemonBus events,
|
||||
Toolchain tc,
|
||||
|
||||
Reference in New Issue
Block a user