revert(startup): flat Claude launch stagger; keep the adaptive gate at shutdown - #111
Merged
Merged
Conversation
…t shutdown The adaptive config-watching gate from #96 could not hold its own 2000ms cap on the launch path. Measured across three runs on a 10-session machine: pre-#107 gate sum ~61s max 22953ms #107 gate sum ~62s max 31378ms #110 gate sum ~34s max 12574ms Two follow-up fixes — #107 moving it off the UI thread, #110 removing the blocked-thread-per-PTY behind it — roughly halved the cost but never bounded it. Three attempts is enough. What it bought when it worked was ~1.2s per session. Across 30 samples: about a third were pinned at ~2000ms (no write observed, waited the full cap — i.e. identical to a flat delay with more machinery), about a third were genuine wins, and the rest ran 5-31s. A mechanism whose best case saves ~1.2s and whose worst case costs 12-31s is not worth keeping on a path the user waits through at every launch. Predictable beats occasionally-clever here. Both launch paths go back to Task.Delay(staggerMs). KEPT at shutdown, where the data says the opposite: cfgSettle measures a consistent ~304ms against the flat 1000ms, on every run and both machines. The machine is quiet at shutdown, so polling is reliable. Same mechanism, different contention, different answer — the comments say so at both sites. #110 stays regardless: parking a thread-pool thread per PTY is wrong on its own merits and it halved this. Net for a 10-Claude-session restore: a bounded ~20s of stagger instead of ~34s with 12-second outliers. 312/312 pass, 0 warnings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NDsEfog5kVkT5NmX1Ya5be
AThraen
added a commit
that referenced
this pull request
Sep 5, 2026
Release review of the 20 commits since v0.6.0. Three documentation defects, one of them actively wrong. WRONG: the active-terminal highlight section claimed the ring's accent "is stashed on Border.Tag at build time so the highlight method doesn't need to look up the VM". The code does the opposite and its own comment says so — RepoRoot is populated asynchronously by GitService and AccentColor changes when it lands, so a cached Tag goes stale and stops matching the sidebar. The Tag is only a fallback. MISSING: three services added this cycle were undocumented — DbGate (#102), PwshLocator (#104), ClaudeConfigGate (now shutdown-only after #111). MISSING: nothing described what actually makes a session active, which is now the most subtle thing in the codebase and was got wrong twice. Added a section covering all three routes and, more importantly, why two of them must come from the page: - WebView2 is an HwndHost, so mouse input on hosted native content raises no WPF routed events at all — a Preview handler on the host Border only ever sees the ring around the terminal (#108). - xterm's onData is not "the user typed": it also carries device-attribute replies, cursor-position reports, OSC colour replies and focus in/out, none of which are distinguishable from typing by inspecting the bytes (#106). Also recorded why the font-load re-fit exists (#113) and why the ClaudeLaunch stagger must stay a flat delay — including that a plain Task.Delay(2000) still logged gate=36339ms after the revert, which is what finally showed the gate was measuring an app-wide stall rather than causing one. Written as a "do not try this again" note with the measurements, since the adaptive version looks obviously better on paper and was attempted three times. Verified while here: every service on disk is now documented, and all nine keybindings in MainWindow match the documented table. Claude-Session: https://claude.ai/code/session_01NDsEfog5kVkT5NmX1Ya5be Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
AThraen
added a commit
that referenced
this pull request
Sep 6, 2026
…nt (#117) Two problems, both found by running it rather than reasoning about it. 1. The adaptive config gate overshoots at shutdown too. #111 reverted it on the launch path but KEPT it here, on my reasoning that "the machine is quiet at shutdown, so polling is reliable". Measurement falsified that. A real run logged: SHUTDOWN 'SiteAnalysis': exit=4656ms cfgSettle=8731ms against a 1000ms cap — 8.7x over, and 56% of the entire shutdown budget spent in one session, which is what forced the remaining six to be killed with no exit wait. Same disease as the launch path: when the thread stalls, the gate measures the stall rather than the file. Now a flat Task.Delay, on both the shutdown loop and the restart path. Recomputing that run flat gives 8969ms instead of 15506ms with nothing force-disposed — the gate's typical ~300ms beats a flat 1000ms right up until it doesn't, and the tail is what costs. ClaudeConfigGate now has no callers and is deleted along with its tests, rather than left as dead code for someone to reintroduce. 2. The 15s budget was sized from the wrong data. It was chosen when the only measurements available were idle sessions exiting in 460-770ms. Busy sessions measure 2.3-4.7s each, so nine of them need roughly 30s, and 15s force-disposed over half the fleet on an ordinary close. Raised to 30s: a clean exit lets Claude finish writing its config, and ShutdownOverlay is already on screen explaining the wait. The budget is there to bound a wedged session, not to hurry a healthy one. Confirmed while here: the HasExited fix from #115 works. No exit= value is near the 10s cap (max observed 4656ms), so nothing is timing out on a process that already died — which was the whole point. CLAUDE.md updated: the gate is gone from both paths, and the budget carries its measurements plus a note to re-measure exit= before shrinking it, since the summary line cannot distinguish slow exits from waits that never return. 304/304 pass (10 fewer — ClaudeConfigGateTests removed), 0 warnings. Claude-Session: https://claude.ai/code/session_01NDsEfog5kVkT5NmX1Ya5be Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Reverting part of my own #96. Three attempts to make the adaptive gate hold its cap on the launch path have failed.
The measurements
Same 10-session machine, three consecutive builds:
Each fix was real — #110 in particular halved it — but neither bounded it.
What it was actually buying
Across all 30 samples:
2003, 2012, 2017, 2018, 2023, 1970, 1999, 2317) — no write observed, waited the full cap. Identical to a flat delay, with more machinery.0, 0, 354, 603, 713, 752, 808, 823, 1211) — roughly 1.2s saved each.A mechanism whose best case saves ~1.2s and whose worst case costs 12–31s doesn't belong on a path the user waits through at every launch. Predictable beats occasionally-clever here.
Both launch paths go back to
Task.Delay(staggerMs).Kept at shutdown, because there the data says the opposite
cfgSettlemeasures a consistent ~304ms against the flat 1000ms — every run, both machines. The machine is quiet at shutdown, so polling is reliable. Same mechanism, different contention, different answer. Both call sites now carry comments explaining which is which, so neither gets "unified" later.#110 stays regardless
Parking a thread-pool thread per PTY is wrong on its own merits, and it halved this. This PR doesn't depend on it and touches different files.
Net effect
For a 10-Claude-session restore: a bounded ~20s of stagger instead of ~34s with 12-second outliers. Restore becomes predictable, and what's left is
launch=— the WebView2 cost, which is the genuine remaining half of #82.🤖 Generated with Claude Code
https://claude.ai/code/session_01NDsEfog5kVkT5NmX1Ya5be