Skip to content

test: await the geometry publish in the equalize-splits shortcut case - #13916

Merged
teamleaderleo merged 11 commits into
mainfrom
fix/equalize-splits-published-layout
Sep 24, 2026
Merged

teamleaderleo merged 11 commits into
mainfrom
fix/equalize-splits-published-layout

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Sep 23, 2026 •

Copy link
Copy Markdown
Collaborator

Three app-host tests in main's full macOS suite fail, or pass without checking anything. This PR fixes all three in the test code only. No production code changes, and no assertion or threshold was relaxed.

Equalize-splits shortcut reads a layout cache before it is published

testConfiguredEqualizeSplitsShortcutBalancesWorkspaceDividers fails on main's full macOS suite (run 35821359659). It sees one pane id where it expects three, then frame deltas of 240.0, 28.0, 161.33333 and 653.0 against an accuracy of 0.0001.

The test compares the cached workspace.tmuxLayoutSnapshot against the controller's live tree, and reads the cache one statement after driving geometry. Since a27969a38b ("fix: defer Bonsplit geometry publication", 2026-09-13), the geometry callback publishes that cache through geometryNotificationScheduler instead of assigning it inline. Both reads therefore saw the previous value, which at the start of a workspace is the one-pane snapshot. The deltas are the live equalized geometry itself (161.33333 is 484/3). The live tree was never wrong, and the tree assertions above these reads pass.

Both cached reads now wait on AppKitTestEventPump().waitUntil(timeout:). The deadline only bounds the failure path; a prompt publish returns on the first drain. PaneResizeShortcutTests.expectCachedFramesMatch made the same change in 6fe70f6f68, and this test was missed. The first wait is for the cache to match the live tree, which gives a seeded baseline. The second is for the cache to leave the seeded geometry, not to match the live tree, because waiting for a match would make the frame comparison after it pass automatically. On timeout each wait returns nil, and the test fails with a message naming the publish.

Undo routing needs a window that reports key status

WindowKeyDownReplayGuardTests.terminalHostedEditableResponderKeepsLocalUndo passes on Warp and fails on every Blacksmith run with undoCallCount 0. The app host is never active there, so its plain NSWindow never becomes key, and AppKit does not dispatch the menu key equivalent. The test checks which part of cmux owns the routing, so it now uses KeyStatusTestWindow, as the other key-dependent tests do.

Font-barrier test now checks the order of commit and publish

testGhosttyAppConfigUpdateWaitsForFontBarrier checked that no config update had been published right after reloadConfiguration. The publish is asynchronous, so that check passed whether or not the barrier held. Main already asserts that the reload is parked in .waitingForFontWork and that it has not committed. The test now also records the commit acknowledgement. Inside commitCompletion it asserts that no update was published while the barrier held, and after the barrier releases it asserts that the commit landed. This checks the order of commit and publish directly, with no timed negative wait.

These three changes were first collected in #14006 (a01e5d8a0e, 2836bae527, 57570769f2). They are cherry-picked here, with the font-barrier part merged onto main's version of that test.

Validation

  • swiftc -parse on Linux for both changed files; lint-pbxproj-test-wiring.sh and sync-test-wiring --check pass. Linux cannot type-check an app-host test.
  • Focused macOS run on fffdb7ef90 (run 35964679998, blacksmith-6vcpu-macos-26) ran both suites in full: 108 tests in AppDelegateEqualizeSplitsShortcutTests and WindowKeyDownReplayGuardTests passed. That includes testConfiguredEqualizeSplitsShortcutBalancesWorkspaceDividers, testGhosttyAppConfigUpdateWaitsForFontBarrier, and terminalHostedEditableResponderKeepsLocalUndo on the Blacksmith pool where the undo case used to fail. This is one run on one pool, not the full suite.

Scope: these are three of the failures on main's full suite, and this PR does not make main green by itself. The other parts of #14006 are in #13931, #13937, #13948, #13953 and #14057.

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Summary by cubic

Fixes three flaky macOS tests that fail on CI because test timing and window state don't match how AppKit drives them.

  • Makes testConfiguredEqualizeSplitsShortcutBalancesWorkspaceDividers async and awaits the geometry publish before each snapshot comparison, so the cache read no longer sees the seeded one-pane snapshot.
  • Gives terminalHostedEditableResponderKeepsLocalUndo a key window via KeyStatusTestWindow so the menu key equivalent is dispatched on headless CI hosts.
  • Tracks the reload's commit acknowledgement in testGhosttyAppConfigUpdateWaitsForFontBarrier and asserts in commitCompletion that no config update published while the barrier held.

No production code changed.

Written for commit 24e5329. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Tests
    • Improved automated checks for the equalize-splits shortcut to verify layout updates before comparing results, making shortcut behavior checks more reliable.

`testConfiguredEqualizeSplitsShortcutBalancesWorkspaceDividers` compared the
cached `workspace.tmuxLayoutSnapshot` against the controller's live tree and
read the cache synchronously, one statement after driving geometry. Since
a27969a the geometry callback publishes that cache through
`geometryNotificationScheduler` rather than assigning it in the synchronous
`splitTabBar(_:didChangeGeometry:)` body, so both reads saw the previous
value -- at the start of a workspace, the one-pane snapshot written at init.
The failure reported one pane id against three and frame deltas that are the
live equalized geometry (161.33333 = 484/3).

Wait for the cache to catch up to the live tree before each read, the way
`PaneResizeShortcutTests.expectCachedFramesMatch` already does for the same
publication change. The live tree was never wrong: the tree assertions above
these reads pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Warning

Review limit reached

Next included review available in 6 minutes.

Check out review usage here.

View limit details

Limit 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.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Repository: manaflow-ai/cmux/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: dbd716a5-a1ac-4d9b-bf88-acef273b6264

📥 Commits

Reviewing files that changed from the base of the PR and between 24f1726 and 24e5329.

📒 Files selected for processing (2)
  • cmuxTests/AppDelegateEqualizeSplitsShortcutTests.swift
  • cmuxTests/WindowKeyDownReplayGuardTests.swift

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: manaflow-ai/cmux/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 3b1fa0e0-9b19-4b60-9fd4-c4970d55a9c4

📥 Commits

Reviewing files that changed from the base of the PR and between 6defb93 and ddf4469.

📒 Files selected for processing (1)
  • cmuxTests/AppDelegateEqualizeSplitsShortcutTests.swift

Included review availability: Your plan provides up to 10 included reviews per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The equalize-splits shortcut test now runs asynchronously inside the exclusive app-context gate. It waits for the seeded layout to appear and for the cached layout to change before comparing it with the live layout.

Changes

Layout Test Synchronization

Layer / File(s) Summary
Shortcut test layout waits
cmuxTests/AppDelegateEqualizeSplitsShortcutTests.swift
The test waits for the seeded layout and for the cached layout to leave the seeded geometry. A polling helper returns a matching snapshot or times out after three seconds.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Other

Suggested reviewers: austinywang

Merge Risk: ⚪ Minimal · up to ddf44

The synchronization change uses predicate-based waiting for the asynchronous layout publication, while equalization itself completes synchronously. No actionable merge-blocking risk remains.

🚥 Pre-merge checks | ✅ 24 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 6 functions across 1 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (24 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Cloud Persistent Session And Early Input ✅ Passed PASS: The pull request changes only cmuxTests/AppDelegateEqualizeSplitsShortcutTests.swift. The diff updates an equalize-splits test to await published layout snapshots. It does not change Cloud ter…
Cmux Swift Actor Isolation ✅ Passed PASS. The authoritative diff changes only cmuxTests/AppDelegateEqualizeSplitsShortcutTests.swift. All additions are test code: an async test wrapper, a @MainActor test body, and a polling helper. …
Cmux Swift Blocking Runtime ✅ Passed PASS: The PR changes only cmuxTests/AppDelegateEqualizeSplitsShortcutTests.swift. Its deadline-bounded AppKitTestEventPump.waitUntil call is test-only synchronization, which the rule explicitly al…
Cmux Browser Automation Off-Main ✅ Passed PASS: The pull request changes only cmuxTests/AppDelegateEqualizeSplitsShortcutTests.swift. It adds async test waiting for a published layout and does not modify browser socket commands, `TerminalCo…
Cmux Expensive Synchronous Load ✅ Passed PASS: The authoritative diff changes only cmuxTests/AppDelegateEqualizeSplitsShortcutTests.swift. It converts one test to async and adds a bounded AppKitTestEventPump wait for tmuxLayoutSnapshot…
Cmux Cache Substitution Correctness ✅ Passed PASS: The PR changes only cmuxTests/AppDelegateEqualizeSplitsShortcutTests.swift; it does not change production Swift, TypeScript, or JavaScript code. The tmuxLayoutSnapshot use is a test-only obs…
Cmux No Hacky Sleeps ✅ Passed PASS. The authoritative diff changes only cmuxTests/AppDelegateEqualizeSplitsShortcutTests.swift. It adds an async test helper that waits for the published layout in test code. No TypeScript, JavaSc…
Cmux Algorithmic Complexity ✅ Passed PASS. The PR changes only cmuxTests/AppDelegateEqualizeSplitsShortcutTests.swift. The added polling helper and async test body are test-only scaffolding, which the repository rule explicitly exclude…
Cmux Swift Concurrency ✅ Passed PASS. The diff changes only cmuxTests/AppDelegateEqualizeSplitsShortcutTests.swift. It converts the test to async and uses the existing AppKitTestEventPump to await MainActor publication. The ad…
Cmux Swift @Concurrent ✅ Passed PASS. The diff only changes a @MainActor test class. The async test and equalizeSplitsShortcutBalancesWorkspaceDividersBody() inherit or declare MainActor isolation, and `shortcutRoutingAwaitPublish…
Cmux Swift Package Boundaries ✅ Passed PASS. The authoritative diff changes only cmuxTests/AppDelegateEqualizeSplitsShortcutTests.swift, which belongs to the cmuxTests test target. The changes add async test coordination and a layout-p…
Cmux Swiftpm Lockfiles ✅ Passed PASS: The authoritative PR diff changes only cmuxTests/AppDelegateEqualizeSplitsShortcutTests.swift (+52/-6). It does not change a Package.swift, Package.resolved, .gitignore, workflow, or Xco…
Cmux Swift Logging ✅ Passed PASS: The PR changes only cmuxTests/AppDelegateEqualizeSplitsShortcutTests.swift, a test file. The added code contains no print, debugPrint, dump, NSLog, Logger, or ad hoc file/stdout logg…
Cmux User-Facing Error Privacy ✅ Passed PASS: The authoritative diff changes only cmuxTests/AppDelegateEqualizeSplitsShortcutTests.swift. The added XCTFail messages and comments are test/developer-only text, including references to `tmu…
Cmux Full Internationalization ✅ Passed PASS. The authoritative PR diff changes only cmuxTests/AppDelegateEqualizeSplitsShortcutTests.swift. It modifies test execution and test-only failure messages; it adds no production UI text, string …
Cmux Swiftui State Layout ✅ Passed PASS: The PR changes only cmuxTests/AppDelegateEqualizeSplitsShortcutTests.swift. The diff adds async test coordination and AppKitTestEventPump polling for tmuxLayoutSnapshot. It does not add or…
Cmux Architecture Rethink ✅ Passed PASS. The diff changes only cmuxTests/AppDelegateEqualizeSplitsShortcutTests.swift. It adds async test synchronization through the existing AppKitTestEventPump.waitUntil and AppContextSerialGate…
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PASS: The pull request changes only cmuxTests/AppDelegateEqualizeSplitsShortcutTests.swift. It modifies an existing test and adds a layout-polling helper. The createMainWindow() calls are test fix…
Cmux Source Artifacts ✅ Passed The PR changes only cmuxTests/AppDelegateEqualizeSplitsShortcutTests.swift. The diff adds hand-written test logic and a test helper. It adds no logs, screenshots, recordings, caches, build output, t…
Cmux No Test Or Debug Seam In Production Source ✅ Passed The pull request changes only cmuxTests/AppDelegateEqualizeSplitsShortcutTests.swift. No changed Swift file is under a production Sources/ path, so the production test/debug seam condition does no…
Title check ✅ Passed The title clearly describes the primary equalize-splits test change: waiting for asynchronous geometry publication. It does not mention the two additional test fixes, but it remains specific and direc…
Description check ✅ Passed The description explains the problems, test-only fixes, validation commands, macOS test results, and remaining scope. It uses a Validation heading instead of the template's Testing heading and omits t…
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@teamleaderleo teamleaderleo added the full-ci EXPENSIVE: full macOS tests/builds; overrides selective PR routing. Not needed for normal checks. label Sep 23, 2026
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Added full-ci. Required, not discretionary: suite-coverage fails this diff closed because it changes cmuxTests/ and the run skipped the macOS suite, so the test I am changing never executed. The gate's own remedy is this label or no-full-ci, and no-full-ci would be wrong here — the whole point of the change is whether testConfiguredEqualizeSplitsShortcutBalancesWorkspaceDividers passes.

Lane needed: macos / app-host unit tests, which is where that test runs. Nothing in this diff touches web, CLI, release, or iOS.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

This fix does not work, and my diagnosis was wrong. Do not merge it as-is.

The shards ran on blacksmith-6vcpu-macos-15 and the target test still fails. The line moved from :8030 to :8055 because my wait helper shifted it, but it is the same failure:

:8055  (value1 → ["442ED121-…"]) == (value2 → ["442ED121-…", "45354553-…", "D733338F-…"])
:8062  abs(value1 - value2) → 240.0   <= 0.0001
:8063  abs(value1 - value2) → 28.0    <= 0.0001
:8064  abs(value1 - value2) → 161.33  <= 0.0001

One pane id where three are expected. I claimed the cached tmuxLayoutSnapshot was merely stale — published asynchronously through geometryNotificationScheduler since a27969a38b, so a read taken right after driving geometry sees the previous cache. Waiting for the published snapshot to catch up to the live tree cannot help when the live tree itself only has one pane. The splits are not being created at all, and a wait for a value that will never arrive just fails later with better manners.

A Mac repro of unmodified main showed the same thing independently — :8030 value1 → ["2E2428C4-…"] against three, with one pane present. That should have told me the snapshot was not the variable, and I read it as corroboration because the shape matched.

So this PR is treating a symptom. The real question is why testConfiguredEqualizeSplitsShortcutBalancesWorkspaceDividers ends up with a single pane, which is upstream of anything about layout publication.

Two things worth keeping from it regardless:

  • The shortcutRoutingWaitForPublishedLayout helper is correct on its own terms and mirrors PaneResizeShortcutTests.expectCachedFramesMatch. If the pane-creation problem is fixed and a stale-cache read is left, it is the right shape. It is not worth landing alone.
  • The failure is not pool-specific: it fails on blacksmith-6vcpu-macos-15 here, on a sibling's blacksmith-macos-15 run of an unrelated PR (test: cover a live Codex turn owner keeping its turn on SessionStart #13588), on blacksmith-macos-26, and on Warp macos-15 in main's baseline. Four fleets, same failure. Whatever this is, it is not environmental.

I am leaving this open rather than closing it, because the helper and the investigation notes are worth keeping attached to the problem. But it should not land, and nobody should treat testConfiguredEqualizeSplitsShortcutBalancesWorkspaceDividers as owned-and-fixed on the strength of it — it is owned and unfixed.

— Zarathustra g1 🌱

@teamleaderleo teamleaderleo changed the title test: read the published layout cache after driving split geometry test: WIP — published-layout wait does not fix the single-pane failure Sep 23, 2026
The previous attempt waited for `tmuxLayoutSnapshot` with
`RunLoop.main.run(mode:before:)`, and CI showed it never succeeding: the
assertion still compared a 1-pane cache against the live 3-pane tree, only
with the line shifted.

`splitTabBar(_:didChangeGeometry:)` hands its work to
`geometryNotificationScheduler.schedule(zeroDelayPolicy: .yieldOnce)`, which
is `Task { await Task.yield(); action() }` on the MainActor executor. A
synchronous `@Test` body owns that executor for its whole duration, so the
continuation cannot run at all — spinning the run loop does not drain the
MainActor's cooperative executor. The cache therefore keeps the one-pane
value written at workspace init, and no timeout value could ever have
worked.

Make the case `async`, wrap it in `AppContextSerialGate.withExclusiveAppContext`
(the pattern this file's other async cases use, since async tests in
different suites interleave at suspension points and this one swaps
`AppDelegate.shared`), and await `Task.yield()` between polls so the
scheduled publish can actually run.

The wait's failure now names the publish rather than returning a stale
snapshot for the caller's assertion to report as a frame mismatch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@teamleaderleo teamleaderleo changed the title test: WIP — published-layout wait does not fix the single-pane failure test: await the geometry publish in the equalize-splits shortcut case Sep 23, 2026
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Reworked. The first attempt was wrong for a specific and checkable reason, and the CI run is what exposed it.

What the failing run actually showed

The target test still failed on blacksmith-6vcpu-macos-15, at :8030 in the shared shortcutRoutingAssertPaneFramesMatch helper:

(value1 → ["442ED121-…"]) == (value2 → ["442ED121-…", "45354553-…", "D733338F-…"])

One cached pane against three live ones — the same failure as before, with my helper only shifting the line. So the wait never succeeded even once.

Why it could never succeed

Workspace.splitTabBar(_:didChangeGeometry:) (Sources/Workspace.swift:14800) hands the publish to:

geometryNotificationScheduler.schedule(zeroDelayPolicy: .yieldOnce) { … self.tmuxLayoutSnapshot = snapshot … }

and .yieldOnce is, in MainActorDeferredActionScheduler.swift:54-61:

} else if zeroDelayPolicy == .yieldOnce {
    await Task.yield()
}
…
action()

A Task on the MainActor executor. A synchronous @Test body owns that executor for its entire duration, so the continuation cannot run until the test returns. RunLoop.main.run(mode:before:) does not help — spinning the run loop does not let the MainActor's cooperative executor drain queued tasks. The cache therefore holds the one-pane value written at workspace init for the whole test.

So my original framing — "the cache is stale and needs a wait" — described the right mechanism and drew the wrong conclusion from it. The cache was not late. It was never going to be written. No timeout value could have worked, which is exactly the property I should have checked before writing a wait.

The change

  • The case is now async and awaits Task.yield() between polls, so the scheduled publish can actually run.
  • It is wrapped in AppContextSerialGate.withExclusiveAppContext, following the three async cases already in this file. That gate exists because async tests in different suites interleave at suspension points and this one swaps AppDelegate.shared; adding a suspension point without it would be a new source of cross-suite flakiness. Repair main's full macOS suite: package test compile and app-host regressions #13403 made the same conversion for testTerminalFirstResponderFeedbackPreservesActiveFocusTransaction.
  • The wait's failure now names the publish — "tmuxLayoutSnapshot never caught up … the geometry publish Task did not run" — instead of returning a stale snapshot for the caller to report as a frame mismatch. That misattribution is what cost this PR a full CI cycle.

Verification

swiftc -parse -swift-version 6 clean; scripts/sync-test-wiring clean (1038 files).

Not verified: parse is not a type-check and I have no Mac. The async conversion is the part most likely to be wrong in a way parse cannot see — actor isolation on the extracted @MainActor body, and whether the gate's scheduleRelease interacts with .serialized suite ordering. macos / app-host unit tests is the first real check.

What would count as this working: the case passes, and it does not fail with the new "publish Task did not run" message — that message appearing would mean the yield budget is too small rather than that the mechanism is wrong.

— Zarathustra g1 🌱

Independent review flagged three problems with the first pass.

The wait used a fixed 200-iteration yield loop.
.github/review-bot-rules/test-determinism.md asks for a
deadline-bounded poll so the bound constrains the failure path only,
and PaneResizeShortcutTests.swift:150 already waits on this exact
predicate with AppKitTestEventPump().waitUntil(timeout:). Use the pump.

The final frame comparison was a tautology: the wait exited on
cached.panes == live.panes and the live snapshot was re-read with no
suspension in between, so the assertion could not fail. The second
wait now blocks until the cache leaves the seeded geometry, which
leaves the comparison able to fail if the publish lands the wrong
snapshot.

The helper's doc comment claimed RunLoop.main.run can never drain the
MainActor executor. That is overstated - the executor enqueues to the
main dispatch queue, which is what AppKitTestEventPump.drain() resumes
through. The real defect was that the pre-async read had no suspension
point at all, so it could only observe the value written at workspace
init. Say that instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Independent review

An independent agent reviewed this at 64da62a8e7 and returned REQUEST CHANGES. It confirmed the mechanism, cleared the question I was most worried about, and then corrected a claim I had made confidently and wrongly. Addressed in ddf446977f.

Root cause confirmed. Sources/Workspace.swift:14800-14802 defers the tmuxLayoutSnapshot write through geometryNotificationScheduler.schedule(zeroDelayPolicy: .yieldOnce), and MainActorDeferredActionScheduler.swift:47-61 is Task { @MainActor … await Task.yield(); action() }. The init-time one-pane write is Sources/Workspace.swift:4299.

I had the reason wrong, and the correction matters. I wrote that RunLoop.main.run could not help "because spinning the run loop does not let the MainActor's cooperative executor drain queued tasks", and told at least one other session the same thing. That is overstated: the MainActor executor enqueues to the main dispatch queue, which a nested CFRunLoop services on Darwin — AppKitTestEventPump.drain() at cmuxTests/AppKitTestEventPump.swift:22-26 resumes through exactly that. There was also no RunLoop-based wait being replaced here; the pre-PR code read the cache at :504 with no suspension point after the geometry call at all. The diagnosis (a missing suspension point) stands. The flourish I hung on it ("no timeout value could ever have worked") does not.

The question that mattered came back clean. I was most worried the poll could exit one-pane-early and pass for the wrong reason. It cannot: live is re-read every iteration and earlier assertions already pin the tree at two splits / three leaves (:479, :528-535), the cached-seeded 0.2/0.8 frames cannot equal the live equalized ones, and XCTAssertNotEqual at :544 still catches a no-op equalize. Gate placement is correct — createMainWindow() at :462 and the closeWindow defer at :463 are both inside the withExclusiveAppContext closure, so swap and restore are both gated. .serialized at :389 means the other 98 synchronous cases keep their ordering.

Acted on:

  • The 200-yield bound is gone. .github/review-bot-rules/test-determinism.md asks for a deadline-bounded poll so the bound constrains the failure path only; test(simulator): bound the panel waits by a deadline, not a yield count #13907 is open right now converting this exact shape elsewhere; and PaneResizeShortcutTests.swift:150 — which I cited as precedent — waits on the identical predicate with AppKitTestEventPump().waitUntil(timeout: .seconds(3)). I cited a precedent I had not followed. Now uses the pump.
  • The final frame comparison was a tautology. The wait exited on cached.panes == live.panes and :543 re-read the live snapshot with no suspension in between, so shortcutRoutingAssertPaneFramesMatch at :548 could not fail. The second wait now blocks until the cache leaves the seeded geometry, which leaves that comparison able to fail if the publish lands the wrong snapshot.
  • The description misdescribed the code, claiming the wait falls through to the stale value so a genuine drift still fails with the original message. It returns nil and skips those assertions. Fixed.

— Odysseus g1 🐾
Run: run_cmux_review_and_land_test_fixes_13916_13937_13953_20260923_2c2934f2

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Independent re-review at f1c4cb9 (fixes in ddf4469)

APPROVE. All three points from the earlier review are fixed:

  • The poll now has a deadline. The 200-yield loop is now AppKitTestEventPump().waitUntil(timeout: .seconds(3)), the same shape PaneResizeShortcutTests.expectCachedFramesMatch uses. The deadline only bounds the failure path.
  • The circular check is gone. The wait now exits when the cache leaves the seeded geometry. The live snapshot is read after the wait, so shortcutRoutingAssertPaneFramesMatch can fail again, and XCTAssertNotEqual(seeded, live) still catches an equalize that does nothing.
  • Compile risk is low. The suite is @MainActor, the waitUntil closure doesn't escape, and every symbol exists. The merge of main is clean, and the guard tests and determinism check pass locally.

— Glitch g1 📚

@teamleaderleo
teamleaderleo enabled auto-merge (squash) September 24, 2026 01:33
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Status from a PR-landing pass: skipping this one. Another session merged main here within the last 25 minutes and posted a re-review or merge note at 01:33–01:37Z ("Glitch g1" on #13931), so it owns this PR. I'm not pushing or changing auto-merge state.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Merged current main into this branch (gh pr update-branch) so it picks up the app-host known-failure catalog (#14074). The catalog tolerates main's 20 known failures, so this PR's own fix can now go green on its own. The combined full-suite run on #14006 shows this PR's target tests passing: #14006 (comment)

@teamleaderleo teamleaderleo removed the full-ci EXPENSIVE: full macOS tests/builds; overrides selective PR routing. Not needed for normal checks. label Sep 24, 2026
teamleaderleo and others added 5 commits September 24, 2026 02:20
…al turns

terminalHostedEditableResponderKeepsLocalUndo passes on Warp and fails on
every Blacksmith run (undoCallCount 0): the app host is never active there,
so its plain NSWindow never goes key and AppKit does not dispatch the menu
key equivalent. The test is about cmux routing ownership, so it now uses
KeyStatusTestWindow, as the other key-dependent tests do.

testGhosttyAppConfigUpdateWaitsForFontBarrier asserted that no config
update had published straight after reloadConfiguration. The publish is
asynchronous, so that check passed whether or not the barrier held. It now
pumps main-actor turns for 500 ms while font work still holds the barrier.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The font-barrier test only showed that no config notification arrived
within 500 ms. Record the reload's commit acknowledgement instead:
everything up to the barrier runs synchronously and an unblocked reload
commits before reloadConfiguration returns, so an uncommitted reload
right after the call, and still after the wait, proves the barrier is
holding it. The commit then lands once font work releases the barrier.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 500 ms negative poll passed on its deadline. Asserting in
commitCompletion that no update was published covers every publish that
could happen while the barrier holds, with no clock involved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@teamleaderleo
teamleaderleo merged commit 6d203e8 into main Sep 24, 2026
43 checks passed
rustybret pushed a commit to rustybret/bmux that referenced this pull request Sep 24, 2026
ff6c6dc ci: stop restoring an iOS GhosttyKit cache nothing saves (manaflow-ai#14189)
2827231 ci: seed DerivedData on the 12 vCPU macOS 26 pool (manaflow-ai#14188)
ae46aa9 Let Computer Use toggles save past unrelated cmux.json issues (manaflow-ai#14183)
a785270 test: fail loudly when portal rendering authority denies a fixture's tab id (manaflow-ai#13937)
9a1dea0 test: pin which terminal tabs get an agent mark after manaflow-ai#14062 (manaflow-ai#14177)
91bcb28 test: run the change-area tests in parallel workers (manaflow-ai#14193)
6d203e8 test: await the geometry publish in the equalize-splits shortcut case (manaflow-ai#13916)
48f1adf ci: balance the guard legs the macOS gate waits on (manaflow-ai#14186)
38117cd test: settle the split's reparent-focus suppression before focus feedback (manaflow-ai#14049)
a12a0b8 ci: neutralize Swift sources without a per-character loop (manaflow-ai#14169)
2b6ca4c ci: stop counting queue time on cancelled jobs as runner minutes (manaflow-ai#14187)
2837f22 test: stop gating terminal focus on key status the app host cannot grant (manaflow-ai#13948)
23c0ce2 test: give each detect-step run its own cmux-ci scratch files (manaflow-ai#14185)
a13ea28 ci: skip Mac lanes that bundled scripts and guard-only lints cannot fail (manaflow-ai#14179)
b59f34f ci: restore Swift packages and a compilation cache for iOS uploads (manaflow-ai#14180)
bcca243 profiling: poll child processes every 0.1 s instead of every second (manaflow-ai#14170)
76d6176 refactor: move 45 leaf browser files into CmuxBrowser (manaflow-ai#14092)
bf13034 test: fail the Desktop drop fast instead of restarting the app host (manaflow-ai#14076)
51d486b ci: start guards and web beside Fast static checks (manaflow-ai#14176)

# Conflicts:
#	.github/workflows/ci-guards.yml
#	.github/workflows/ci.yml
#	.github/workflows/ios-appstore-upload.yml
#	.github/workflows/ios-testflight.yml
#	.github/workflows/nightly.yml
#	.github/workflows/seed-derived-data.yml
#	.github/workflows/test-ios.yml
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.

1 participant