Skip to content

ghostty-next: pin 6cc749a8a, a frame the embedder presents itself wakes no main-thread present (S4 flip prerequisite) - #35900

Merged
lawrencecchen merged 1 commit into
feat-cmux-nextfrom
fix/ghosttykit-pin-embedder-wake
Nov 5, 2026
Merged

lawrencecchen merged 1 commit into
feat-cmux-nextfrom
fix/ghosttykit-pin-embedder-wake

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Summary

FRAME OWNER REFACTOR S4, the step before the default flip of motion.frameOwner.terminals (cmux-ad m6). GhosttyNextKit and the ghostty-next gitlink move together from 7d43bb68b to 6cc749a8a on release/cmux-9ce9b7d0-e2:

With the switch off (the default), nothing a person sees changes: renderer mode is unchanged in the fork.

The release: xcframework-6cc749a8a53b1d62465e0faf848e430a82f690aa-apple-v6 (run 41922548637), zip sha256 018d2bdb8b7f26c9e97ff6732b4e80e52f24ca3086e456efe0fb6d8aa38f4a40 (its SHA256SUMS). None of the changed fork files (src/renderer, src/termio/Thread.zig, src/apprt/embedded.zig, include/ghostty.h, NEXT.md) is in the libghostty-vt link graph, so vt-link-graph.json changes only its commit, and package-notices.json names the new paths (no license file or dependency change).

Coordination: cmux #35478 (upstream sync, risk:high) moves the same pin to a6b9048e9 on release/cmux-sync-20261104, which does not have this fix. The same fork changes are merged on that line too (manaflow-ai/ghostty-next#76 and #78, tip 58f44beb579). Fork #77 (a test and one shared helper, no behavior change) merged after this pin, as ae7c1f741; this pin does not contain it. If this PR lands first, #35478 must pin 58f44beb579 or later; if #35478 lands first, this PR moves to the sync line (comment 6644453471 on #35478).

Testing

  • The terminal suites at this head: display step 15c50cdb6a3a9a3150dddcc2: all 3 suites pass (TerminalKeyResizeFrameTests 2 tests, TerminalFrameOwnerMainThreadTests 1, TerminalSurfaceCommitSizeTests 6; key resize misplaced 0, unsized 0, stretched 0, PTY grids per terminal [1, 1], main ms per frame p50 0.32, p95 0.46, max 0.77); the 42 terminal suites: step 80c78c6554c4b3a313463af7: all 42 suites pass.
  • The gate A/B for the flip, at this head on cmux24-perf (bench(terminal-resize): probe the drag, accept held terminals, A/B key resizes in one launch #35407's bench, OFF and ON in one launch, 6 rounds, layouts keys, light, stress, keys-stress; single presses and held keys --key-repeat=6): single presses step f39f3cb82c7418e4b2decd3e, held keys step e16d78afdddfd1f84690e79c (both run). The first single-press step ac3662e31afed4a0f3716afc failed on the fleet, not in the code: the warm slot on cmux24 had a GhosttyNextKit precompiled module built from the old ghostty.h ("ghostty.h has been modified since the module file ... needs to be rebuilt"); the step dropped that .build, and f39f3cb8 reruns the same SHA. Any warm slot with a module of the old pin can fail once like this after the pin moves. The gate rule (pixel-perfect): 8 rows; a row fails if ON is higher in 4 or more net rounds, or ON's missed total is more than 1.25 x OFF's + 3, or ON's motion main p95 is more than 1.15 x OFF's; one rerun per failing row; this PR's OFF arm must also pass that rule against the baseline's OFF arm. Baseline at 8255dfd7ac42 (Frame owner S4: terminals follow their pane in every frame of a key resize (switch-on path; default stays off) #35470's merge, the old pin), single presses, missed refreshes per round: keys-stress OFF 1, 2, 2, 3, 4, 2 (14) / ON 4, 3, 3, 5, 5, 3 (23), ON higher in 6 of 6 rounds; keys 10 / 15; STRESS drag 7 / 11; light drag 0 / 0 (step 0a1e445d2262d43d8fd0a0ab).
  • Fork tests (host present: filter on the fork's CI): red run 41915563142 (the two embedder-mode tests fail, expected 0, found 1), green run 41917705365 (109/109, lib-vt 97/97).

Gate A/B results at this head (cmux24-perf, rule from pixel-perfect; missed refreshes OFF / ON):

  • Held keys (step e16d78afdddfd1f84690e79c): keys 23 / 15 pass, light 2 / 2 pass, STRESS drag 11 / 9 pass, keys-stress 45 / 86 FAIL (ON higher in 6 of 6 rounds). The rule's one rerun (step 41988e97abde8ca7dfc9f9c9): keys-stress 45 / 76, ON higher in 6 of 6 rounds: FAIL confirmed. The baseline at 8255dfd7ac42 was 46 / 89 (step ee5d440bbac9c256a471d656). So Release v0.46.0 #75 alone does not remove the held-key cost; the switch stays off (the flip is a separate PR).
  • Single presses (step f39f3cb82c7418e4b2decd3e): keys 16 / 12 pass, light 0 / 4 FAIL rule (b) (all in one round), STRESS drag 8 / 9 pass, keys-stress 17 / 13 pass.
  • This PR's own check (its OFF arm, the shipped path, against the OFF arm at 8255dfd7ac42): single keys 10 -> 16 missed, motion main p95 6.75 -> 9.21 ms (FAIL); held STRESS drag 6 -> 11 (FAIL rule (b)); the other 6 rows pass. The only change on the OFF path is Multi-tab notifications #73's per-surface wake counters. Spread check (Lead's order: is OFF 6 vs 11 inside the run-to-run spread?): yes, PASS. The OFF arm alone, base and pin interleaved (base, pin, pin, base) on cmux24-perf, 6 rounds, STRESS drag, keys and keys-stress in one launch, the same bench file on both. The base is this commit's parent d8936fb5a11. Steps: base 3e90283a, pin 3f6ba259, pin 19db9742, base b45bdbf2. Missed refreshes (pixel-perfect's two-build rule: a row fails only if both pin runs are worse than both base runs and the means differ by more than 1.25 x + 3, or motion p95 by more than 1.15 x):
    • STRESS drag: pin 19, 10 / base 16, 11 (motion p95 pin 7.73, 7.32 / base 7.64, 7.57 ms): pass. Pin 1's 19 is one round of 10; its other 5 rounds are 9.
    • keys: pin 13, 8 / base 12, 9 (p95 7.07, 6.77 / 7.00, 6.70 ms): pass.
    • keys-stress: pin 6, 10 / base 11 (p95 7.77, 8.43 / 8.98 ms): pass. Base 2's 6 keys-stress rounds are dropped: mediaanalysisd used 2.6-2.9 s CPU in each (over the 1 s limit). Both pin runs are below the one valid base, so this row cannot fail.
    • Background CPU per round in every other round: at most 0.05 s. No other round is dropped.
      So the earlier OFF 6 vs 11 on the held STRESS drag was run-to-run spread (and a base 12 commits older), not Multi-tab notifications #73's counters. No change moves the counters.
      Single light ON 4 / OFF 0 is flip-only (the ON arm), so it goes to the flip PR's gate, not to this PR. If the difference is outside the spread, Multi-tab notifications #73's counters move off the hot path in the fork first, and this PR waits for that.

Observed / Not run

  • Observed: the fork runs above, the terminal and display suites at this head (all pass), the baseline A/B, the single-press and held-key gate A/B at this head and the held-key rerun (all steps exit 0; results above), and prepush-gate.py passes.
  • Observed also: the 4 OFF spread runs above (pass).
  • Base finding (not caused by this PR): cmux-next swift test is red on run 41925421646. 12 of its 17 failing tests fail the same way on 4 unrelated PRs of the same hour (41941156936, 41940386041, 41939732509, 41937839721). The other 5 (countsByDomainMatchInventory, exportIsFresh, reportIsFresh, checkedInSchemaMatchesTheCatalog, paletteTitlesUseTitleCase) check generated action and schema files that this PR does not touch; the merge ref was built at 22:27Z from a feat state with those files stale, which the base autofix 333c3976d0f8 (23:24Z) regenerated.
  • Not run: the flip. The flip PR turns the switch on only if its gate A/B shows no layout worse with ON.

Frames: terminal: with the switch off (the default), what the person sees in each frame of a key resize or a divider drag does not change: the same frames as at the old pin 7d43bb68b; the only OFF-path change is #73's wake counters. With the switch on (not shipped), a frame that the layer cannot show at its bounds no longer wakes the main thread; the frames shown are the same.

Changelog

none

…edder presents itself wakes no main-thread present

GhosttyNextKit and the ghostty-next gitlink move together from 7d43bb68b to
6cc749a8a on release/cmux-9ce9b7d0-e2:
- #72: ghostty.h's lock-span rule for ghostty_surface_host_frame's present
  (comments only);
- #73: each surface's thread wakes counted by source in
  ghostty_surface_render_counters (new fields at the end; cmux reads the struct
  by field);
- #75: with the embedder presenting host sizes (motion.frameOwner.terminals
  on), a frame kept for its present that the layer cannot show in its bounds
  goes to no main-queue block. That block was dropped on main: one main-thread
  wake per moving terminal and frame, the held-key cost that keeps the switch
  off (keys-stress A/B at 8255dfd: ON missed more refreshes in 6 of 6
  rounds).

Release xcframework-6cc749a8a53b1d62465e0faf848e430a82f690aa-apple-v6 (run
41922548637), zip sha256 018d2bdb8b7f26c9e97ff6732b4e80e52f24ca3086e456efe0fb6d8aa38f4a40
(SHA256SUMS). None of the changed fork files is in the libghostty-vt link
graph, so vt-link-graph.json changes only its commit; package-notices names
the new paths (src/renderer, src/termio/Thread.zig, src/apprt/embedded.zig,
include/ghostty.h, NEXT.md; no license file or dependency change).
@lawrencecchen lawrencecchen added the risk:normal Risk tier (docs/ci/risk-tiers.md) label Nov 4, 2026
@github-actions

github-actions Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

cmux status (one comment, edited in place; each line is updated by the job that owns it)

Review 44ab81a: HQ feed card · gallery before/after runs · checks

App of this head (app.zip, kept 1 day)

gh workflow run reload-build.yml -R manaflow-ai/cmux --ref feat-cmux-next -f tag=pr35900 -f ref=fix/ghosttykit-pin-embedder-wake -f platform=macos -f backend_mode=local

Shipped in:

@lawrencecchen

Copy link
Copy Markdown
Contributor Author

Review (independent subagent): SHIP at 44ab81a

I reviewed the exact head in a new detached worktree, read only. I did not build, test or run the app. I found no correctness defect in the pin, the API use, the notice files or the doc. The PR description has errors. Correct it before you add ready-to-land. You do not have to push for that.

What I checked (correct)

  1. Pin. The ghostty-next gitlink and the URL at Packages/Shared/CmuxGhosttyKit/Package.swift:144 both name 6cc749a8a53b1d62465e0faf848e430a82f690aa. The checksum at :145 is equal to the zip line of the release SHA256SUMS, to the GitHub asset digest and to manifest.json archive.sha256. The manifest commit is 6cc749a8a53b…. The ghostty.h of all three zip slices is byte-identical to the fork include/ghostty.h at 6cc749a8a. Release run 41922548637 is the push run of 6cc749a8a on release/cmux-9ce9b7d0-e2, and it passed.
  2. Fork diff 7d43bb68b..6cc749a8a (8 files, no file added or removed).
    • API and layout: ghostty_surface_render_counters_s gets 7 uint64_t fields at the end. The Zig extern struct has the same 23 fields in the same order. All cmux readers read by field (TerminalSession+Diagnostics.swift:105, TerminalSurfaceView+MotionSample.swift:32, 3 tests). No cmux code names the type, uses a memberwise init or uses its size. cmux-tui does not use it. The other ghostty.h changes are comments.
    • Switch OFF (the default): the new skip in Metal.present runs only when embedder_presents is true. cmux sets EMBEDDER only when motion.frameOwner.terminals is on (TerminalSurfaceView+FrameOwner.swift:171, default false at TerminalDragTunables.swift:30). The other OFF changes are atomic counter adds on the renderer, termio and GPU completion threads. The main thread gets no new work. A person sees no change.
    • Switch ON: the new ghostty.h rule says: after the embedder gives bounds without a present, it calls ghostty_surface_host_frame(surface, true) when the answer is READY. cmux already does this. TerminalFramePresenter.swift:153-157 presents a READY watched view under the lock. Each path that gives bounds without a present (fallback, NONE, window move, switch off) watches the view or presents the kept frame. Both threads lock the CA lock first, then fresh.mutex. TerminalFrameOwnerMainThreadTests (switch on, main-thread stall guard) passed at this head.
    • REVIEW.md section 1: the fork test asserts the new behavior (0 main-queue presents, and the embedder still shows the frame). Red run 41915563142 fails with expected 0, found 1. Green run 41917705365 (109/109, lib-vt 97/97) tests cd11dc544. Its code is the same as the merged 5f97f6389, except ghostty.h comments.
  3. Notices. No @import line changed. LICENSE, build.zig.zon, vendor/ and pkg/ are identical. Thus the packages and vendored directories of vt-link-graph.json cannot change, and a commit-only edit is correct. The earlier pin moves (Frame owner S4: terminals ready-or-keep, bounds and pixels in one commit (behind motion.frameOwner.terminals, off) #33956, ghostty-next: a clear of rows (ED, DECALN) breaks the soft wrap across the cleared rows' edges #30012, ghostty-next: the wrap edge break reads a compressed history row only when the pair was joined #29775, ghostty-next: a scroll region scroll breaks the soft wrap across its edges (VT fuzz finding E2) #29609) did the same. I ran the two CI data checks at this head. Both pass: check_ghostty_vt_notices.py --check-link-graph and package_notices.py check-data. The license_tree text at cmux-tui/dist/notices/package-notices.json:12 names the correct changed paths.
  4. Doc. docs/ghostty-fork.md:17-31 is correct: 6cc749a8a is the merge of fork PR 75 on bd7ff2687 (fork PR 72 on fork PR 73 on 7d43bb68b). The release name, zip hash and run are correct.
  5. Suites. Step 80c78c6554c4b3a313463af7: all 42 suites passed. Step 15c50cdb6a3a9a3150dddcc2: 3 suites passed (2, 1 and 6 tests; misplaced 0, unsized 0, stretched 0; PTY grids [1, 1]). The failed step ac3662e31afed4a0f3716afc has the stale-module error that the body names.

Findings (description only; no push is necessary)

F1. The Testing section is out of date. It says that the A/B steps "run" and "Not run yet: the suites and the gate A/B". All four steps are done with exit 0 (e16d78af at 23:12Z, f39f3cb8 at 23:29Z). Why it matters: the body is the record of what you saw. Fix: replace the "Not run yet" line with the results below.

F2. The held-key A/B does not support the Summary sentence "This is the held-key cost that keeps the switch off." Missed refreshes, OFF / ON:

layout held, this head e16d78af held, baseline 8255dfd7ac42 (ee5d440bbac9c256a471d656) single, this head f39f3cb8 single, baseline 0a1e445d
keys 23 / 15 19 / 16 16 / 12 10 / 15
light 2 / 2 0 / 2 0 / 4 0 / 0
stress 11 / 9 6 / 13 8 / 9 7 / 11
keys-stress 45 / 86 46 / 89 17 / 13 14 / 23

Held keys-stress at this head: ON is higher in 6 of 6 rounds (OFF 6, 6, 10, 6, 5, 12; ON 13, 15, 16, 16, 13, 13), and 86 > 1.25 x 45 + 3. This row fails your gate rule, almost the same as at the baseline. The pin closes the single-press keys-stress gap (ON 13 / OFF 17; it was 23 / 14), but not the held-key gap. Why it matters: the flip decision uses this PR. Fix: in the Summary, say that fork PR 75 removes the per-frame main wake and fixes the single-press rows, and that held keys-stress still fails the gate. Cite the held baseline step above. Optional, in a later PR: at Package.swift:119, change "held-key cost" to "a per-frame main wake in a held key resize".

F3. Your own rule needs one rerun of each of these rows:

  • single light, ON against OFF: 4 vs 0 (all 4 in round 6); 4 > 1.25 x 0 + 3.
  • your OFF arm against the baseline OFF arm: single keys 16 vs 10 (higher in 4 rounds; motion main p95 9.21 vs 6.75 ms); held stress 11 vs 6 (11 > 10.5).

I do not hold the PR for the OFF rows. With the switch off, the fork adds no main-thread work, so these are most probably run-to-run noise. But the body says the OFF arm "must also pass". Record the reruns, or remove that sentence.

F4. REVIEW.md section 12: add a Frames: line for terminals. Example: "Frames: terminal. Switch off: no change. Switch on: the same frames as before. Main dropped the skipped block before, and the presenter still shows each new size's frame with its bounds. No content stops following its pane."

Coordination with #35478

Base finding (not caused by this PR)

ios_notices.py check-repo fails at this head and at its parent with the same 4 errors. scripts/cmux-next/notices/ios/link-set.json:270, scripts/cmux-next/notices/ghosttykit-macos-link-set.json:24 and ios/cmux/Settings.bundle/Acknowledgements.plist:5534 still name 9ce9b7d08 (pin 4277faf01e59). No PR job runs this check (cmux-next-notices.yml is dispatch-only). Regenerate the 4 files with ghosttykit_repin.py in a separate PR.

Not done by me: no build, no test, no app. I read the step logs and the A/B reports with cmux-ci. The cmux-next swift test check was pending.

Review (independent subagent): SHIP at 44ab81a

@lawrencecchen

Copy link
Copy Markdown
Contributor Author

Base finding, not caused by this PR: cmux-next swift test (run 41925421646, job 126910277530) is red.

  • 12 of its 17 failing tests fail the same way on 4 unrelated PRs of the same hour: 41941156936, 41940386041, 41939732509, 41937839721.
  • The other 5 (countsByDomainMatchInventory, exportIsFresh x2, reportIsFresh, checkedInSchemaMatchesTheCatalog, paletteTitlesUseTitleCase) compare generated action and managed-preferences files with the catalog. This PR changes only the Ghostty pin files (Package.swift, the gitlink, notices, docs/ghostty-fork.md). The merge ref was built at 22:27Z from a feat state where those generated files were stale; the base autofix 333c3976d0f8 (23:24Z) regenerated them.

@lawrencecchen lawrencecchen added the ready-to-land Reviewed at this head; next-autoland merges it into feat-cmux-next when its only reds are base reds label Nov 5, 2026
@lawrencecchen
lawrencecchen merged commit b490f09 into feat-cmux-next Nov 5, 2026
372 of 393 checks passed
@lawrencecchen
lawrencecchen deleted the fix/ghosttykit-pin-embedder-wake branch November 5, 2026 02:00

This branch was previously deployed

1 inactive deployment
ci-routing-agent — 44ab81aa Deployed Nov 4, 2026 by lawrencecchen via namespace autofix #14634
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-land Reviewed at this head; next-autoland merges it into feat-cmux-next when its only reds are base reds risk:normal Risk tier (docs/ci/risk-tiers.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant