instar 1.3.648 → 1.3.649

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,39 @@
1
+ # Side-Effects Review — WorktreeMonitor async git scans
2
+
3
+ **Version / slug:** `worktree-monitor-async-git`
4
+ **Date:** `2026-06-23`
5
+ **Author:** Echo (autonomous)
6
+ **Tier:** 1 (behavior-preserving async refactor — no new feature, no decision points, no dark-gate, no migration)
7
+ **Second-pass reviewer:** not-required (Tier 1; small, low-risk, fully covered by existing unit tests)
8
+
9
+ ## Summary of the change
10
+
11
+ `WorktreeMonitor.scanWorktrees()` issues several git commands — `git worktree list --porcelain` plus per-worktree `rev-list`/`diff` — through a private `gitCommand()` that used **`spawnSync('/bin/sh', ['-c', 'git …'])`**. On a repository with many worktrees (Echo accumulated ~282) `git worktree list` alone takes seconds, and the scan ran on BOTH a 5-minute periodic timer AND on every `sessionComplete` event. Each scan therefore blocked the **server event loop** synchronously for seconds.
12
+
13
+ That freeze was invisible on `localhost` (the dashboard websocket reconnects instantly), but over the Cloudflare tunnel (`echo.dawn-tunnel.dev`) a 1–2s freeze times out in-flight requests, dropping the dashboard websocket and failing its polls → the user-visible "Disconnected / 0 sessions / Mem 0%". This is the residual event-loop-block cause behind the dashboard "disconnected" reports, after the sync-READ fixes in #1247–#1251.
14
+
15
+ ## The change
16
+
17
+ Convert `gitCommand` from `spawnSync` to async `execFile` (promisified), and propagate `await` through the scan call graph so the event loop stays responsive while git runs in the background.
18
+
19
+ Files modified:
20
+ - `src/monitoring/WorktreeMonitor.ts` — `gitCommand` → `async` (execFile, stdout-on-nonzero-exit/timeout semantics preserved to exactly match the old `spawnSync result.stdout ?? ''`); `listWorktrees`, `checkUnmergedWork`, `findOrphanBranches`, `getDefaultBranch`, `getWorktreeAge`, `scanWorktrees` → `async`; `periodicScan` resolves worktree ages with `Promise.all` before filtering; `formatPeriodicAlert` → `async`.
21
+ - `src/server/routes.ts` — `GET /hooks/worktrees` handler → `async`, awaits `scanWorktrees()`.
22
+ - `tests/unit/worktree-monitor.test.ts` — the 13 tests that called the now-async methods updated to `await` them.
23
+
24
+ ## Side effects & risk
25
+
26
+ - **Behavior preserved.** The scans still run on the same triggers and the same 5-minute interval; only the blocking changed to non-blocking. `gitCommand`'s return contract (stdout even on a non-zero exit / timeout) is kept identically, so callers see the same strings.
27
+ - **No external surface change.** `GET /hooks/worktrees` returns the same JSON; the periodic + post-session alert paths are unchanged.
28
+ - **Concurrency.** Scans are serialized per the existing event flow (no new parallelism introduced beyond `Promise.all` over `getWorktreeAge`, which is read-only `git show` per worktree).
29
+ - **Risk:** low. A behavior-preserving async conversion, fully covered by the existing 22 unit tests (updated to await) + the serendipity-routes integration tests, both green.
30
+
31
+ ## Verification
32
+
33
+ - `tsc --noEmit`: 0 errors.
34
+ - `tests/unit/worktree-monitor.test.ts`: 22/22 pass.
35
+ - `tests/integration/serendipity-routes.test.ts`: 22/22 pass.
36
+
37
+ ## Rollout
38
+
39
+ No flag, no migration — the fix is strictly better behavior on the existing code path and ships on the next release. (A shadow-dist hotpatch — disabling both scan triggers — relieved the freeze immediately on the affected agent; this PR is the durable replacement that keeps the feature working without blocking.)