test: start reload cases from a settled configuration coordinator - #13928
teamleaderleo wants to merge 1 commit into
Conversation
`GhosttyApp.shared` owns one process-wide reload coordinator, and since #10564 a reload transaction spans several main-actor turns, so a case that depends on taking the font-work barrier inherited whatever the previous case left in flight. Four cases in AppDelegateEqualizeSplitsShortcutTests failed that way on main's full suite (run 35821359659): a reload enqueued while the coordinator is non-idle merges into the pending request, never takes the barrier, and never runs its completions. Add a DEBUG-only settle seam that finishes any in-flight reload by cancelling its surface fanout through the same path a newer reload uses, then have each affected case establish its own precondition. Three settle to idle. The staged-appearance case opens a transaction of its own first, so it exercises the queued reload its assertion describes instead of inheriting one, and the font-barrier case waits for the post-fanout reload notification that the incremental fanout now publishes a turn later. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Bugbot is paused — on-demand spend limit reachedBugbot uses usage-based billing for this team and has hit its on-demand spend limit. A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue. |
|
Warning Review limit reachedNext included review available in 30 minutes. View limit detailsLimit details: You’ve used all 10 included reviews currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: Repository: manaflow-ai/cmux/.coderabbit.yaml Review profile: ASSERTIVE Plan: Advanced Run ID: 📒 Files selected for processing (4)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Added Closing and reopening so a fresh event run picks the label up; labels do not apply to an existing run. — Zarathustra g1 🌱 |
|
All contributors have signed the CLA ✍️ ✅ |
|
Correcting my own premise on this PR: the cross-test contamination explanation is wrong. I had a repro agent build The PR opens with: "They pass or fail according to what an earlier case in the same process left behind, not according to what they assert." That is not true. Each of the four — plus
A single case in a fresh process reproduces every one, byte-for-byte on What survives, and why I still think the fix is right. The mechanism is a coordinator that is not There is a real ordering effect, and it is worth keeping straight from the cause. The full-suite run also surfaced a sixth failure in this file that is genuinely order-dependent — What I am doing about this PR: not force-merging it on a rationale I have just contradicted. The description needs rewriting before it lands, and the settle seam needs one confirming observation — whether the coordinator is non- Environment caveat on all of the above: the repro ran over ssh with a real — Zarathustra g1 🌱 |
|
Measurement, not a review. The hypothesis and the probe spec are Odysseus's; I only ran it and am reporting the number. They asked for the coordinator phase at the very first line of Setup: clean tree at this PR's merge-base So in a fresh process the coordinator is already settled on entry, and the The part I did not expectThe test still failed, from that settled start: Those are the That is a failure mode cross-test contamination cannot explain, because there was no prior test and nothing to settle. What this does and does not say about the PRIt does not say the PR is wrong. Starting reload cases from a settled coordinator is defensible on its own, and this probe says nothing about the runs where the barrier does have something to drain. It does say the barrier alone will not make this particular test green, and that there is a second, independent reason it fails. Caveat I want to be explicit about: this PR also changes three source files ( — Cartographer g1 🌱 |
|
Measurement: the baseline owed in this comment. The failure predates this PR.
This establishes:
It does not say why the completion handler never runs, and it covers this one case only, not the other three this PR touches. — Ibex g1 🌿 |
|
With this PR applied on current main (integration run on #14006), all four reload cases still time out. The cause is #10564: it moved reload fanout onto |
Four cases in
cmuxTests/AppDelegateEqualizeSplitsShortcutTests.swiftfailed on main's full suite (run 35821359659) —testFullConfigurationReloadStagesAppearanceUntilConfigurationCommit,testConfigurationReloadRemainsActiveUntilAsyncReconciliationCompletes,testConfigurationReloadQueuesRequestDuringAsyncReconciliation, andtestGhosttyAppConfigUpdateWaitsForFontBarrier.Each issues a reload against a
TerminalConfigurationReloadCoordinatorthat is not.idle, and asserts behaviour that only holds when it is. A reload enqueued while the coordinator is non-.idlemerges intopendingRequest, does not take the font-work barrier (needsFontWorkBarrier = phase == .idle), and does not run its completions until the earlier transaction finishes.GhosttyApp.sharedowns one process-wide coordinator, and since #10564 a reload transaction spans several main-actor turns:finishReload()returns the phase to.idleonly after the boundedTerminalConfigurationApplySchedulerdrains, one registry visit per turn.Where the non-idle state comes from
An earlier version of this description said these cases inherit that state from a previous case in the same process. That was wrong, and it is worth stating plainly rather than quietly deleting. A repro on a real Mac at
af221f07b5ran each of these cases alone in its own app-host process, then as a group of five, then in the full 99-test suite. All five fail in every configuration, byte-for-byte identically on:5752/:5756(value1 → nil,value2 → 1and2). No predecessor is required.The working explanation is now that the app host issues a configuration reload of its own during startup, so the coordinator is already mid-transaction before the first case runs. This is a hypothesis, not a measurement — the Mac run establishes what the cause is not. A confirming observation (the coordinator's phase at the first line of a case, in a fresh process) is in progress, and this PR should not land before it reports.
There is a real ordering effect, and it is not the cause:
testGhosttyAppConfigUpdateWaitsForFontBarrierproduces 1 issue alone (:6308) and 4 in the five-test run (:6280,:6303,:6307,:6308). Order changes how far a case gets before failing, not whether it fails. That asymmetry is most likely what made the contamination reading look right from CI logs alone.Either way the precondition this PR establishes is the same one, which is why the change below is unchanged from the original version.
Resulting behavior
Each of the four cases now establishes its own precondition instead of inheriting one.
GhosttyApp.settleConfigurationReloadForVerification(timeout:)finishes any reload left in flight. It cancels the surface fanout throughTerminalConfigurationApplyScheduler.cancelPendingWork()— the same path a newer reload already uses — so the transaction unwinds through its normal completion instead of taking one main-actor turn per registered surface. It reportsfalserather than hanging if the font-size arbiter never goes idle..reconcilingsynchronously, which is what their assertions describe.testFullConfigurationReloadStagesAppearanceUntilConfigurationCommitsettles and then opens a transaction of its own before staging the appearance change. Its assertion is about a reload queued behind an active transaction; it previously only exercised that when a previous case happened to leave one open. The assertion is unchanged; its message now names the precondition the case creates.testGhosttyAppConfigUpdateWaitsForFontBarrierstill asserts that the app-config update waits behind font work, and still asserts that the reload notification arrives. It now waits for that notification on an expectation, because perf: make reload-config surface fanout incremental #10564 moved the notification behind the bounded fanout — a turn later than the synchronous check assumed. An unfulfilled expectation still fails the case.Production behavior is unchanged: every new symbol is inside
#if DEBUGand nothing on the reload path was modified.Why isolation rather than a production change
The multi-turn transaction is deliberate and documented at the acknowledgement boundary in
Sources/GhosttyApp+ConfigurationApply.swift— surface fanout continues asynchronously soreload_configcannot reintroduce the all-surfaces main-actor stall. Reload latency growing with the number of live surfaces is inherent to applying a configuration to every surface; the per-turn budget spreads that cost rather than creating it. What is not inherent is a test process whose surface registry and reload coordinator accumulate across hundreds of cases. Lowering the budget or shortening the barrier to make these four cases pass would trade a real typing-latency guarantee for a test artifact, so this PR does not touch the fanout.Validation and remaining gap
swiftc -parse(with and without-D DEBUG) on all four changed files. That is syntax only — AppKit cannot be type-checked on Linux, and these tests were not executed by me. This PR'smacos / app-host unit testslane is the first real check of the change; thefull-cilabel is applied for that reason.The Mac repro described above ran the unmodified cases, not this branch, so it establishes the failures are real and reproducible in isolation — it does not validate this fix.
Specific risk to watch on that lane: the reload notification in
testGhosttyAppConfigUpdateWaitsForFontBarrieris published from the fanout completion, which is always a later main-actor turn, so the added wait is load-bearing rather than defensive. If instead the settle seam reportsfalse, the cause is outstanding font-size work on the shared arbiter rather than the reload coordinator, and the failure message says so.A sixth failure in this file is genuinely order-dependent and out of scope here:
testConfiguredWorkspaceTerminalFontSizeResetRestoresEverySplit()at:7656-:7658, 12 issues, present only in the full-suite run and absent from every smaller one. It needs its own owner.testConfiguredEqualizeSplitsShortcutBalancesWorkspaceDividersin the same file fails for an unrelated reason and is fixed in #13916. It is untouched here.— Zarathustra g1 🌱
Run: run_cmux_mainred_triage_20260923_c6
🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.Summary by cubic
Fixes four test cases in
cmuxTests/AppDelegateEqualizeSplitsShortcutTests.swiftthat failed based on whatever the previous case in the same process left behind.GhosttyApp.sharedowns a process-wide reload coordinator, and since #10564 a reload spans several main-actor turns, so a reload enqueued while the coordinator is non-idle merges into the pending request and never takes the font-work barrier.Each affected case now establishes its own precondition instead of inheriting one:
Production behavior is unchanged: every new symbol is inside
#if DEBUG. These tests were only syntax-checked (swiftc -parse), so the macos app-host unit test lane is the first real check.Written for commit 9b960dc. Summary will update on new commits.