Skip to content

fix(cmux-next): Release compile crash on main-actor generic class deinits (macOS 14 target) - #20408

Merged
teamleaderleo merged 2 commits into
feat-cmux-nextfrom
op-next-icons/release-compile-crash
Oct 14, 2026
Merged

teamleaderleo merged 2 commits into
feat-cmux-nextfrom
op-next-icons/release-compile-crash

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

The fleet Release compile of feat-cmux-next is red. swift-frontend (Xcode 26.6) crashes in EarlyPerfInliner (isCallerAndCalleeLayoutConstraintsCompatible) on the deinit of ThemedPicker.Coordinator, a MainActor class nested in the generic ThemedPicker<ID>. The crash started after the package moved to a macOS 14 deployment target (ae36648).

The cause is one class of bug. In the UI targets (defaultIsolation(MainActor)) a class's deinit is isolated, and below macOS 15 that runs through the back-deploy path. The Release inliner crashes on it for a generic class. TaskLocal+InTask's ImmediateRun already opts out with nonisolated deinit {}.

  • ThemedPicker: the pop-up's target is now one non-generic class, ThemedPickerTarget. On each update the picker sets the closure it calls. Behavior is unchanged: a choice still writes the binding, and an out-of-range index is still ignored.
  • CEFReplyWaiters and BrowserHistoryWriter.Once, the remaining main-actor generic classes, get a nonisolated deinit {}. The first fix's compile crashed next on CEFReplyWaiters. Neither class had a deinit body.

A scan of the package's Sources (generic classes, and classes nested in generic types) finds no other main-actor generic class without a nonisolated deinit.

Verification

  • Red: fleet Release compile of base afbe6fa crashes on ThemedPicker.Coordinator's deinit.
  • Green: fleet Release compile of this head (receipt in a comment below).
  • check-concurrency.sh and crash_ratchet.py pass.

Changelog

none

🤖 Generated with Claude Code


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


Summary by cubic

Fixes the fleet Release compile crash of feat-cmux-next: swift-frontend (Xcode 26.6) crashed in EarlyPerfInliner on the deinit of ThemedPicker.Coordinator, a MainActor class nested in the generic ThemedPicker<ID>, after the package moved to a macOS 14 deployment target.

The cause is one class of bug. In the UI targets (defaultIsolation(MainActor)) a class's deinit is isolated, and below macOS 15 that runs through the back-deploy path. The Release inliner crashes on it for a generic class; TaskLocal+InTask's ImmediateRun already opts out with nonisolated deinit {}.

  • ThemedPicker: the pop-up's target is now one non-generic class, ThemedPickerTarget. On each update the picker sets the closure it calls. Behavior is unchanged: a choice still writes the binding, and an out-of-range index is still ignored.
  • CEFReplyWaiters and BrowserHistoryWriter.Once, the remaining main-actor generic classes, get a nonisolated deinit {}. Neither had a deinit body.
  • A scan of the package's Sources finds no other main-actor generic class without a nonisolated deinit.

Verification

  • Fleet Release compile of the old base crashes on ThemedPicker.Coordinator's deinit; this head compiles green.
  • check-concurrency.sh and crash_ratchet.py pass.

Written for commit 503464c. Summary will update on new commits.

View guided diff Turn on auto-fix

The fleet Release compile of feat-cmux-next crashed swift-frontend (Xcode 26.6,
EarlyPerfInliner in isCallerAndCalleeLayoutConstraintsCompatible) on the deinit of
ThemedPicker.Coordinator, a MainActor class nested in the generic picker, once the
package targeted macOS 14. The pop-up's target is now one non-generic class that
calls a closure the picker sets on each update.

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

coderabbitai Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Repository: manaflow-ai/cmux/.coderabbit.yaml
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 621ac81e-9c32-4cb1-b8d8-35bac4ca48f9

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Not verified: macOS jobs did not run: compile and app tests were skipped on 503464c8f6; ci-status is green without them.

CI passes on 503464c8f6 (run 38505906780 attempt 1).
macOS jobs did not run: compile and app tests were skipped: Fast static checks skipped (the macos job needs both to succeed). Re-run CI once they pass, before merging.

Written by scripts/ci/classify_failures.py (ci-failure-attribution.yml); signatures are its SIGNATURES table. A machine verdict is the runner's fault, not this PR's; yours means the failing file is one this PR changes, also red on main that main's latest full suite fails the same way, seen on other PRs that it failed on another pull request's run lately.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Bisect from op-next-continuity, fleet Release compiles:

The only Package.swift change in the gap is ae36648 (deployment target 26 to 14); its own compile was cancelled. The red runs crash the same way: swift-frontend 6.3.3, -O -wmo, arm64-apple-macos14.0, EarlyPerfInliner isCallerAndCalleeLayoutConstraintsCompatible on ThemedPicker.Coordinator's deallocating deinit, exit 65 with no error line. This PR removes the generic nested coordinator, the only class the crash names.

The next Release compile crashed the same way on CEFReplyWaiters' deallocating
deinit. Under the UI targets' MainActor default isolation a class's deinit is
isolated, and below macOS 15 that goes through the back-deploy path the inliner
crashes on for a generic class (TaskLocal+InTask's ImmediateRun already opts out).
CEFReplyWaiters and BrowserHistoryWriter.Once, the remaining main-actor generic
classes, now have a nonisolated deinit; neither had a deinit body.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@teamleaderleo teamleaderleo changed the title fix(cmux-next): Release compile crash in ThemedPicker's coordinator fix(cmux-next): Release compile crash on main-actor generic class deinits (macOS 14 target) Oct 14, 2026
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Fleet Release compile on 503464c: exit 0 (check-release-compile.sh, isolated). It's green past both earlier crash sites: ThemedPicker in CmuxNextDesign and CEFReplyWaiters in CmuxNextBrowser. Review subagent: no blockers. lint-ios-package-conventions.sh, check-concurrency.sh and crash_ratchet.py pass.

@teamleaderleo
teamleaderleo merged commit 215fe45 into feat-cmux-next Oct 14, 2026
106 of 120 checks passed
@teamleaderleo
teamleaderleo deleted the op-next-icons/release-compile-crash branch October 14, 2026 03:38
azooz2003-bit added a commit that referenced this pull request Oct 14, 2026
…x in shipped strings

- The How to Connect and Allow iPhone Connections texts say cmux, not cmux cnx, in every language; schema, MDM docs and page strings regenerated.
- The renamed production service URL is defined but not used until its secrets are in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
teamleaderleo added a commit that referenced this pull request Oct 15, 2026
The Release compile crashes swift-frontend 6.3.3 in EarlyPerfInliner on
the isolated deinit of this main-actor generic class (exit 65, no error
line), the same crash #20408 fixed elsewhere.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
teamleaderleo added a commit that referenced this pull request Oct 15, 2026
…Release compile crash) (no bead) (#20876)

* cmux-next: mark the Chief profile relay retry loop as bounded (no bead)

check-concurrency flags its while true as a loop that can spin. It retries
once, only after a lost connection, and every other error throws.

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

* cmux-next: nonisolated deinit for PageCallDeadline.Once (no bead)

The Release compile crashes swift-frontend 6.3.3 in EarlyPerfInliner on
the isolated deinit of this main-actor generic class (exit 65, no error
line), the same crash #20408 fixed elsewhere.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
teamleaderleo added a commit that referenced this pull request Oct 15, 2026
* test(acpmux): a chat its harness never titles gets a generated title (cx-ncc.91)

Red: acpmux keeps the first prompt as a Claude chat's title, and has no
title command. The tests use a fake title command; none reaches a model.

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

* test(acpmux): format the generated-title test (cx-ncc.91)

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

* acpmux: generated titles for chats their harness never titles (cx-ncc.91)

Claude Code's -p stream-json has no title event, so a Claude chat kept its
first prompt's line as its title, and so did the workspace named after it.
After a chat's first completed turn, acpmux asks a title command once for
3-6 words: titles.command (or ACPMUX_TITLE_COMMAND), else the chat's own
Claude Code profile with --model haiku, no tools, MCP or session file. A
harness title always wins. The result is a titled record that watchers hear
of as session_changed. titles.generate: false turns it off.

The session-changed kind table moves to server/change_kind.rs and the test
to its own binary: server/mod.rs and hub_integration.rs are over budget.

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

* chore(next): PR autofix at c7394b9

- regenerated schemas/defaults/cmux-next.json

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38542576704

* acpmux: title only a chat's own first prompt, without the person's hooks (cx-ncc.91)

Review: a chat titled before generated titles, or adopted, was retitled on
its next turn from its latest prompt; a queued second turn could swap the
prompt and empty the reply first; the title call ran the person's Claude
hooks; a wrapper's children outlived the timeout.

Now only a title set from this chat's first prompt (title_source prompt) is
replaced, from that prompt as dispatched, with the reply only while no later
turn has started. The default command passes disableAllHooks, and a timeout
ends the command's whole process group.

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

* test(acpmux): the title test watches session changes, as the app does (cx-ncc.91)

_acpmux/session_changed reaches only connections that called _acpmux/watch.

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

* acpmux: an adopted chat keeps its title; end a timed-out title group first (cx-ncc.91)

Re-review: an adopted chat whose replay set no title (history from the
harness, no replayable turns) took its first prompt here as a prompt title,
so a later prompt was sent to the title model. check_resumed, which every
adoption passes, now marks it adopted. On a timeout the title command's
group is killed while its unreaped leader still holds the group id.

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

* acpmux tests: every acpmux process a test starts gets a hermetic environment (cx-s8wux)

One helper, tests/support/daemon_env.rs, starts the acpmux binary for every
integration test: env_clear, a bare PATH (python3 plus named host tools,
then the system directories), a scratch HOME, and ACPMUX_AGENT_LAUNCH=deny
unless the test drives a stand-in agent (Agents::StandIn keeps the bare
PATH). The 26 test files that started the binary with the host environment
use it; tests/hermetic_daemon_guard.rs refuses a test file that names
CARGO_BIN_EXE_acpmux anywhere else.

Root cause of cx-w014j: a fleet host's PATH holds real codex/claude, which a
test daemon discovered and ran (probes, sign-in checks, auto-updates).

* acpmux tests: the hermetic HOME is <scratch>/home, never a chat's folder

* acpmux tests: review fixes for the hermetic environment (cx-s8wux)

python3 links the interpreter itself (sys.executable), not a version
manager's shim; stale tool links from an earlier run are replaced; the guard
also flags daemon_env::bin() outside a listed use; the live codex test probes
only codex; codex_bridge_config's discovery test runs with launches denied.

* acpmux tests: router_install_secret starts the router through daemon_env (cx-s8wux)

* cmux-next: fix two Chief profile relay base reds (concurrency check, Release compile crash) (no bead) (#20876)

* cmux-next: mark the Chief profile relay retry loop as bounded (no bead)

check-concurrency flags its while true as a loop that can spin. It retries
once, only after a lost connection, and every other error throws.

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

* cmux-next: nonisolated deinit for PageCallDeadline.Once (no bead)

The Release compile crashes swift-frontend 6.3.3 in EarlyPerfInliner on
the isolated deinit of this main-actor generic class (exit 65, no error
line), the same crash #20408 fixed elsewhere.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cmux-next): the Chief profile relay's retry is a two-attempt for loop, not while true with a wakeup-allow (cx-ebm.59.5)

* cmux-next sidebar perf round 3: Release-build hitch bench with real wheel input; O(1) hover-card hit

- scripts/cmux-next/sidebar-rows-perf-release.py: runs in a Release tagged app (no debug.* methods) inside
  nx-remote --needs input: CLI workspaces (50 with a terminal, 950 empty), HID-tap wheel events, Animation
  Hitches per gesture, A/B of two apps in one job (side by side, interleaved), traces and the recorder's ktrace
  files removed after each export, PTY count logged.
- SidebarListView+HoverCard: the hover-card hit reads the list's row dictionary instead of
  SidebarModel.isPlaceholder, an O(workspaces) section walk on every scroll frame (7-12% of main-thread samples
  while scrolling 1,000 rows in Release).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Channels: a primary or danger button keeps its fill under the pointer (a soft ring shows the hover); gallery play primary-hover

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Channels gallery: primary-hover asserts the hover ring and the unchanged fill; a budget with a reason for the Create frame

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* cmux-motion: Chrome tab strip tween (200 ms easeOutQuad, restart from value), tabStrip geometry tokens, damping <= 0 takes the fallback (hq-1d, no bead)

Additive: MotionTween {TabOpen, TabClose, TabMove}, TweenCurve::EaseOutQuad,
ease_out_quad, TweenParameters, TweenValue (f64 core: retarget restarts the
duration from the current value with no velocity; the same target does not
restart), Tween (f32, token-driven), MotionPolicy::tween (0 under speed off,
Reduce Motion, or tabOpen/tabClose with ui.animations.newTab off),
TabStripGeometry (design-tokens.json motion.tabStrip: max 200, open/close
18, min 56 active / 32 inactive / 64 pinned, reorder 16 x width / 200,
closing mode +40 below / +60 trailing / 0.3 s). cmux-app-ffi ABI 2 exports
cmux_motion_tween_{base,seconds,retarget,step}, cmux_motion_ease and
cmux_motion_tab_strip_geometry. SpringParameters::finite_or now treats a
damping fraction <= 0 like NaN (hq-4e nit). The parity corpus example
prints tween paths (60 Hz: 12 frames for 200 ms).

* acpmux: record a session's git branch and HEAD at start (hq#1920) (#20840)

* test(acpmux): record a session's git branch and head at start

A session should keep its folder's branch and HEAD commit as they were at
session/new, and a fork its own folder's state at fork time (hq#1920), so
agent spend can be joined to the pull request a session produced. These
fail today: neither the saved meta nor _acpmux/info carries gitBranch or
gitHead.

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

* feat(acpmux): record a session's git branch and head at start

session/new and session/fork now store the cwd's checked-out branch and
full HEAD commit in the session meta (gitBranch, gitHead), so a session
can later be joined to the pull request it produced (hq#1920). The values
are read once, as the session starts, from the repository's own files
(HEAD, loose refs, packed-refs, a linked worktree's gitdir and commondir),
never a git process, and off the async runtime. A detached HEAD records
only the head; a folder outside a repository records neither; a lookup
that fails never fails the session. A fork reads its own folder at fork
time instead of copying the parent's. _acpmux/info carries both fields.
Old session.json files load unchanged.

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

* test(acpmux): git state skips non-files, bad branch names and missing folders; follows symlinks

Review of #20840: a FIFO HEAD hung session/new, a symlinked folder was walked
lexically, a missing fork folder took its parent repository's state, and an
invalid ref name was stored as the branch. The test git also runs without the
machine's global or system config.

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

* fix(acpmux): git state reads only small regular files, the physical folder and valid ref names

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* PR dock chips; tooltips only where nothing else names the control (#20375)

* test: no tooltip on an element whose text shows

Red on base: 105 elements with visible text carry title/data-tooltip.
Command: bun test test/tooltip-rules.test.ts

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

* fix: PR dock chips; tooltips only where nothing else names the control

The pull request dock's bars become content-sized chips (icon, #N, repo, title cut off at
360 px) with the dismiss button inside and no tooltip. Across the pages, a row, chip, link,
path or thought whose text shows carries no title tooltip any more; tooltips stay on
icon-only controls, shortcuts and text shown nowhere else (why a control is disabled or
failed, who reacted, policy detail), each marked tooltip-allow. Tooltips wait the native
hover delay (about a second) and show at once when moving straight on to the next one.

Green: bun test test/tooltip-rules.test.ts

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

* fix: review of tooltip audit

Keep the PR dock's state colours, drop four more tooltips the lint now finds inside
components (a setting's summary, the branch and folder pickers, a video), let a click or
scroll end the quick next-tooltip window, and move two tests to the no-tooltip rule.

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

* test: Backdrop Art's line has no tooltip

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

* chore(next): PR autofix at 047c247

- vp fmt webviews/src/pages/history/HistoryPage.tsx

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38502564051

* test(mock): the worked chat opens its pull request, so mock mode shows the PR dock

Captures of the agent pane in a real window (--showcase runs the in-page
mock) now show the pull request dock.

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

* test: the PR dock takes its own space; the transcript ends above it

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

* chore(next): PR autofix at 39f3ca9

- vp fmt webviews/test/ime-composition-keys.test.ts
- vp fmt webviews/test/new-tab-omnibar-pointer.test.ts

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38567629461

* chore(next): PR autofix at c7cb261

- regenerated Packages/macOS/CmuxNext/ci-target-graph.json

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38592980576

* fix: a channel image keeps its name tooltip (the name shows only when it fails)

Base added title={part.name} on channel image attachments (AttachmentViews.tsx).
A loaded image shows no name, so the tooltip is its only label; mark it with
tooltip-allow. tooltip-rules.test.ts: red before, green after.

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

* chore(next): PR autofix at f221864

- regenerated Packages/macOS/CmuxNext/ci-target-graph.json

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38633116142

* fix: keep the tool card scope hint and Steer shortcut tooltips

Both explain something shown nowhere else, so they stay under tooltip-allow.

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

* chore(next): PR autofix at 022cc8e

- vp fmt webviews/src/pages/home-channels/cloudHome.ts

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38647981824

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: cmux-autofix[bot] <cmux-autofix[bot]@users.noreply.github.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: cmux-autofix[bot] <cmux-autofix[bot]@users.noreply.github.com>
Co-authored-by: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com>
teamleaderleo added a commit that referenced this pull request Oct 15, 2026
#20880)

* cx-tgf4h red: cmux chief -p under ACPMUX_AGENT_LAUNCH=deny fails at once with a typed refusal

* cx-tgf4h: the Chief says at once that agents are disabled in this build session

With ACPMUX_AGENT_LAUNCH=deny (the nx-remote gui/input default since HQ
#2130) or CMUX_AGENTS_DISABLED=1, no Chief turn can start an agent.
`cmux chief -p` started a daemon and a brain and waited 240 s for a reply
that could not come.

- cmux-tui-core agent_launch: one test of the kill switch (acpmux
  launch_policy.rs), the reason agent_launch_disabled.
- `cmux chief` (pipe and chat) refuses before it starts or sends anything:
  operation.failed, details.reason agent_launch_disabled, exit 1.
- The daemon's supervisor starts no Chief brain; chief.engine.* and
  chief.stop answer the typed refusal at once.
- optchat-chief host does not start (logs why, exit 0).
- Home: the local Chief conversation's notice says agents are disabled;
  the engine panel maps the typed refusal (localized, all Home languages).
- Every Chief proof script refuses at once under deny and names
  `nx-remote --allow-agents`.

* test: code pages follow the terminal font; font variables resolve

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

* cx-tgf4h review: forward chief control to a running brain first; daemon-side deny test; engine/stop CLI text; guard before side effects; fmt

* acpmux tests: every acpmux process a test starts gets a hermetic environment (cx-s8wux)

One helper, tests/support/daemon_env.rs, starts the acpmux binary for every
integration test: env_clear, a bare PATH (python3 plus named host tools,
then the system directories), a scratch HOME, and ACPMUX_AGENT_LAUNCH=deny
unless the test drives a stand-in agent (Agents::StandIn keeps the bare
PATH). The 26 test files that started the binary with the host environment
use it; tests/hermetic_daemon_guard.rs refuses a test file that names
CARGO_BIN_EXE_acpmux anywhere else.

Root cause of cx-w014j: a fleet host's PATH holds real codex/claude, which a
test daemon discovered and ran (probes, sign-in checks, auto-updates).

* cx-tgf4h: fmt and clippy

* acpmux tests: the hermetic HOME is <scratch>/home, never a chat's folder

* acpmux tests: review fixes for the hermetic environment (cx-s8wux)

python3 links the interpreter itself (sys.executable), not a version
manager's shim; stale tool links from an earlier run are replaced; the guard
also flags daemon_env::bin() outside a listed use; the live codex test probes
only codex; codex_bridge_config's discovery test runs with launches denied.

* acpmux tests: router_install_secret starts the router through daemon_env (cx-s8wux)

* fix: code pages draw in the terminal's font; agent pane defines --font-mono

The diff, markdown and code editor pages were written to follow the terminal font
(appearance.ts, editorFont), but no cmux NEXT host ever sent it, so code always drew
in Menlo. Every page theme now carries the terminal's family (cmux.json
terminal.fontFamily, else the Ghostty config's font-family), resent when it changes.

The agent pane's mono text read --font-mono, which only Tailwind's theme layer
emitted because a source file happened to name it; conversation.css defines it.

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

* cmux-next: fix two Chief profile relay base reds (concurrency check, Release compile crash) (no bead) (#20876)

* cmux-next: mark the Chief profile relay retry loop as bounded (no bead)

check-concurrency flags its while true as a loop that can spin. It retries
once, only after a lost connection, and every other error throws.

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

* cmux-next: nonisolated deinit for PageCallDeadline.Once (no bead)

The Release compile crashes swift-frontend 6.3.3 in EarlyPerfInliner on
the isolated deinit of this main-actor generic class (exit 65, no error
line), the same crash #20408 fixed elsewhere.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cmux-next): the Chief profile relay's retry is a two-attempt for loop, not while true with a wakeup-allow (cx-ebm.59.5)

* cmux-next sidebar perf round 3: Release-build hitch bench with real wheel input; O(1) hover-card hit

- scripts/cmux-next/sidebar-rows-perf-release.py: runs in a Release tagged app (no debug.* methods) inside
  nx-remote --needs input: CLI workspaces (50 with a terminal, 950 empty), HID-tap wheel events, Animation
  Hitches per gesture, A/B of two apps in one job (side by side, interleaved), traces and the recorder's ktrace
  files removed after each export, PTY count logged.
- SidebarListView+HoverCard: the hover-card hit reads the list's row dictionary instead of
  SidebarModel.isPlaceholder, an O(workspaces) section walk on every scroll frame (7-12% of main-thread samples
  while scrolling 1,000 rows in Release).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Channels: a primary or danger button keeps its fill under the pointer (a soft ring shows the hover); gallery play primary-hover

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Channels gallery: primary-hover asserts the hover ring and the unchanged fill; a budget with a reason for the Create frame

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* cmux-motion: Chrome tab strip tween (200 ms easeOutQuad, restart from value), tabStrip geometry tokens, damping <= 0 takes the fallback (hq-1d, no bead)

Additive: MotionTween {TabOpen, TabClose, TabMove}, TweenCurve::EaseOutQuad,
ease_out_quad, TweenParameters, TweenValue (f64 core: retarget restarts the
duration from the current value with no velocity; the same target does not
restart), Tween (f32, token-driven), MotionPolicy::tween (0 under speed off,
Reduce Motion, or tabOpen/tabClose with ui.animations.newTab off),
TabStripGeometry (design-tokens.json motion.tabStrip: max 200, open/close
18, min 56 active / 32 inactive / 64 pinned, reorder 16 x width / 200,
closing mode +40 below / +60 trailing / 0.3 s). cmux-app-ffi ABI 2 exports
cmux_motion_tween_{base,seconds,retarget,step}, cmux_motion_ease and
cmux_motion_tab_strip_geometry. SpringParameters::finite_or now treats a
damping fraction <= 0 like NaN (hq-4e nit). The parity corpus example
prints tween paths (60 Hz: 12 frames for 200 ms).

* fix: read the font from the files the Ghostty config loaded, includes too

Review: a font-family in a config-file include won in the terminal but not on pages.
Read the runtime's loaded files (with includes, in load order) instead of guessing
paths, repaint after the last override is set, and fix a stale doc comment.

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

* acpmux: record a session's git branch and HEAD at start (hq#1920) (#20840)

* test(acpmux): record a session's git branch and head at start

A session should keep its folder's branch and HEAD commit as they were at
session/new, and a fork its own folder's state at fork time (hq#1920), so
agent spend can be joined to the pull request a session produced. These
fail today: neither the saved meta nor _acpmux/info carries gitBranch or
gitHead.

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

* feat(acpmux): record a session's git branch and head at start

session/new and session/fork now store the cwd's checked-out branch and
full HEAD commit in the session meta (gitBranch, gitHead), so a session
can later be joined to the pull request it produced (hq#1920). The values
are read once, as the session starts, from the repository's own files
(HEAD, loose refs, packed-refs, a linked worktree's gitdir and commondir),
never a git process, and off the async runtime. A detached HEAD records
only the head; a folder outside a repository records neither; a lookup
that fails never fails the session. A fork reads its own folder at fork
time instead of copying the parent's. _acpmux/info carries both fields.
Old session.json files load unchanged.

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

* test(acpmux): git state skips non-files, bad branch names and missing folders; follows symlinks

Review of #20840: a FIFO HEAD hung session/new, a symlinked folder was walked
lexically, a missing fork folder took its parent repository's state, and an
invalid ref name was stored as the branch. The test git also runs without the
machine's global or system config.

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

* fix(acpmux): git state reads only small regular files, the physical folder and valid ref names

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* cx-tgf4h: keep CmuxNextApp line count (App size cap)

* acpmux: a pooled agent host no longer spins on Linux reading its own record (cx-7z8yx)

The record watch used notify's inotify backend, whose watch mask always
includes open, close-write and modify, and ran its callback on every event.
The callback reads the host record; on Linux that read queued an open and a
close in the watched directory, which ran the callback again. Every pooled
host spun at 100% CPU (about 200,000 read calls a second).

Linux now has its own inotify watch, like the macOS kqueue watch: a mask of
entry changes only (create, delete, moved from/to) and the directory's own
delete or move, one thread in poll with no timeout, an eventfd to stop it.
A read of the record never queues an event. The notify fallback (other
platforms) ignores access events. macOS is unchanged.

Behavior test (tests/host_lifetime.rs, Linux): a real daemon pools a host;
idle for 3 s it makes fewer than 100 read calls and 30 CPU ticks (before:
599,042 reads, 298 ticks), a rename rewrite of its record keeps it, and the
record removed ends it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* cmux-tui smoke: sidebar drag checks the drawn sidebar document order

Since sidebar-v2 S6 the TUI draws the daemon's sidebar document, where a new
workspace goes to the top (workspaces.newPlacement default), and a workspace
drop is one sidebar.move that changes the document only. The smoke still
assumed the list-workspaces order: it dragged the drawn top row (the new
workspace) below the original, which the product did, and then read
list-workspaces, which a sidebar.move never changes.

The smoke now reads the order from sidebar.get, checks that the rail draws
it, drags the top row below the second (a real reorder), and drags again when
needed so the original workspace ends below the new one.

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

* PR dock chips; tooltips only where nothing else names the control (#20375)

* test: no tooltip on an element whose text shows

Red on base: 105 elements with visible text carry title/data-tooltip.
Command: bun test test/tooltip-rules.test.ts

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

* fix: PR dock chips; tooltips only where nothing else names the control

The pull request dock's bars become content-sized chips (icon, #N, repo, title cut off at
360 px) with the dismiss button inside and no tooltip. Across the pages, a row, chip, link,
path or thought whose text shows carries no title tooltip any more; tooltips stay on
icon-only controls, shortcuts and text shown nowhere else (why a control is disabled or
failed, who reacted, policy detail), each marked tooltip-allow. Tooltips wait the native
hover delay (about a second) and show at once when moving straight on to the next one.

Green: bun test test/tooltip-rules.test.ts

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

* fix: review of tooltip audit

Keep the PR dock's state colours, drop four more tooltips the lint now finds inside
components (a setting's summary, the branch and folder pickers, a video), let a click or
scroll end the quick next-tooltip window, and move two tests to the no-tooltip rule.

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

* test: Backdrop Art's line has no tooltip

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

* chore(next): PR autofix at 047c247

- vp fmt webviews/src/pages/history/HistoryPage.tsx

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38502564051

* test(mock): the worked chat opens its pull request, so mock mode shows the PR dock

Captures of the agent pane in a real window (--showcase runs the in-page
mock) now show the pull request dock.

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

* test: the PR dock takes its own space; the transcript ends above it

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

* chore(next): PR autofix at 39f3ca9

- vp fmt webviews/test/ime-composition-keys.test.ts
- vp fmt webviews/test/new-tab-omnibar-pointer.test.ts

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38567629461

* chore(next): PR autofix at c7cb261

- regenerated Packages/macOS/CmuxNext/ci-target-graph.json

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38592980576

* fix: a channel image keeps its name tooltip (the name shows only when it fails)

Base added title={part.name} on channel image attachments (AttachmentViews.tsx).
A loaded image shows no name, so the tooltip is its only label; mark it with
tooltip-allow. tooltip-rules.test.ts: red before, green after.

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

* chore(next): PR autofix at f221864

- regenerated Packages/macOS/CmuxNext/ci-target-graph.json

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38633116142

* fix: keep the tool card scope hint and Steer shortcut tooltips

Both explain something shown nowhere else, so they stay under tooltip-allow.

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

* chore(next): PR autofix at 022cc8e

- vp fmt webviews/src/pages/home-channels/cloudHome.ts

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38647981824

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: cmux-autofix[bot] <cmux-autofix[bot]@users.noreply.github.com>

* ci: nightly-next recovery initializes the notices submodule before continuing accepted submissions

The recovery job's checkout had no ghostty-next submodule, so the bundle
notice check that notarize-nightly-dmg.sh runs after Apple accepts failed,
and run 38669958906 skipped five accepted builds as 'Apple did not accept'.
Initialize the submodule like the signing job does, and make the skip notice
say that a post-acceptance check can also be the cause.

* ci: the cmux NIGHTLY-identity job also initializes the notices submodule

Run 38669958906's publish-nightly-identity-legacy job saw Apple accept,
then failed the bundle notice check for lack of ghostty-next and retired
the accepted submission of run 38609024986.

* agent pane: progress and stop for a running Claude subagent or workflow (cx-liswe) (#20718)

* test: a running subagent or workflow shows what it does and what it has spent

Red on base: both rows read only "Running" (bun test
test/subagent-progress.test.tsx).

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

* webviews: a running subagent or workflow shows what it does and what it has spent

The subagent fold keeps acpmux's subagent_progress fields (action, tokens,
tool uses, a workflow's agents done of started), and the transcript's
subagent row adds them to its status line through one shared helper
(subagentProgress.ts) the summary list and the Background tasks pane can use.
Three plural strings, translated for every pane locale.

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

* test: a running subagent row has a stop button

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

* feat: stop a running Claude subagent or workflow from its row

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

* chore: restore the catalog's base order after the merge

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

* test: stopping a subagent leaves its row closed, and a refused stop says so

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

* fix: a subagent's stop button waits for Claude and says when the stop failed

The button is off while Claude has the request and until the subagent
ends; a refused or unanswered stop (10 s) turns it into "Couldn't stop
<name>" and it can be tried again. The pane no longer drops the error.
Also moves the stop wiring above showSubagent's doc comment and corrects
two comments.

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

* style: format the subagent progress test

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

* fix: stop rejects without English the pane would never show

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

* chore: restore the catalog's base order after the merge

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

* chore(next): PR autofix at 2fe6cc4

- vp fmt webviews/src/pages/home-channels/HomeChannelsPage.tsx

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38637059713

* test: the agent pane relays a subagent stop for its chat (cx-liswe)

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

* fix: the agent pane may stop a subagent of its chat (cx-liswe)

Adds _acpmux/subagent_stop to the pane's method allowlist: session-scoped,
params sessionId and subagentSessionId, no gesture (it stops work, like
_acpmux/kill). Without it the host refused every stop click.

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

* fix: mirror the subagent stop into the shared pane policy (cx-liswe)

The Swift allowlist took _acpmux/subagent_stop; policy.json, its parity
source for Rust hosts, gets the same request, session scope and params,
with cases for an in-scope stop, another chat and an unknown param.

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

* gallery: a light window with no theme named renders light (#20794)

* test(gallery): a light window with no theme named should show the light default

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

* fix(gallery): a light window with no theme named shows Apple System Colors Light

The gallery defaulted the theme to the dark side of the app's default pair
whatever the window appearance, so colorScheme=light (or Appearance: Light)
rendered every stage in dark colors under a light media query. The default
now follows the appearance, as the app's does with no theme set.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: cmux-autofix[bot] <cmux-autofix[bot]@users.noreply.github.com>

* Channels view: gallery plays for a message link with no link read (red)

A Cloud conversation has no link resolver (G4), so the Messages view's switch hand-off
#/conversation/<id>/message/<id> showed an error. Two plays: a thread reply and a timeline
message, opened through the store's openLink with an owner that has no resolver. cx-hnmbt

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Channels view: open a known conversation's message link without the owner's link read

When conversation-link-resolve fails (a Cloud conversation, an older daemon) and the page knows
the conversation, it opens it, pages the timeline until the message shows, opens its thread when
it is a reply, and marks it. The Messages view's switch hands off Cloud selections this way.
cx-hnmbt

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Channels view: format parseConversationLink (vp fmt)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Channels view: link fallback review follow-ups

Ids in a known conversation's link may hold '-' (the gallery's sample ids); the scheme check
follows the daemon (cmux[-<tag>], any case); the fallback runs only for a Cloud conversation or
an owner without a link read, so a refused local link (a deleted message) still shows its error.
cx-hnmbt

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* acpmux: host_lifetime idle test cleans up from the start and never signals a reused pid (cx-7z8yx review)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* cmux-tui: move the build identity into a leaf crate so an edit no longer recompiles cmux-remote

cmux-remote's build script hashed the dirty source tree into
CMUX_TUI_BUILD_IDENTITY, so every edit in the workspace reran it and
recompiled cmux-remote and every crate above it (about 8 s of a 12.6 s
one-line edit loop on rbx). The same build script now lives in
cmux-build-identity, which only cmux-tui reads; cmux-remote takes the
identity as an argument (SshBootstrapConfig::defaults,
pinned_linux_payload). The identity value, its inputs and format, and
every skew and SSH re-bootstrap comparison are unchanged.

* test: warm the dev server in the Claude reasoning default browser test (#20884)

#20592 landed after #20873 without warmViteServer, so check:browser-lanes is
red on base again.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* cmux-next: frame-strip report as an NSWindow method, not a namespace enum (no bead) (#20887)

The package conventions lint fails every PR on the new all-static public
enum WindowFrameStrip (cx-me3xd). The report reads its window, so it is an
extension on NSWindow; same body, same report.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Home channels: the run card is an inline disclosure (no frame, steps on an indented rail)

The live and done run cards under a Chief's reply lose their box: "Working
for" / "Worked for", the step count, cost and Open sit inline under the
message, and the opened steps hang on a thin rail under the status mark. The
narrow rules stay: the first line never wraps, and below 480 px of card width
the step count and cost move under it with no leading separator.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Home channels: the run rail and columns follow the author's picture

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Home channels: the run sheet keeps its error line; run card columns in logical properties

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Revert "cmux-motion: Chrome tab strip tween ..." (8a3ee62) until the app-ffi release App 403 is fixed (run 38682698310) (hq-1d, chief go 2026-10-15, no bead)

The release App could not publish cmux-app-ffi-854febb26527 (HTTP 403 on
POST /releases), so the app FFI pin check stays red on the tip. The tween
re-lands with the publish and repin after the App is fixed.

* test(ci): nightly jobs that run the license check must init ghostty-next (#20893)

* test(ci): nightly jobs that run the license check must init ghostty-next

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

* fix(ci): nightly recovery initializes ghostty-next before the license check

Apple accepted, stapled and launch-tested each recovered nightly-next build,
then verify-app-bundle-licenses.sh failed on the missing
ghostty-next/build.zig.zon and the build was skipped as not accepted.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* cmux-next: terminal-face repaint lives in DesignSettings, not the App target

Keeps CmuxNextApp under its size cap: setting either terminal family
repaints pages when the face the terminal draws in changed.

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

---------

Co-authored-by: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: cmux-autofix[bot] <cmux-autofix[bot]@users.noreply.github.com>
Co-authored-by: safe-push <safe-push@cmux.local>
teamleaderleo added a commit that referenced this pull request Oct 15, 2026
* feat(cx-gakp): unify slash commands, skills and prompts

Keep the existing Milkdown editor and its URL, mention and attachment behavior. Label ACP command sources so skills and prompts share the same searchable picker.

* chore(next): PR autofix at cade19a

- vp fmt webviews/src/agent-session/acpmux/composer.gallery.ts

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38535762014

* fix(cx-gakp): localize slash picker labels and cmux command descriptions

The Skill/Prompt labels and the four cmux command descriptions were
hard-coded English, which pane-english rejects. They now come from the
pane's table in all 21 languages; cmux commands carry a descriptionKey.

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

* fix(cx-gakp): keep Codex skills' $ prefix in the slash picker

codex-acp names each skill `$name` and passes `/$name` through to
Codex as a skill mention. Stripping the $ inserted `/name`, which ran a
same-named builtin or nothing, and deduped the skill behind cmux's own
commands. Keep the name and use the $ as the skill signal.

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

* chore(next): PR autofix at f4ed982

- regenerated schemas/defaults/cmux-next.json

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38541336447

* fix(cx-gakp): read the skill tag from ACP _meta in the slash picker

acpmux will tag Claude skills as _meta.kind (ACP keeps extensions in
_meta); read it alongside codex-acp's $ prefix.

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

* fix(cx-gakp): slash picker play finds its rows by name; harness stays harness

- The gallery play waited for exactly 3 rows, but cmux's /import shares
  the menu; it now finds the fixture rows by name, asserts Skill and
  Prompt on their own rows and no label on a harness command, and clears
  the "/" before typing the draft.
- The comment no longer claims acpmux tags Claude skills.
- /continue's description keeps "harness" as the catalog does elsewhere
  (17 locales said "tool").

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

* chore: regenerate pane strings after the base merge

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

* webviews: regenerate pane strings after the base merge

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

* Chief registry: chief.profile.adopt makes an existing agent participant a Chief record under its own id

The owner (a trusted local Unix caller acting as the person) adopts an agent that is already in a
conversation: the record keeps the participant's id and display name, so its DMs, history and
credential stay its own, and gets chief.profile.create's defaults and only the fields given.
A page (WebSocket or the host's page relay, even with a gesture), a paired or remote device and a
bound agent get origin.forbidden; there is no page route. agent_mux is default_chief, a live record
already_chief, a deleted Chief deleted_chief (an adopt never revives its tombstone or kept memory),
and a person or an unknown id not_an_agent_participant. The write goes through the daemon's
registry ledger (idempotent per key) and the same chief_written hook as a create.
Capability chief-profile-adopt-v1; CLI cmux chief adopt AGENT [PROFILE], refused for an agent
caller. Real-daemon tests for each refusal and for an adopt that keeps the DM and its history.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chief.profile.adopt review fixes: owner gate first, any page origin refused, brain directory collisions refused, tests for the event and the limit

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chief.profile.adopt: refuse a bound participant, revoke its earlier credentials in the same write, and only the brain (or, with no supervisor, the person's terminal) mints the Chief's credential

The adopt claims the participant inside the registry write: under the bindings lock it refuses a
participant with a bound connection (participant_bound, "stop its current agent session first")
and otherwise revokes every credential issued for it. conversation-bind now checks the credential
and binds under the same lock, so a revoked credential never binds after the adopt. For a Chief
record other than agent_mux, conversation-agent-token is refused while a brain supervisor runs
(the supervisor mints the brain's credential in process); with no supervisor only the person's
own terminal mints it, never a page, a paired or remote device or an agent. Tests: a bound
participant is refused; after it unbinds the adopt succeeds and the brain starts; the old
credential is refused; the brain's credential binds; the person mints the post-adopt credential
on a daemon with no supervisor; a page, a device and an agent cannot mint it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chief.profile.adopt: the Chief-credential check and the mint run under the registry lock

Review finding: a mint that passed the Chief check before an adopt could overwrite the brain's
credential after it. conversation-agent-token now checks and mints inside one registry-locked step
(registry, then the conversation store, the adopt's order).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Regenerate bindings after merge

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chief.profile.adopt: clippy (one write struct for mutate, no redundant clone)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* resource_attach: chief.profile.adopt is the 54th connection operation

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* cmux-next: CmuxNextApp cap back to 113692 (frame-strip proof analysis and animation palette toggles leave the App target) (cx-me3xd)

The debug.new_tab frame_strip analysis (frame comparisons, PNGs, report fields) moves to
WindowFrameStrip in CmuxNextDesign beside WindowFrame and MotionFrameProgress; the drag_new
gesture moves to TabStripDebug.dragFirstTab in CmuxNextTabs. DebugFrameStrip keeps only the
wiring (record, send key or gesture, spans), so plus, drag_new and every report field stay.
The ui.animations.<kind> toggles and bounce bind through AnimationKindsSetting.bindActions
(CmuxNextSettings) on the shared SettingChoiceActions save, as ChromeStyleSetting does (ported
from nx-grow-land-fallback b1e0c989). CmuxNextApp 113810 -> 113692; the cap follows.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* cx-tgf4h red: cmux chief -p under ACPMUX_AGENT_LAUNCH=deny fails at once with a typed refusal

* cx-tgf4h: the Chief says at once that agents are disabled in this build session

With ACPMUX_AGENT_LAUNCH=deny (the nx-remote gui/input default since HQ
#2130) or CMUX_AGENTS_DISABLED=1, no Chief turn can start an agent.
`cmux chief -p` started a daemon and a brain and waited 240 s for a reply
that could not come.

- cmux-tui-core agent_launch: one test of the kill switch (acpmux
  launch_policy.rs), the reason agent_launch_disabled.
- `cmux chief` (pipe and chat) refuses before it starts or sends anything:
  operation.failed, details.reason agent_launch_disabled, exit 1.
- The daemon's supervisor starts no Chief brain; chief.engine.* and
  chief.stop answer the typed refusal at once.
- optchat-chief host does not start (logs why, exit 0).
- Home: the local Chief conversation's notice says agents are disabled;
  the engine panel maps the typed refusal (localized, all Home languages).
- Every Chief proof script refuses at once under deny and names
  `nx-remote --allow-agents`.

* Channels view: gallery plays for the host's home.currentSelection (red: the page does not serve it yet)

The host-callable command type (hostActions.ts) and two plays: a conversation, the keyboard's
current message and the open thread; the docked list pane answers null. cx-hnmbt

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Channels view: home.currentSelection for the Home dock's view switch

The page serves window.cmuxHomeActions["home.currentSelection"] (the agent page's
cmuxAcpmuxActions pattern; the host calls it with callAsyncJavaScript): {conversation,
message?, thread?} = the open conversation, the keyboard's current message or the message a
link marked, and the open thread's root; null on the narrow list pane or with nothing open.
The host never reads the page hash for the hand-off. cx-hnmbt

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Channels view: serve home.currentSelection on the page bridge, not a window global

The host calls it with PageRouter.callPage (the typed host-to-page call the page bridge already
has; PageClient.handle on the page): {} -> {conversation, message?} | null. message = the
keyboard's current message, else the message a link marked, else the open thread's root. The
gallery plays drive a real BridgePageClient through __cmuxPageReceive, as the app does. cx-hnmbt

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Channels view: home.currentSelection review follow-ups

Layout effect for the answered selection; null before the page's first measure (a docked list
pane never reports a hidden conversation); the gallery host uses a private bridge target; the
thread case is its own play (the gallery opens the thread). cx-hnmbt

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* acpmux: Codex picks run the in-process bridge, not the npx codex-acp adapter (#20800)

* test(acpmux): the npx codex-acp adapter is offered beside the Codex bridge

Red: with codex and npx on PATH and no codex-acp, discovery keeps the
pinned npx adapter (@agentclientprotocol/codex-acp) as `codex-acp` next
to the in-process bridge. That adapter reads the account through codex's
account/read, which fails for a ChatGPT login behind a routing proxy
(subrouter): its sessions never start ("Authentication required").

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

* acpmux: Codex picks run the in-process bridge, not the npx codex-acp adapter

With codex installed and no codex-acp on PATH, discovery kept the pinned
npx adapter (@agentclientprotocol/codex-acp@1.10.0) as `codex-acp` beside
the in-process bridge. The adapter reads the account through codex's
account/read; with codex 0.162 a ChatGPT login behind a routing proxy
(subrouter) fails that ("selected workspace missing from routing
discovery"), so its chats never started ("Authentication required"),
while the bridge works on the same login.

Discovery no longer offers the npx adapter: an installed codex is the
bridge, and `codex-acp` names the bridge too (like `codex-app-server`), so
sessions, presets and model picks that recorded it run the bridge. A
codex-acp on PATH, an ~/.acpx entry and a config-written launch are
unchanged.

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

* acpmux: one description for the bridge's aliases (rustfmt)

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* cx-tgf4h review: forward chief control to a running brain first; daemon-side deny test; engine/stop CLI text; guard before side effects; fmt

* test: warm the dev server in three agent pane browser tests (#20873)

check:browser-lanes (#20733) requires warmViteServer after listen; #20752,
#20780 and #20785 landed tests without it, so react-apps-check is red on base.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* acpmux tests: every acpmux process a test starts gets a hermetic environment (cx-s8wux)

One helper, tests/support/daemon_env.rs, starts the acpmux binary for every
integration test: env_clear, a bare PATH (python3 plus named host tools,
then the system directories), a scratch HOME, and ACPMUX_AGENT_LAUNCH=deny
unless the test drives a stand-in agent (Agents::StandIn keeps the bare
PATH). The 26 test files that started the binary with the host environment
use it; tests/hermetic_daemon_guard.rs refuses a test file that names
CARGO_BIN_EXE_acpmux anywhere else.

Root cause of cx-w014j: a fleet host's PATH holds real codex/claude, which a
test daemon discovered and ran (probes, sign-in checks, auto-updates).

* cx-tgf4h: fmt and clippy

* acpmux tests: the hermetic HOME is <scratch>/home, never a chat's folder

* acpmux tests: review fixes for the hermetic environment (cx-s8wux)

python3 links the interpreter itself (sys.executable), not a version
manager's shim; stale tool links from an earlier run are replaced; the guard
also flags daemon_env::bin() outside a listed use; the live codex test probes
only codex; codex_bridge_config's discovery test runs with launches denied.

* cmux-tui: agent-session-attach takes the agent pane's whole kind list (up to 32; the pane asks 9 since prompt bookmarks), so a remote chat shows after Trust instead of falling back to New Tab with bad request (cx-kpd0v)

* agent pane: the attach asks at most 8 kinds again (session offers come with transcript), so an older remote cmux-tui shows the chat (cx-kpd0v)

* cmux-tui: test fake needs no mut (cx-kpd0v)

* acpmux tests: router_install_secret starts the router through daemon_env (cx-s8wux)

* cmux-next composer: keep base's catalog order; regenerate strings on the merged base

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

* acpmux: one typed protocol registry; generated web types and drift checks (hq#2025, no bead) (#20576)

* acpmux: typed protocol registry and IR (hq#2025, no bead)

One registry names every acpmux method and notification; sessions and
permissions carry serde/schemars types, and the daemon builds
SessionSummary and PermissionGroup from them. ir() emits
spec/acpmux-protocol.json for the web codegen. Tests fail when the
committed IR is stale or the dispatcher serves a name the registry lacks.

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

* webviews: acpmux protocol codegen, typed request helper and drift check (hq#2025, no bead)

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

* acpmux protocol: committed IR, generated web types; permissions and session_changed use them (hq#2025, no bead)

The permission group client and permission_respond go through the typed
request; session_changed and permission_pending are narrowed by the
registry's notification map. The IR declares a peer summary's optional
fields and peer key, permission_pending's always-sent keys, and closed
param objects.

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

* webviews: format direct.ts

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

* webviews: acpmux codegen lives beside the pane protocol codegen, outside the pane sources

Its developer-facing IR errors tripped pane-english, which scans
src/agent-session/acpmux for user-visible English.

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

* chore(next): PR autofix at 1bb7576

- cargo fmt in Native/OptChat/optchat-chief

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38569912312

* chore(next): PR autofix at c601de5

- regenerated Packages/macOS/CmuxNext/ci-target-graph.json

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38593588859

* chore(next): PR autofix at 7a7fb66

- regenerated Packages/macOS/CmuxNext/ci-target-graph.json

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38606761975

* acpmux protocol: register offer_session and offer_respond

Base (#20305) serves two new methods; the registry's completeness test
caught them. Both are untyped requests for now. Spec and generated
TS are regenerated.

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

* acpmux protocol: register prompt_withdraw

Base serves _acpmux/prompt_withdraw; the completeness test caught it. Spec and
generated TS are regenerated.

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

* chore(next): PR autofix at 4e1d3c9

- vp fmt webviews/src/pages/home-channels/HomeChannelsPage.tsx

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38637598208

* acpmux protocol: register the process methods

Base serves _acpmux/process/list and process/kill and sends
_acpmux/process_update; the completeness test caught them. Spec and generated
TS are regenerated.

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

* chore(next): PR autofix at 64ca080

- vp fmt webviews/src/pages/home-channels/cloudHome.ts
- cargo fmt in Native/OptChat/optchat-chief

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38649959912

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: cmux-autofix[bot] <cmux-autofix[bot]@users.noreply.github.com>

* cmux-next: fix two Chief profile relay base reds (concurrency check, Release compile crash) (no bead) (#20876)

* cmux-next: mark the Chief profile relay retry loop as bounded (no bead)

check-concurrency flags its while true as a loop that can spin. It retries
once, only after a lost connection, and every other error throws.

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

* cmux-next: nonisolated deinit for PageCallDeadline.Once (no bead)

The Release compile crashes swift-frontend 6.3.3 in EarlyPerfInliner on
the isolated deinit of this main-actor generic class (exit 65, no error
line), the same crash #20408 fixed elsewhere.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cmux-next): the Chief profile relay's retry is a two-attempt for loop, not while true with a wakeup-allow (cx-ebm.59.5)

* cmux-next sidebar perf round 3: Release-build hitch bench with real wheel input; O(1) hover-card hit

- scripts/cmux-next/sidebar-rows-perf-release.py: runs in a Release tagged app (no debug.* methods) inside
  nx-remote --needs input: CLI workspaces (50 with a terminal, 950 empty), HID-tap wheel events, Animation
  Hitches per gesture, A/B of two apps in one job (side by side, interleaved), traces and the recorder's ktrace
  files removed after each export, PTY count logged.
- SidebarListView+HoverCard: the hover-card hit reads the list's row dictionary instead of
  SidebarModel.isPlaceholder, an O(workspaces) section walk on every scroll frame (7-12% of main-thread samples
  while scrolling 1,000 rows in Release).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Channels: a primary or danger button keeps its fill under the pointer (a soft ring shows the hover); gallery play primary-hover

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Channels gallery: primary-hover asserts the hover ring and the unchanged fill; a budget with a reason for the Create frame

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* cmux-motion: Chrome tab strip tween (200 ms easeOutQuad, restart from value), tabStrip geometry tokens, damping <= 0 takes the fallback (hq-1d, no bead)

Additive: MotionTween {TabOpen, TabClose, TabMove}, TweenCurve::EaseOutQuad,
ease_out_quad, TweenParameters, TweenValue (f64 core: retarget restarts the
duration from the current value with no velocity; the same target does not
restart), Tween (f32, token-driven), MotionPolicy::tween (0 under speed off,
Reduce Motion, or tabOpen/tabClose with ui.animations.newTab off),
TabStripGeometry (design-tokens.json motion.tabStrip: max 200, open/close
18, min 56 active / 32 inactive / 64 pinned, reorder 16 x width / 200,
closing mode +40 below / +60 trailing / 0.3 s). cmux-app-ffi ABI 2 exports
cmux_motion_tween_{base,seconds,retarget,step}, cmux_motion_ease and
cmux_motion_tab_strip_geometry. SpringParameters::finite_or now treats a
damping fraction <= 0 like NaN (hq-4e nit). The parity corpus example
prints tween paths (60 Hz: 12 frames for 200 ms).

* acpmux: record a session's git branch and HEAD at start (hq#1920) (#20840)

* test(acpmux): record a session's git branch and head at start

A session should keep its folder's branch and HEAD commit as they were at
session/new, and a fork its own folder's state at fork time (hq#1920), so
agent spend can be joined to the pull request a session produced. These
fail today: neither the saved meta nor _acpmux/info carries gitBranch or
gitHead.

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

* feat(acpmux): record a session's git branch and head at start

session/new and session/fork now store the cwd's checked-out branch and
full HEAD commit in the session meta (gitBranch, gitHead), so a session
can later be joined to the pull request it produced (hq#1920). The values
are read once, as the session starts, from the repository's own files
(HEAD, loose refs, packed-refs, a linked worktree's gitdir and commondir),
never a git process, and off the async runtime. A detached HEAD records
only the head; a folder outside a repository records neither; a lookup
that fails never fails the session. A fork reads its own folder at fork
time instead of copying the parent's. _acpmux/info carries both fields.
Old session.json files load unchanged.

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

* test(acpmux): git state skips non-files, bad branch names and missing folders; follows symlinks

Review of #20840: a FIFO HEAD hung session/new, a symlinked folder was walked
lexically, a missing fork folder took its parent repository's state, and an
invalid ref name was stored as the branch. The test git also runs without the
machine's global or system config.

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

* fix(acpmux): git state reads only small regular files, the physical folder and valid ref names

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* cx-tgf4h: keep CmuxNextApp line count (App size cap)

* acpmux: a pooled agent host no longer spins on Linux reading its own record (cx-7z8yx)

The record watch used notify's inotify backend, whose watch mask always
includes open, close-write and modify, and ran its callback on every event.
The callback reads the host record; on Linux that read queued an open and a
close in the watched directory, which ran the callback again. Every pooled
host spun at 100% CPU (about 200,000 read calls a second).

Linux now has its own inotify watch, like the macOS kqueue watch: a mask of
entry changes only (create, delete, moved from/to) and the directory's own
delete or move, one thread in poll with no timeout, an eventfd to stop it.
A read of the record never queues an event. The notify fallback (other
platforms) ignores access events. macOS is unchanged.

Behavior test (tests/host_lifetime.rs, Linux): a real daemon pools a host;
idle for 3 s it makes fewer than 100 read calls and 30 CPU ticks (before:
599,042 reads, 298 ticks), a rename rewrite of its record keeps it, and the
record removed ends it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* cmux-tui smoke: sidebar drag checks the drawn sidebar document order

Since sidebar-v2 S6 the TUI draws the daemon's sidebar document, where a new
workspace goes to the top (workspaces.newPlacement default), and a workspace
drop is one sidebar.move that changes the document only. The smoke still
assumed the list-workspaces order: it dragged the drawn top row (the new
workspace) below the original, which the product did, and then read
list-workspaces, which a sidebar.move never changes.

The smoke now reads the order from sidebar.get, checks that the rail draws
it, drags the top row below the second (a real reorder), and drags again when
needed so the original workspace ends below the new one.

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

* PR dock chips; tooltips only where nothing else names the control (#20375)

* test: no tooltip on an element whose text shows

Red on base: 105 elements with visible text carry title/data-tooltip.
Command: bun test test/tooltip-rules.test.ts

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

* fix: PR dock chips; tooltips only where nothing else names the control

The pull request dock's bars become content-sized chips (icon, #N, repo, title cut off at
360 px) with the dismiss button inside and no tooltip. Across the pages, a row, chip, link,
path or thought whose text shows carries no title tooltip any more; tooltips stay on
icon-only controls, shortcuts and text shown nowhere else (why a control is disabled or
failed, who reacted, policy detail), each marked tooltip-allow. Tooltips wait the native
hover delay (about a second) and show at once when moving straight on to the next one.

Green: bun test test/tooltip-rules.test.ts

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

* fix: review of tooltip audit

Keep the PR dock's state colours, drop four more tooltips the lint now finds inside
components (a setting's summary, the branch and folder pickers, a video), let a click or
scroll end the quick next-tooltip window, and move two tests to the no-tooltip rule.

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

* test: Backdrop Art's line has no tooltip

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

* chore(next): PR autofix at 047c247

- vp fmt webviews/src/pages/history/HistoryPage.tsx

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38502564051

* test(mock): the worked chat opens its pull request, so mock mode shows the PR dock

Captures of the agent pane in a real window (--showcase runs the in-page
mock) now show the pull request dock.

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

* test: the PR dock takes its own space; the transcript ends above it

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

* chore(next): PR autofix at 39f3ca9

- vp fmt webviews/test/ime-composition-keys.test.ts
- vp fmt webviews/test/new-tab-omnibar-pointer.test.ts

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38567629461

* chore(next): PR autofix at c7cb261

- regenerated Packages/macOS/CmuxNext/ci-target-graph.json

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38592980576

* fix: a channel image keeps its name tooltip (the name shows only when it fails)

Base added title={part.name} on channel image attachments (AttachmentViews.tsx).
A loaded image shows no name, so the tooltip is its only label; mark it with
tooltip-allow. tooltip-rules.test.ts: red before, green after.

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

* chore(next): PR autofix at f221864

- regenerated Packages/macOS/CmuxNext/ci-target-graph.json

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38633116142

* fix: keep the tool card scope hint and Steer shortcut tooltips

Both explain something shown nowhere else, so they stay under tooltip-allow.

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

* chore(next): PR autofix at 022cc8e

- vp fmt webviews/src/pages/home-channels/cloudHome.ts

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38647981824

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: cmux-autofix[bot] <cmux-autofix[bot]@users.noreply.github.com>

* ci: nightly-next recovery initializes the notices submodule before continuing accepted submissions

The recovery job's checkout had no ghostty-next submodule, so the bundle
notice check that notarize-nightly-dmg.sh runs after Apple accepts failed,
and run 38669958906 skipped five accepted builds as 'Apple did not accept'.
Initialize the submodule like the signing job does, and make the skip notice
say that a post-acceptance check can also be the cause.

* ci: the cmux NIGHTLY-identity job also initializes the notices submodule

Run 38669958906's publish-nightly-identity-legacy job saw Apple accept,
then failed the bundle notice check for lack of ghostty-next and retired
the accepted submission of run 38609024986.

* agent pane: progress and stop for a running Claude subagent or workflow (cx-liswe) (#20718)

* test: a running subagent or workflow shows what it does and what it has spent

Red on base: both rows read only "Running" (bun test
test/subagent-progress.test.tsx).

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

* webviews: a running subagent or workflow shows what it does and what it has spent

The subagent fold keeps acpmux's subagent_progress fields (action, tokens,
tool uses, a workflow's agents done of started), and the transcript's
subagent row adds them to its status line through one shared helper
(subagentProgress.ts) the summary list and the Background tasks pane can use.
Three plural strings, translated for every pane locale.

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

* test: a running subagent row has a stop button

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

* feat: stop a running Claude subagent or workflow from its row

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

* chore: restore the catalog's base order after the merge

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

* test: stopping a subagent leaves its row closed, and a refused stop says so

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

* fix: a subagent's stop button waits for Claude and says when the stop failed

The button is off while Claude has the request and until the subagent
ends; a refused or unanswered stop (10 s) turns it into "Couldn't stop
<name>" and it can be tried again. The pane no longer drops the error.
Also moves the stop wiring above showSubagent's doc comment and corrects
two comments.

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

* style: format the subagent progress test

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

* fix: stop rejects without English the pane would never show

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

* chore: restore the catalog's base order after the merge

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

* chore(next): PR autofix at 2fe6cc4

- vp fmt webviews/src/pages/home-channels/HomeChannelsPage.tsx

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38637059713

* test: the agent pane relays a subagent stop for its chat (cx-liswe)

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

* fix: the agent pane may stop a subagent of its chat (cx-liswe)

Adds _acpmux/subagent_stop to the pane's method allowlist: session-scoped,
params sessionId and subagentSessionId, no gesture (it stops work, like
_acpmux/kill). Without it the host refused every stop click.

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

* fix: mirror the subagent stop into the shared pane policy (cx-liswe)

The Swift allowlist took _acpmux/subagent_stop; policy.json, its parity
source for Rust hosts, gets the same request, session scope and params,
with cases for an in-scope stop, another chat and an unknown param.

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

* gallery: a light window with no theme named renders light (#20794)

* test(gallery): a light window with no theme named should show the light default

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

* fix(gallery): a light window with no theme named shows Apple System Colors Light

The gallery defaulted the theme to the dark side of the app's default pair
whatever the window appearance, so colorScheme=light (or Appearance: Light)
rendered every stage in dark colors under a light media query. The default
now follows the appearance, as the app's does with no theme set.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: cmux-autofix[bot] <cmux-autofix[bot]@users.noreply.github.com>

* Channels view: gallery plays for a message link with no link read (red)

A Cloud conversation has no link resolver (G4), so the Messages view's switch hand-off
#/conversation/<id>/message/<id> showed an error. Two plays: a thread reply and a timeline
message, opened through the store's openLink with an owner that has no resolver. cx-hnmbt

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Channels view: open a known conversation's message link without the owner's link read

When conversation-link-resolve fails (a Cloud conversation, an older daemon) and the page knows
the conversation, it opens it, pages the timeline until the message shows, opens its thread when
it is a reply, and marks it. The Messages view's switch hands off Cloud selections this way.
cx-hnmbt

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Channels view: format parseConversationLink (vp fmt)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Channels view: link fallback review follow-ups

Ids in a known conversation's link may hold '-' (the gallery's sample ids); the scheme check
follows the daemon (cmux[-<tag>], any case); the fallback runs only for a Cloud conversation or
an owner without a link read, so a refused local link (a deleted message) still shows its error.
cx-hnmbt

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* acpmux: host_lifetime idle test cleans up from the start and never signals a reused pid (cx-7z8yx review)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* cmux-tui: move the build identity into a leaf crate so an edit no longer recompiles cmux-remote

cmux-remote's build script hashed the dirty source tree into
CMUX_TUI_BUILD_IDENTITY, so every edit in the workspace reran it and
recompiled cmux-remote and every crate above it (about 8 s of a 12.6 s
one-line edit loop on rbx). The same build script now lives in
cmux-build-identity, which only cmux-tui reads; cmux-remote takes the
identity as an argument (SshBootstrapConfig::defaults,
pinned_linux_payload). The identity value, its inputs and format, and
every skew and SSH re-bootstrap comparison are unchanged.

* test: warm the dev server in the Claude reasoning default browser test (#20884)

#20592 landed after #20873 without warmViteServer, so check:browser-lanes is
red on base again.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* cmux-next: frame-strip report as an NSWindow method, not a namespace enum (no bead) (#20887)

The package conventions lint fails every PR on the new all-static public
enum WindowFrameStrip (cx-me3xd). The report reads its window, so it is an
extension on NSWindow; same body, same report.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Home channels: the run card is an inline disclosure (no frame, steps on an indented rail)

The live and done run cards under a Chief's reply lose their box: "Working
for" / "Worked for", the step count, cost and Open sit inline under the
message, and the opened steps hang on a thin rail under the status mark. The
narrow rules stay: the first line never wraps, and below 480 px of card width
the step count and cost move under it with no leading separator.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Home channels: the run rail and columns follow the author's picture

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Home channels: the run sheet keeps its error line; run card columns in logical properties

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Revert "cmux-motion: Chrome tab strip tween ..." (8a3ee62) until the app-ffi release App 403 is fixed (run 38682698310) (hq-1d, chief go 2026-10-15, no bead)

The release App could not publish cmux-app-ffi-854febb26527 (HTTP 403 on
POST /releases), so the app FFI pin check stays red on the tip. The tween
re-lands with the publish and repin after the App is fixed.

* test(ci): nightly jobs that run the license check must init ghostty-next (#20893)

* test(ci): nightly jobs that run the license check must init ghostty-next

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

* fix(ci): nightly recovery initializes ghostty-next before the license check

Apple accepted, stapled and launch-tested each recovered nightly-next build,
then verify-app-bundle-licenses.sh failed on the missing
ghostty-next/build.zig.zon and the build was skipped as not accepted.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* fix: keep catalog in base order after feat merge

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

---------

Co-authored-by: cmux-autofix[bot] <cmux-autofix[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com>
Co-authored-by: safe-push <safe-push@cmux.local>
teamleaderleo added a commit that referenced this pull request Oct 15, 2026
…t folder, icon-only computer when narrow (#20791)

* test: location strip lines up with the prompt and survives a narrow pane

Boundary tests for the composer's location strip: its folder icon and branch
chevron line up with the prompt's edges, no gap shows where it stands inside
the card's corner, no "·" floats between folder and computer, and at
290-300 px the folder cuts in the middle, the computer shows its icon alone
and the branch keeps its name.

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

* CmuxNext: align the composer location strip with the prompt; middle-cut folder, icon-only computer when narrow

- The strip's folder icon sits over the prompt's first letter and the branch
  chevron over its last column (strip inset 9 px, a foot covers the card's
  corner drop so it still stands on the card).
- The floating "·" between folder and computer is gone; the two are ghost
  buttons 2 px apart, told apart by their icons.
- A folder name cuts in the middle ("atl…web"); below a 320 px strip the
  computer shows its icon alone with its name in the tooltip and label; the
  branch keeps its name.

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

* CmuxNext: location strip cuts a folder only when it runs out of room; tail keeps the last word

The strip's folder and branch still carried the controls' fixed cap (190 px,
145 px under a 480 px pane), so a long folder name cut beside empty strip,
and a long branch cut before half the strip. They now cut only to fit. The
computer never shrinks (a sub-pixel shrink put an ellipsis on "This Mac").
The middle cut keeps the name's last word when it is 3 to 10 characters
("atlas-we…service", not "…ervice"). Adds a long-folder gallery variant and
boundary tests at 300, 400 and 760 px.

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

* cmux-next browser: Fit as 16:9 Desktop per browser tab (browser.tab.emulate {fit}, palette, device menu, CEF + WebKit) (cx-va1wd)

A browser tab can fit a desktop layout instead of a device: the daemon
keeps {fit: {aspect: [16, 9]}} on the tab and shows extra.emulation
{mode: fit, fit {aspect, min_width 1024, max_width 1920}}. The engine lays
the page out at clamp(view height x 16/9, 1024, 1920) CSS px, scales it to
the view width, and makes the layout as tall as the view over that scale,
so the page fills the pane with no bars. It recomputes on resize without a
reload. CEF sends the Emulation calls with the computed size and scale;
WebKit sizes the web view and scales its container. Surfaces: the
browserFitDesktop toggle (palette with a check, context menu, cmux browser
fit-desktop, MCP), a row in Emulate Device..., and cmux tab <tab> emulate
--fit 16:9 (MCP browser.tab.emulate fit). Palette rows of toggles that are
on show a check.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* cmux-next: an agent page in an unfocused pane cannot take the keyboard by itself; responder source counts the current event only during its dispatch (cx-3hw0i.2)

A subagent pane's page focuses its composer as it loads; WebKit then asks the
window to make its web view first responder (WebPageProxy::makeFirstResponder,
seen in debug stacks of nx job 1014-202829-5378d2), and the focus model took
that as the user's choice: the new pane got the keyboard and the daemon focus
although its move ran with keep_focus. ShellWindow now refuses that request
when no input event is in dispatch, the focus model is not applying its own
target or waiting on an expectation, and the page is not in the focused pane.
FocusEvent.Source.current read NSApp.currentEvent, which stays the last key
after its dispatch, so such a change was logged as keyboard input; it now
counts the event only inside sendEvent.

* cmux-next: Source.current returns its switch (cx-3hw0i.2)

* cmux-next: only a focus expectation for the page's own pane lets it take the keyboard (review P2) (cx-3hw0i.2)

* cmux-next browser: Fit as 16:9 Desktop review fixes (cx-va1wd)

browser-fit-v1 gates the action and the menu row, so an older daemon
never sees an unknown fit field. A fit that shows the page as is keeps the
WebKit view at the container's size (it stayed 0x0 or stale). The palette
check comes from a new opt-in, non-refusing Action.paletteState, so other
toggles keep their icons and no refusal shows while the palette builds.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* cmux-tui: the fit case joins the extra transport path cases (one case per operation in cases.rs) (cx-va1wd)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* cmux-next: debug agent-pane click on the view path gives the clicked web view the keyboard inside an input event, as AppKit's click does (cx-3hw0i.2)

* cmux-next browser: the device menu's Fit row comes from a helper (the inline optional closure crashed the type checker) (cx-va1wd)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* AcpmuxChiefHandoff: the log says every built-in Chief preset of a mismatched daemon fails closed, not only codex (review note of 1ae9e97593fd)

* cx-8mla8 red: cmux chief list shows the daemon's words for an internal registry failure

* chief CLI brain HOME red: a brain cmux chief starts gets the account's home, not the caller's (follow-up of c47dd2fd0112)

* cx-8mla8: the CLI shows the daemon's own words for an operation.failed (details.extra.message), never a bare 'internal: internal'

* chief CLI: a brain cmux chief starts gets the account's home (getpwuid_r), like the daemon's brains (c47dd2fd0112)

* CI optchat-chief job: run the dm_daemon behavior test against a real daemon (shared_acpmux already runs in the suite step)

* cx-8mla8 review: accurate handoff log text (codex turn/compactor fail closed, others start without MUX_HOME); sanitize the daemon's words; control refusal keeps the reason; bounded wait for the state dir; skip the HOME test without a user record

* scripts: agent-pane-live.py typed-keys run brings the app front with open before it clicks the composer (cx-3hw0i.2)

* scripts: ext-mv2-live.py judges uBlock Origin by what DevTools sees on an uncached load

uBlock Origin enforced its filters in the earlier runs: it answers
adsbygoogle.js and pagead2 XHR with neutered local resources
(||pagead2.googlesyndication.com^$xhr,redirect=noop.js), so the page saw
loads. The network check now classifies each watched request from the
Network domain (blocked, redirected to the extension, loaded) with the cache
off; dynamic filtering is judged the same way; a tabs.insertCSS probe checks
the cosmetic path.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* cmux-next browser: full uBlock Origin built in (bundled, on by default, browser.builtInAdBlocker toggle)

Lawrence 2026-10-14: ship full uBlock Origin (Manifest V2) pre-installed and
enabled, with a Settings toggle; the Web Store lists only uBlock Origin Lite.

- scripts/cmux-next/fetch-ublock.sh stages the pinned gorhill release ZIP
  (1.75.0, SHA-256 checked) with the Web Store key in its manifest (stable id
  cjpalhdlnbpafiamejdnhcphjbkeiagm), LICENSE.txt, SOURCE.txt (corresponding
  source link) and a source receipt; bundle-ublock.sh copies it to
  Contents/Resources/Extensions/ublock-origin from the Embed CEF phase.
- CEFBuiltInExtensions loads it at Chromium start through --load-extension
  without turning on the extensions developer mode.
- Setting browser.builtInAdBlocker (Browser > Extensions, default on; agents
  may not change it: privacy) is followed by the CEF engine.
- The Extensions menu says "uBlock Origin (full) is built in".
- ext-mv2-live.py builtin checks the bundled copy.

Bead cx-d0d.100.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* scripts: ext-mv2-live.py counts data: no-op redirects as enforcement, turns on uBlock Origin's advanced mode for dynamic rules, reads its content-script state

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* settings: the Browser page shows the Extensions group (built-in uBlock Origin toggle)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* cmux-next exports: settings schema, MDM, defaults snapshot and Settings page strings for browser.builtInAdBlocker

Generated by the export-settings cmux-ci step (abe9d52acb54), defaults_guard.py snapshot and gen-strings.mjs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* scripts: ext-mv2-live.py --label and --config (A/B runs of the built-in uBlock Origin)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* scripts: ext-mv2-live.py checks generic cosmetic filtering on example.com and reads dynamic blocks from the page

uBlock Origin turns generic cosmetic filtering off on the 127.0.0.1 test page
(its content script reports noGenericCosmeticFiltering); dynamic filtering
did block the script (the page saw onerror; DevTools reported 'failed other').

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* cmux-next browser: built-in uBlock Origin stays enabled without developer mode; review fixes

Live proof on a fresh profile: Chromium recorded the --load-extension copy
as unpacked and disabled it (DISABLE_UNSUPPORTED_DEVELOPER_EXTENSION, 1<<24)
because the built-in load does not turn on developer mode. When the
built-in copy loads, disable-features=ExtensionDisableUnsupportedDeveloper
keeps it on (the shim merges it into CEF's list).

Review fixes: the setting's loaded value is set before any Chromium tab can
start Chromium; the Extensions menu line shows only when this Chromium loaded
the built-in copy; a folder path with a comma is not passed; a failed stage
removes a stale copy from the app; the shared cache moves a staged copy in
with one rename (concurrent builds); SOURCE.txt pins the source tarball
digest and names the bundled components' licenses; embed-cef.test.sh copies
bundle-ublock.sh and sets CMUX_NEXT_SKIP_UBLOCK=1; cmux-next.yml path
filters list fetch-ublock.sh.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* scripts: embed-cef strip test skips the built-in uBlock Origin stage

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* cmux-next browser: Fit as 16:9 Desktop state and requests live in CmuxNextDaemon (TabModel.fitsDesktop, setTabFit, setTabEmulation); the App binding does not grow the App (cx-va1wd)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* cmux-next: the self-focus refusal lives in CmuxNextAgentPane (AgentPaneView.refusesSelfFocus); drop the expectation exemption (review: the focus model lands those first); no CmuxNextApp growth (cx-3hw0i.2)

* cmux-next exports: regenerate settings schema, MDM and Settings page strings after rebasing on feat-cmux-next (browser.builtInAdBlocker)

export-settings cmux-ci step a05ed0dab553.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* cmux-tui: move the build identity into a leaf crate so an edit no longer recompiles cmux-remote

cmux-remote's build script hashed the dirty source tree into
CMUX_TUI_BUILD_IDENTITY, so every edit in the workspace reran it and
recompiled cmux-remote and every crate above it (about 8 s of a 12.6 s
one-line edit loop on rbx). The same build script now lives in
cmux-build-identity, which only cmux-tui reads; cmux-remote takes the
identity as an argument (SshBootstrapConfig::defaults,
pinned_linux_payload). The identity value, its inputs and format, and
every skew and SSH re-bootstrap comparison are unchanged.

* cx-8mla8 red: an internal failure gives the CLI a Request ID that the daemon log names with the cause

* cx-8mla8: an internal operation.failed carries a Request ID (details.extra.request_id); the daemon logs it with the operation and the cause; the CLI prints it

* Home channels: the run card is an inline disclosure (no frame, steps on an indented rail)

The live and done run cards under a Chief's reply lose their box: "Working
for" / "Worked for", the step count, cost and Open sit inline under the
message, and the opened steps hang on a thin rail under the status mark. The
narrow rules stay: the first line never wraps, and below 480 px of card width
the step count and cost move under it with no leading separator.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Home channels: the run rail and columns follow the author's picture

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Home channels: the run sheet keeps its error line; run card columns in logical properties

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* cx-8mla8: fmt

* Revert "cmux-motion: Chrome tab strip tween ..." (8a3ee620b09a) until the app-ffi release App 403 is fixed (run 38682698310) (hq-1d, chief go 2026-10-15, no bead)

The release App could not publish cmux-app-ffi-854febb26527 (HTTP 403 on
POST /releases), so the app FFI pin check stays red on the tip. The tween
re-lands with the publish and repin after the App is fixed.

* test(ci): nightly jobs that run the license check must init ghostty-next (#20893)

* test(ci): nightly jobs that run the license check must init ghostty-next

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

* fix(ci): nightly recovery initializes ghostty-next before the license check

Apple accepted, stapled and launch-tested each recovered nightly-next build,
then verify-app-bundle-licenses.sh failed on the missing
ghostty-next/build.zig.zon and the build was skipped as not accepted.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* ci: publish acpmux for Windows (cmux-tui-acpmux-x86_64-pc-windows-gnu.exe) for GPUI (cx-ephhf)

cmux-tui-build-package.yml: input windows_acpmux; job build-windows-acpmux, in
parallel with build-windows on the same runner, Rust pin, MinGW toolchain check
and lld: cargo build -p acpmux --release --locked --target x86_64-pc-windows-gnu,
acpmux.exe --version, artifact cmux-tui-acpmux-x86_64-pc-windows-gnu. Optional
(continue-on-error): a failure never drops the Windows daemon.
cmux-tui-artifacts.yml sets windows_acpmux; the publish job moves the exe into
cmux-tui-windows/<sha>/ (manifest sha256), never cmux-tui/<sha>/ (forbidden in
the pre-upload check). publish-cmux-tui-tree.py: an optional Windows companion,
so a new tree carries cmux-tui/tree/<key>/cmux-tui-acpmux-x86_64-pc-windows-gnu.exe
and .sha256, attested in completion-windows.json; the tree check verifies each
optional Windows companion that completion attests. No acpmux code change.

* acpmux: suggested tasks, Do it here and child results back to the parent (cx-lo9yf) (#20864)

* acpmux: test suggested tasks, Do it here and results back to the parent (cx-lo9yf)

Behavior tests at the hub boundary for the suggested-tasks contract: a
groupId on session_offer, the "here" decision, the child's first reply
delivered once to its parent with offer_result provenance, and the
cmux-sessions MCP server's suggest_tasks tool. They fail before the fix.

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

* acpmux: suggested tasks, Do it here and child results back to the parent (cx-lo9yf)

- cmux-sessions MCP server gains suggest_tasks: 1 to 5 follow-ups, one
  _acpmux/offer_session per task under one fresh groupId.
- _acpmux/offer_session takes an optional one-line groupId (<= 64 bytes),
  recorded on session_offer.
- _acpmux/offer_respond takes outcome "here": the offer closes with no chat.
- When an accepted offer's chat ends its first turn, the hub sends its last
  reply to the parent once (steer, else queue), recorded with provenance
  {kind: offer_result, offerId, childSessionId, title}. Set hub-internally;
  no new client param.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* ci: an Apple-accepted cmux NIGHTLY-identity submission is never retired; bundle checks run from the build's own source (cx-x7hwx)

Run 38669958906: Apple accepted 00b3d8f2, staple and smokes passed, then
the bundle notice check failed (no ghostty-next in the job's checkout) and
the job retired the accepted submission.

hq-a3 decision 2026-10-15:
- An Accepted submission is never retired by a later failure (staple, spctl,
  smoke, license check or publication). The job stores metadata marking its
  bundle accepted (bundle_run points at the original bundle); later runs
  continue it with no time limit, re-staple, re-check and publish, and never
  submit that build again while the bundle is kept. The failure counts
  ("Build N failed:"); two counted failures stop the build, a newer build
  still goes. Apple refused -> retire and count. (A new submission Apple
  accepts within the 20 min wait keeps no bundle: notarize-nightly-dmg.sh
  writes no state for it.)
- The legacy-identity-target script counts failures per build itself
  (--failure-log, --max-failures 2) for continue and new.
- Class fix: the bundle notice check (LICENSE, toolchain pins, ghostty and
  ghostty-next gitlinks and license trees) runs from a checkout of the
  copied build's own commit (CMUXCommit) with ghostty-next initialized there
  (CMUX_VERIFY_LICENSES_TOOL), because the copied cmux NEXT build can be older
  than the run's commit.
- CI guard in tests/test_nightly_universal_build.sh: every job that calls
  notarize-nightly-dmg.sh initializes ghostty-next first; the legacy job runs
  the checks from the build's source.

* cmux-next: launch marks for the connecting screen and a launch journey script (cx-r650e)

connecting_view_shown / connecting_status_shown / connecting_view_left /
workspace_content_shown say how long a launch showed the Connecting to
cmux-tui screen. scripts/cmux-next/launch-connect-journey.py drives a
fleet-built tagged app through first, warm and restart launches with a
large registry and checks the screen never shows on a relaunch.

* cmux-next: restore phase launch marks; launch journey checks the first launch's status line (cx-r650e)

* cmux-next browser: Chromium asks browser.builtInAdBlocker when it starts (no CmuxNextApp growth)

The App hands the CEF engine a closure over the settings snapshot instead of
following the value in a task: the value Chromium reads is the one current at
its start, and CmuxNextApp stays at the base's line count (size cap).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* cmux-next: workspace create launch marks (cx-r650e)

* launch-connect-journey: never-left flag, clear server status error (review)

* cmux-next: keep only the connecting-screen launch marks (CmuxNextApp size cap); restore/spawn marks were diagnosis only (cx-r650e)

* cmux-next: no Connecting to cmux-tui line once connected; the first-workspace wait shows no status (cx-r650e, cx-b4sjc)

* cmux-next: CmuxNextApp cap 113735 = base 113729 + 6 launch-mark/status lines (chief go 2026-10-15, cx-r650e)

* web: fix the feat-cmux-next web test base reds (no bead) (#20865)

* web: fix two feat-cmux-next web test base reds (no bead)

The main sync brought #20524's devbox-agents-1014b ladder, so the
awaiting-rebake excuse for feat's own devbox sources now names that bake
and feat's current digest. The publishable-key test still expected the
x-stack-project-id header that #20236 replaced with x-hexclave-project-id.

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

* web: drop cmux-tui files staged in this worktree by mistake

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

* web: give the 0.65.2 changelog cards their try-it lines

The changelog cards test requires every feature of the latest documented
release to say how to try it; v0.65.2 shipped without them (red on main
and feat-cmux-next).

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* test: cmux NIGHTLY-identity failure count moved into the target script (cx-x7hwx)

* cmux-next: All chats Filter menu: Working, Needs input, Done (cx-htpk0) (#20747)

* test: All chats filters by status (cx-htpk0)

Red: the All chats header has no Filter menu yet. Adds the filter's
strings (All, Working, Needs input, Done, Filter) in all 21 locales,
copied from the existing translations of the same words.

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

* feat: All chats Filter menu: Working, Needs input, Done (cx-htpk0)

The New Tab and History pages' All chats header gets a Filter menu next
to Chats and Projects. All stays the default and shows the list as it
was. Working and Needs input show the live chats in that state (a failed
turn counts as needing input); Done shows live chats at rest, then the
indexed chats. A filter with nothing to show says No matching chats.

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

* chore(next): PR autofix at b02e6111f073

- vp fmt webviews/src/pages/home-channels/cloudHome.ts

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38646041870

* test: a Filter pick closes its menu (cx-htpk0)

Red: the shared Select keeps its menu open after a pick, and in the agent
pane (which does not load ui.css) that open menu draws as bare text over
the rows.

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

* fix: All chats Filter uses the pane's styled menu (cx-htpk0)

The real-app capture showed the Select popup unstyled: the agent pane
does not load ui.css. The Filter is now a Menu styled as the changes
view's scope menu, with a chevron, and a pick closes it.

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

* gallery: All chats Filter open (cx-htpk0)

Covers ChatFilterMenu, which gallery coverage requires of every component.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: cmux-autofix[bot] <cmux-autofix[bot]@users.noreply.github.com>

* Fix: hibernated Chromium tabs leave their renderers running (#20866)

* test: hidden pages hibernate despite scripted forms and keep bounded snapshots

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

* fix: hibernate pages whose scripts fill their forms and bound hibernated snapshots

The hibernation probe called every form field whose value differed from its
default the person's input, so pages that fill fields from script (a
<select> with no default option, a version menu, appearance radios) never
hibernated: 7 of the audit's 20 sites, one live renderer each. The page
recorder now notes fields the person changed (trusted input and change
events) and the probe asks it, falling back to the old comparison when the
recorder did not answer.

A hibernated tab's placeholder kept its own full-size snapshot outside the
32 MB preview cache; it now reads the picture from that cache and holds it
only while a pane shows it. An exempt page is no longer captured before the
probe decides it stays.

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

* test: hibernation keeps used pages and rich edits, and wakes with a fresh bounded snapshot

#20866 review: a page the person used whose script then set a field, a
contenteditable draft, a pane larger than the preview cache, and a page
that hibernates a second time. Adds a capture seam to BrowserHibernation
(no behavior change).

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

* fix: hibernation keeps used and rich-edited pages, and wakes with a fresh bounded snapshot

#20866 review findings:
- A recorder 'no edits' no longer overrides the field comparison once the
  person used the document (trusted key, pointer or paste, or a user
  activation the page had when Chromium's late-attached recorder arrived):
  values a widget or script set for the person keep the page. Untouched
  documents whose script fills fields still hibernate.
- Trusted typing in a contenteditable editor counts as an edit.
- The hibernation snapshot is scaled to at most a quarter of the 32 MB
  preview cache, which refused a large pane's picture outright.
- A page takes a fresh picture each time it hibernates and drops its old
  one when it wakes.

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

* test: drive the used-page case with a key press

The synthesized click never reached the WebKit page on the fleet (the test
stopped before its expectation, red and green alike); a key press to the
first-responder web view is the path the typing tests already use.

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

* test: browsing a page or sending a composer leaves it free to hibernate; snapshots keep color and shape

#20866 second review: a key press on a page whose script filled its fields,
a rich editor cleared after typing, and a large P3 pane's snapshot color
space and aspect.

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

* fix: browsing a page or sending a composer no longer keeps a tab awake

#20866 second review:
- At the person's first use the recorder notes every field's value; after
  that a field counts only when it differs from its default and from that
  value, so fields a script filled at load no longer pin the tab, while a
  value a widget sets later still does.
- Rich editors are tracked weakly with the text they held when the person
  started typing; a cleared or removed composer no longer counts.
- Trusted click and drop count as use.
- The hibernation snapshot scales through TabPreviewFitting (off the main
  actor, keeps the color space and aspect) instead of a new helper.

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

* refactor: the hibernation probe script lives with the page recorder

Keeps the app target from growing (its size cap).

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

* fix: the moved field probe gets its own name

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* ci: cmux NIGHTLY-identity skips pending metadata whose bundle is confirmed gone; guard ignores comments (cx-x7hwx)

* cmux NEXT: Cloud machines show Reconnecting while the link redials (cx-z5oif) (#20896)

* test(cx-z5oif): a Cloud link that redials stops reading as connected

The test runs a CloudMachineLink against a stand-in /api/vm, hub and
cmux-tui remote connect that reports reconnecting, then connected. The
seams (CloudLinkRedial, SidebarMachine.redialing) are inert here, so the
machine keeps reading as connected: red.

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

* fix(cx-z5oif): show Reconnecting while a Cloud link redials

After sleep, a network drop or a Wi-Fi change the cmux-tui remote connect
client redials on its own and keeps its local socket up, so a Cloud
machine kept reading as connected while its terminals froze. The link
now follows the client's later connection-snapshot lines into
CloudLinkRedial, and the sidebar header reads Reconnecting until the
client reports connected again, as SSH machines do (cx-ojk7n). The
App target gains no lines.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* New Tab composer: a long draft wraps and grows instead of showing as one cut line (#20870)

* test: a typed draft wraps whole and the composer caps then scrolls

The New Tab field (where every chat starts) holds a draft on one
clipped line. This boundary test types a long one-line draft at 300
and 760 px, focused and blurred, and grows a draft word by word past
the field's cap, in Chromium and WebKit, for the New Tab field and
the chat composer (idle, running, queued). The New Tab cases fail.

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

* fix: the New Tab composer wraps a long draft instead of cutting it

The New Tab page, where every chat starts, held the draft in a one-line
<input> in a fixed 52px row, so a long prompt showed as one clipped line
(scrolled to the caret while typing, cut at the field's edge once
blurred). The field is now a textarea that wraps and grows with the
draft up to eight lines (the chat composer's cap) or 40% of the pane,
then scrolls inside, keeping a caret at the draft's end in view. Return
still sends; the prompt glyph sits on the first line; the placeholder
stays one line. Tests that set the field's value use its own prototype.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* acpmux: Claude chats get a short generated title (cx-ncc.91) (#20538)

* test(acpmux): a chat its harness never titles gets a generated title (cx-ncc.91)

Red: acpmux keeps the first prompt as a Claude chat's title, and has no
title command. The tests use a fake title command; none reaches a model.

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

* test(acpmux): format the generated-title test (cx-ncc.91)

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

* acpmux: generated titles for chats their harness never titles (cx-ncc.91)

Claude Code's -p stream-json has no title event, so a Claude chat kept its
first prompt's line as its title, and so did the workspace named after it.
After a chat's first completed turn, acpmux asks a title command once for
3-6 words: titles.command (or ACPMUX_TITLE_COMMAND), else the chat's own
Claude Code profile with --model haiku, no tools, MCP or session file. A
harness title always wins. The result is a titled record that watchers hear
of as session_changed. titles.generate: false turns it off.

The session-changed kind table moves to server/change_kind.rs and the test
to its own binary: server/mod.rs and hub_integration.rs are over budget.

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

* chore(next): PR autofix at c7394b95436c

- regenerated schemas/defaults/cmux-next.json

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38542576704

* acpmux: title only a chat's own first prompt, without the person's hooks (cx-ncc.91)

Review: a chat titled before generated titles, or adopted, was retitled on
its next turn from its latest prompt; a queued second turn could swap the
prompt and empty the reply first; the title call ran the person's Claude
hooks; a wrapper's children outlived the timeout.

Now only a title set from this chat's first prompt (title_source prompt) is
replaced, from that prompt as dispatched, with the reply only while no later
turn has started. The default command passes disableAllHooks, and a timeout
ends the command's whole process group.

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

* test(acpmux): the title test watches session changes, as the app does (cx-ncc.91)

_acpmux/session_changed reaches only connections that called _acpmux/watch.

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

* acpmux: an adopted chat keeps its title; end a timed-out title group first (cx-ncc.91)

Re-review: an adopted chat whose replay set no title (history from the
harness, no replayable turns) took its first prompt here as a prompt title,
so a later prompt was sent to the title model. check_resumed, which every
adoption passes, now marks it adopted. On a timeout the title command's
group is killed while its unreaped leader still holds the group id.

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

* acpmux tests: every acpmux process a test starts gets a hermetic environment (cx-s8wux)

One helper, tests/support/daemon_env.rs, starts the acpmux binary for every
integration test: env_clear, a bare PATH (python3 plus named host tools,
then the system directories), a scratch HOME, and ACPMUX_AGENT_LAUNCH=deny
unless the test drives a stand-in agent (Agents::StandIn keeps the bare
PATH). The 26 test files that started the binary with the host environment
use it; tests/hermetic_daemon_guard.rs refuses a test file that names
CARGO_BIN_EXE_acpmux anywhere else.

Root cause of cx-w014j: a fleet host's PATH holds real codex/claude, which a
test daemon discovered and ran (probes, sign-in checks, auto-updates).

* acpmux tests: the hermetic HOME is <scratch>/home, never a chat's folder

* acpmux tests: review fixes for the hermetic environment (cx-s8wux)

python3 links the interpreter itself (sys.executable), not a version
manager's shim; stale tool links from an earlier run are replaced; the guard
also flags daemon_env::bin() outside a listed use; the live codex test probes
only codex; codex_bridge_config's discovery test runs with launches denied.

* acpmux tests: router_install_secret starts the router through daemon_env (cx-s8wux)

* cmux-next: fix two Chief profile relay base reds (concurrency check, Release compile crash) (no bead) (#20876)

* cmux-next: mark the Chief profile relay retry loop as bounded (no bead)

check-concurrency flags its while true as a loop that can spin. It retries
once, only after a lost connection, and every other error throws.

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

* cmux-next: nonisolated deinit for PageCallDeadline.Once (no bead)

The Release compile crashes swift-frontend 6.3.3 in EarlyPerfInliner on
the isolated deinit of this main-actor generic class (exit 65, no error
line), the same crash #20408 fixed elsewhere.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cmux-next): the Chief profile relay's retry is a two-attempt for loop, not while true with a wakeup-allow (cx-ebm.59.5)

* cmux-next sidebar perf round 3: Release-build hitch bench with real wheel input; O(1) hover-card hit

- scripts/cmux-next/sidebar-rows-perf-release.py: runs in a Release tagged app (no debug.* methods) inside
  nx-remote --needs input: CLI workspaces (50 with a terminal, 950 empty), HID-tap wheel events, Animation
  Hitches per gesture, A/B of two apps in one job (side by side, interleaved), traces and the recorder's ktrace
  files removed after each export, PTY count logged.
- SidebarListView+HoverCard: the hover-card hit reads the list's row dictionary instead of
  SidebarModel.isPlaceholder, an O(workspaces) section walk on every scroll frame (7-12% of main-thread samples
  while scrolling 1,000 rows in Release).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Channels: a primary or danger button keeps its fill under the pointer (a soft ring shows the hover); gallery play primary-hover

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Channels gallery: primary-hover asserts the hover ring and the unchanged fill; a budget with a reason for the Create frame

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* cmux-motion: Chrome tab strip tween (200 ms easeOutQuad, restart from value), tabStrip geometry tokens, damping <= 0 takes the fallback (hq-1d, no bead)

Additive: MotionTween {TabOpen, TabClose, TabMove}, TweenCurve::EaseOutQuad,
ease_out_quad, TweenParameters, TweenValue (f64 core: retarget restarts the
duration from the current value with no velocity; the same target does not
restart), Tween (f32, token-driven), MotionPolicy::tween (0 under speed off,
Reduce Motion, or tabOpen/tabClose with ui.animations.newTab off),
TabStripGeometry (design-tokens.json motion.tabStrip: max 200, open/close
18, min 56 active / 32 inactive / 64 pinned, reorder 16 x width / 200,
closing mode +40 below / +60 trailing / 0.3 s). cmux-app-ffi ABI 2 exports
cmux_motion_tween_{base,seconds,retarget,step}, cmux_motion_ease and
cmux_motion_tab_strip_geometry. SpringParameters::finite_or now treats a
damping fraction <= 0 like NaN (hq-4e nit). The parity corpus example
prints tween paths (60 Hz: 12 frames for 200 ms).

* acpmux: record a session's git branch and HEAD at start (hq#1920) (#20840)

* test(acpmux): record a session's git branch and head at start

A session should keep its folder's branch and HEAD commit as they were at
session/new, and a fork its own folder's state at fork time (hq#1920), so
agent spend can be joined to the pull request a session produced. These
fail today: neither the saved meta nor _acpmux/info carries gitBranch or
gitHead.

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

* feat(acpmux): record a session's git branch and head at start

session/new and session/fork now store the cwd's checked-out branch and
full HEAD commit in the session meta (gitBranch, gitHead), so a session
can later be joined to the pull request it produced (hq#1920). The values
are read once, as the session starts, from the repository's own files
(HEAD, loose refs, packed-refs, a linked worktree's gitdir and commondir),
never a git process, and off the async runtime. A detached HEAD records
only the head; a folder outside a repository records neither; a lookup
that fails never fails the session. A fork reads its own folder at fork
time instead of copying the parent's. _acpmux/info carries both fields.
Old session.json files load unchanged.

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

* test(acpmux): git state skips non-files, bad branch names and missing folders; follows symlinks

Review of #20840: a FIFO HEAD hung session/new, a symlinked folder was walked
lexically, a missing fork folder took its parent repository's state, and an
invalid ref name was stored as the branch. The test git also runs without the
machine's global or system config.

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

* fix(acpmux): git state reads only small regular files, the physical folder and valid ref names

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* PR dock chips; tooltips only where nothing else names the control (#20375)

* test: no tooltip on an element whose text shows

Red on base: 105 elements with visible text carry title/data-tooltip.
Command: bun test test/tooltip-rules.test.ts

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

* fix: PR dock chips; tooltips only where nothing else names the control

The pull request dock's bars become content-sized chips (icon, #N, repo, title cut off at
360 px) with the dismiss button inside and no tooltip. Across the pages, a row, chip, link,
path or thought whose text shows carries no title tooltip any more; tooltips stay on
icon-only controls, shortcuts and text shown nowhere else (why a control is disabled or
failed, who reacted, policy detail), each marked tooltip-allow. Tooltips wait the native
hover delay (about a second) and show at once when moving straight on to the next one.

Green: bun test test/tooltip-rules.test.ts

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

* fix: review of tooltip audit

Keep the PR dock's state colours, drop four more tooltips the lint now finds inside
components (a setting's summary, the branch and folder pickers, a video), let a click or
scroll end the quick next-tooltip window, and move two tests to the no-tooltip rule.

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

* test: Backdrop Art's line has no tooltip

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

* chore(next): PR autofix at 047c2470d420

- vp fmt webviews/src/pages/history/HistoryPage.tsx

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38502564051

* test(mock): the worked chat opens its pull request, so mock mode shows the PR dock

Captures of the agent pane in a real window (--showcase runs the in-page
mock) now show the pull request dock.

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

* test: the PR dock takes its own space; the transcript ends above it

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

* chore(next): PR autofix at 39f3ca973ed3

- vp fmt webviews/test/ime-composition-keys.test.ts
- vp fmt webviews/test/new-tab-omnibar-pointer.test.ts

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38567629461

* chore(next): PR autofix at c7cb26191eb1

- regenerated Packages/macOS/CmuxNext/ci-target-graph.json

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38592980576

* fix: a channel image keeps its name tooltip (the name shows only when it fails)

Base added title={part.name} on channel image attachments (AttachmentViews.tsx).
A loaded image shows no name, so the tooltip is its only label; mark it with
tooltip-allow. tooltip-rules.test.ts: red before, green after.

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

* chore(next): PR autofix at f221864ee886

- regenerated Packages/macOS/CmuxNext/ci-target-graph.json

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38633116142

* fix: keep the tool card scope hint and Steer shortcut tooltips

Both explain something shown nowhere else, so they stay under tooltip-allow.

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

* chore(next): PR autofix at 022cc8e1ae35

- vp fmt webviews/src/pages/home-channels/cloudHome.ts

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38647981824

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: cmux-autofix[bot] <cmux-autofix[bot]@users.noreply.github.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: cmux-autofix[bot] <cmux-autofix[bot]@users.noreply.github.com>
Co-authored-by: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com>

* chore(next): base autofix at 15c5ffe4ec30

- regenerated plans/cmux-next/action-surfaces.json
- regenerated plans/cmux-next/actions.md

scripts/cmux-next/base-autofix.sh: https://github.com/manaflow-ai/cmux/actions/runs/38688832594

* Composer: show the location strip only before a chat's first send (#20869)

* test: composer location strip shows only before a chat's first send

Boundary tests for the composer's folder · computer · branch strip: a new
chat shows it, the first send hides it, a reopened chat with history never
draws it (not even while loading), a failed first send offers it again, and
a prompt queued before the first turn hides it.

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

* fix: hide the composer location strip after a chat's first send

Not every harness can change folder or machine mid-chat, and cmux does
not, so the composer's folder · computer · branch strip now shows only on
a new chat before its first send (locationRow.ts). The first send is read
from the session: a turn acpmux recorded, a user prompt that did not fail
(the in-flight optimistic row included), a running turn, or a queued
prompt. A selected session whose summary has not arrived shows none, so a
reopened chat never flashes it; a failed first send offers it again.

The strip's CSS keys on :has(> .acpmux-composer-strip), so without it the
queue and the card close up with no gap. Galleries and browser tests that
measured the strip on started chats now measure a new chat, or assert the
strip is gone and the queue meets the card.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* refactor: move SidebarSameTurn into CmuxNextSidebar (#20883)

#20709 put SidebarSameTurn in CmuxNextApp and left the app 37 lines over
its size cap (113729 vs 113692 on 9d70c8abb75a). It only uses SidebarView
and ObservationStream, so it belongs with the sidebar; the app keeps the
bridge that owns it. CmuxNextApp is now 113687 lines.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* CmuxNext: mark the computer picker's tooltip as allowed (it names the icon-only computer)

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

* ci: the tour comment says when capture died before writing a receipt (#20899)

* test: a UI tour whose capture wrote no receipt says so in the comment (#20763)

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

* ci: the tour comment says when capture died before writing a receipt (#20763)

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* test(cmux-next): a closed sidebar row may park hidden in the list (base red) (#20793)

* test: a closed sidebar row may park hidden in the list

Lawrence's 5763d7372701 parks a recycled row view hidden in place instead
of removing it, so SidebarInsertWhileDisplayAsleepTests' two
superview == nil checks failed on base on display hosts. A completed
close is now: out of the list or hidden.

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

* test: count only a closing row still shown as leaving

A row recycled at once now stays parked hidden in the list, so without this
the setup would pass for a close that skipped its motion.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* cx-o9yox: every rounded hairline draws one device pixel (follow-up to #20481) (#20713)

* test: scanner for raw sub-pixel rims on rounded elements (wip, cx-o9yox)

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

* test: rounded rims draw one device pixel, not a raw 0.5px (cx-o9yox)

A ratchet over webviews/src: a border, outline or ring shadow thinner than
one CSS pixel on a rounded element (or in a shadow token) must use
var(--cmux-hairline). Red on base: 29 rims outside the pending list (the
popup shadow token, cards, pills, bubbles, popovers, the find bar and the
prototype), plus styles.css over its pending count. The pending list holds
the composer and New Tab files that another branch owns.

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

* fix: rounded rims draw one device pixel (cx-o9yox)

29 rounded rims drew a raw 0.5px inset shadow or border. macOS WebKit snaps
the hole of a sub-pixel rim to device pixels but not its rounded clip, so
on a 1x display or at a fractional pane zoom they lost edges and corner
arcs (#20481). Each now uses var(--cmux-hairline, .5px), with its color
and alpha unchanged: the popup shadow token (every menu and popover),
dictation notice, New Tab box and kind chip, file search, diff scope and
tools pills, edited-files and turn-preview cards, question card, secret
field, chat gallery tiles, user bubble, render card, find bar, Jump to
latest, minimap popover, branch pill, changes banner, harness action,
replay controls, update cards, the effort hint and the prototype. The
Jump to latest and minimap popover rims, the two with a fixed color, now
clear with appearance.borders none as the popups' does.
popup-one-hairline.test.ts accepts the token as the popup's one hairline.

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

* test: hairline scanner checks each shadow layer; review fixes

A shadow with one hairline-token layer and a raw 0.5px ring layer is now
flagged. popup-one-hairline matches --cmux-hairline exactly, not
--cmux-hairline-ring. The update card comment names the token.

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

* fix: composer, attachment and New Tab card rims draw one device pixel

#20678 landed, so the composer files are free: the composer box base rim,
the permission card, the attachment chip, thumbnail outline (and its
offset), PDF sheet, viewer and open button, the remove button ring and
the New Tab card move onto var(--cmux-hairline), colors unchanged. The
hairline pending list is empty.

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

* chore(next): PR autofix at fc69dee66225

- vp fmt webviews/src/pages/home-channels/HomeChannelsPage.tsx

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38635963929

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: cmux-autofix[bot] <cmux-autofix[bot]@users.noreply.github.com>

* Code pages draw in the terminal's font; agent pane defines --font-mono (#20880)

* cx-tgf4h red: cmux chief -p under ACPMUX_AGENT_LAUNCH=deny fails at once with a typed refusal

* cx-tgf4h: the Chief says at once that agents are disabled in this build session

With ACPMUX_AGENT_LAUNCH=deny (the nx-remote gui/input default since HQ
#2130) or CMUX_AGENTS_DISABLED=1, no Chief turn can start an agent.
`cmux chief -p` started a daemon and a brain and waited 240 s for a reply
that could not come.

- cmux-tui-core agent_launch: one test of the kill switch (acpmux
  launch_policy.rs), the reason agent_launch_disabled.
- `cmux chief` (pipe and chat) refuses before it starts or sends anything:
  operation.failed, details.reason agent_launch_disabled, exit 1.
- The daemon's supervisor starts no Chief brain; chief.engine.* and
  chief.stop answer the typed refusal at once.
- optchat-chief host does not start (logs why, exit 0).
- Home: the local Chief conversation's notice says agents are disabled;
  the engine panel maps the typed refusal (localized, all Home languages).
- Every Chief proof script refuses at once under deny and names
  `nx-remote --allow-agents`.

* test: code pages follow the terminal font; font variables resolve

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

* cx-tgf4h review: forward chief control to a running brain first; daemon-side deny test; engine/stop CLI text; guard before side effects; fmt

* acpmux tests: every acpmux process a test starts gets a hermetic environment (cx-s8wux)

One helper, tests/support/daemon_env.rs, starts the acpmux binary for every
integration test: env_clear, a bare PATH (python3 plus named host tools,
then the system directories), a scratch HOME, and ACPMUX_AGENT_LAUNCH=deny
unless the test drives a stand-in agent (Agents::StandIn keeps the bare
PATH). The 26 test files that started the binary with the host environment
use it; tests/hermetic_daemon_guard.rs refuses a test file that names
CARGO_BIN_EXE_acpmux anywhere else.

Root cause of cx-w014j: a fleet host's PATH holds real codex/claude, which a
test daemon discovered and ran (probes, sign-in checks, auto-updates).

* cx-tgf4h: fmt and clippy

* acpmux tests: the hermetic HOME is <scratch>/home, never a chat's folder

* acpmux tests: review fixes for the hermetic environment (cx-s8wux)

python3 links the interpreter itself (sys.executable), not a version
manager's shim; stale tool links from an earlier run are replaced; the guard
also flags daemon_env::bin() outside a listed use; the live codex test probes
only codex; codex_bridge_config's discovery test runs with launches denied.

* acpmux tests: router_install_secret starts the router through daemon_env (cx-s8wux)

* fix: code pages draw in the terminal's font; agent pane defines --font-mono

The diff, markdown and code editor pages were written to follow the terminal font
(appearance.ts, editorFont), but no cmux NEXT host ever sent it, so code always drew
in Menlo. Every page theme now carries the terminal's family (cmux.json
terminal.fontFamily, else the Ghostty config's font-family), resent when it changes.

The agent pane's mono text read --font-mono, which only Tailwind's theme layer
emitted because a source file happened to name it; conversation.css defines it.

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

* cmux-next: fix two Chief profile relay base reds (concurrency check, Release compile crash) (no bead) (#20876)

* cmux-next: mark the Chief profile relay retry loop as bounded (no bead)

check-concurrency flags its while true as a loop that can spin. It retries
once, only after a lost connection, and every other error throws.

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

* cmux-next: nonisolated deinit for PageCallDeadline.Once (no bead)

The Release compile crashes swift-frontend 6.3.3 in EarlyPerfInliner on
the isolated deinit of this main-actor generic class (exit 65, no error
line), the same crash #20408 fixed elsewhere.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cmux-next): the Chief profile relay's retry is a two-attempt for loop, not while true with a wakeup-allow (cx-ebm.59.5)

* cmux-next sidebar perf round 3: Release-build hitch bench with real wheel input; O(1) hover-card hit

- scripts/cmux-next/sidebar-rows-perf-release.py: runs in a Release tagged app (no debug.* methods) inside
  nx-remote --needs input: CLI workspaces (50 with a terminal, 950 empty), HID-tap wheel events, Animation
  Hitches per gesture, A/B of two apps in one job (side by side, interleaved), traces and the recorder's ktrace
  files removed after each export, PTY count logged.
- SidebarListView+HoverCard: the hover-card hit reads the list's row dictionary instead of
  SidebarModel.isPlaceholder, an O(workspaces) section walk on every scroll frame (7-12% of main-thread samples
  while scrolling 1,000 rows in Release).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Channels: a primary or danger button keeps its fill under the pointer (a soft ring shows the hover); gallery play primary-hover

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Channels gallery: primary-hover asserts the hover ring and the unchanged fill; a budget with a reason for the Create frame

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* cmux-motion: Chrome tab strip tween (200 ms easeOutQuad, restart from value), tabStrip geometry tokens, damping <= 0 takes the fallback (hq-1d, no bead)

Additive: MotionTween {TabOpen, TabClose, TabMove}, TweenCurve::EaseOutQuad,
ease_out_quad, TweenParameters, TweenValue (f64 core: retarget restarts the
duration from the current value with no velocity; the same target does not
restart), Tween (f32, token-driven), MotionPolicy::tween (0 under speed off,
Reduce Motion, or tabOpen/tabClose with ui.animations.newTab off),
TabStripGeometry (design-tokens.json motion.tabStrip: max 200, open/close
18, min 56 active / 32 inactive / 64 pinned, reorder 16 x width / 200,
closing mode +40 below / +60 trailing / 0.3 s). cmux-app-ffi ABI 2 exports
cmux_motion_tween_{base,seconds,retarget,step}, cmux_motion_ease and
cmux_motion_tab_strip_geometry. SpringParameters::finite_or now treats a
damping fraction <= 0 like NaN (hq-4e nit). The parity corpus example
prints tween paths (60 Hz: 12 frames for 200 ms).

* fix: read the font from the files the Ghostty config loaded, includes too

Review: a font-family in a config-file include won in the terminal but not on pages.
Read the runtime's loaded files (with includes, in load order) instead of guessing
paths, repaint after the last override is set, and fix a stale doc comment.

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

* acpmux: record a session's git branch and HEAD at start (hq#1920) (#20840)

* test(acpmux): record a session's git branch and head at start

A session should keep its folder's branch and HEAD commit as they were at
session/new, and a fork its own folder's state at fork time (hq#1920), so
agent spend can be joined to the pull request a session produced. These
fail today: neither the saved meta nor _acpmux/info carries gitBranch or
gitHead.

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

* feat(acpmux): record a session's git branch and head at start

session/new and session/fork now store the cwd's checked-out branch and
full HEAD commit in the session meta (gitBranch, gitHead), so a session
can later be joined to the pull request it produced (hq#1920). The values
are read once, as the session starts, from the repository's own files
(HEAD, loose refs, packed-refs, a linked worktree's gitdir and commondir),
never a git process, and off the async runtime. A detached HEAD records
only the head; a folder outside a repository records neither; a lookup
that fails never fails the session. A fork reads its own folder at fork
time instead of copying the parent's. _acpmux/info carries both fields.
Old session.json files load unchanged.

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

* test(acpmux): git state skips non-files, bad branch names and missing folders; follows symlinks

Review of #20840: a FIFO HEAD hung session/new, a symlinked folder was walked
lexically, a missing fork folder took its parent repository's state, and an
invalid ref name was stored as the branch. The test git also runs without the
machine's global or system config.

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

* fix(acpmux): git state reads only small regular files, the physical folder and valid ref names

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* cx-tgf4h: keep CmuxNextApp line count (App size cap)

* acpmux: a pooled agent host no longer spins on Linux reading its own record (cx-7z8yx)

The record watch used notify's inotify backend, whose watch mask always
includes open, close-write and modify, and ran its callback on every event.
The callback reads the host record; on Linux that read queued an open and a
close in the watched directory, which ran the callback again. Every pooled
host spun at 100% CPU (about 200,000 read calls a second).

Linux now has its own inotify watch, like the macOS kqueue watch: a mask of
entry changes only (create, delete, moved from/to) and the directory's own
delete or move, one thread in poll with no timeout, an eventfd to stop it.
A read of the record never queues an event. The notify fallback (other
platforms) ignores access events. macOS is unchanged.

Behavior test (tests/host_lifetime.rs, Linux): a real daemon pools a host;
idle for 3 s it makes fewer than 100 read calls and 30 CPU ticks (before:
599,042 reads, 298 ticks), a rename rewrite of its record keeps it, and the
record removed ends it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* cmux-tui smoke: sidebar drag checks the drawn sidebar document order

Since sidebar-v2 S6 the TUI draws the daemon's sidebar document, where a new
workspace goes to the top (workspaces.newPlacement default), and a workspace
drop is one sidebar.move that changes the document only. The smoke still
assumed the list-workspaces order: it dragged the drawn top row (the new
workspace) below the original, which the product did, and then read
list-workspaces, which a sidebar.move never changes.

The smoke now reads the order from sidebar.get, checks that the rail draws
it, drags the top row below the second (a real reorder), and drags again when
needed so the original workspace ends below the new one.

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

* PR dock chips; tooltips only where nothing else names the control (#20375)

* test: no tooltip on an element whose text shows

Red on base: 105 elements with visible text carry title/data-tooltip.
Command: bun test test/tooltip-rules.test.ts

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

* fix: PR dock chips; tooltips only where nothing else names the control

The pull request dock's bars become content-sized chips (icon, #N, repo, title cut off at
360 px) with the dismiss button inside and no tooltip. Across the pages, a row, chip, link,
path or thought whose text shows carries no title tooltip any more; tooltips stay on
icon-only controls, shortcuts and text shown nowhere else (why a control is disabled or
failed, who reacted, policy detail), each marked tooltip-allow. Tooltips wait the native
hover delay (about a second) and show at once when moving straight on to the next one.

Green: bun test test/tooltip-rules.test.ts

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

* fix: review of tooltip audit

Keep the PR dock's state colours, drop four more tooltips the lint now finds inside
components (a setting's summary, the branch and folder pickers, a video), let a click or
scroll end the quick next-tooltip window, and move two tests to the no-tooltip rule.

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

* test: Backdrop Art's line has no tooltip

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

* chore(next): PR autofix at 047c2470d420

- vp fmt webviews/src/pages/history/HistoryPage.tsx

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38502564051

* test(mock): the worked chat opens its pull request, so mock mode shows the PR dock

Captures of the agent pane in a real window (--showcase runs the in-page
mock) now show the pull request dock.

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

* test: the PR dock takes its own space; the transcript ends above it

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

* chore(next): PR autofix at 39f3ca973ed3

- vp fmt webviews/test/ime-composition-keys.test.ts
- vp fmt webviews/test/new-tab-omnibar-pointer.test.ts

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38567629461

* chore(next): PR autofix at c7cb26191eb1

- regenerated Packages/macOS/CmuxNext/ci-target-graph.json

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38592980576

* fix: a channel image keeps its name tooltip (the name shows only when it fails)

Base added title={part.name} on channel image attachments (AttachmentViews.tsx).
A loaded image shows no name, so the tooltip is its only label; mark it with
tooltip-allow. tooltip-rules.test.ts: red before, green after.

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

* chore(next): PR autofix at f221864ee886

- regenerated Packages/macOS/CmuxNext/ci-target-graph.json

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38633116142

* fix: keep the tool card scope hint and Steer shortcut tooltips

Both explain something shown nowhere else, so they stay under tooltip-allow.

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

* chore(next): PR autofix at 022cc8e1ae35

- vp fmt webviews/src/pages/home-channels/cloudHome.ts

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38647981824

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: cmux-autofix[bot] <cmux-autofix[bot]@users.noreply.github.com>

* ci: nightly-next recovery initializes the notices submodule before continuing accepted submissions

The recovery job's checkout had no ghostty-next submodule, so the bundle
notice check that notarize-nightly-dmg.sh runs after Apple accepts failed,
and run 38669958906 skipped five accepted builds as 'Apple did not accept'.
Initialize the submodule like the signing job does, and make the skip notice
say that a post-acceptance check can also be the cause.

* ci: the cmux NIGHTLY-identity job also initializes the notices submodule

Run 38669958906's publish-nightly-identity-legacy job saw Apple accept,
then failed the bundle notice check for lack of ghostty-next and retired
the accepted submission of run 38609024986.

* agent pane: progress and stop for a running Claude subagent or workflow (cx-liswe) (#20718)

* test: a running subagent or workflow shows what it does and what it has spent

Red on base: both rows read only "Running" (bun test
test/subagent-progress.test.tsx).

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

* webviews: a running subagent or workflow shows what it does and what it has spent

The subagent fold keeps acpmux's subagent_progress fields (action, tokens,
tool uses, a workflow's agents done of started), and the transcript's
subagent row adds them to its status line through one shared helper
(subagentProgress.ts) the summary list and the Background tasks pane can use.
Three plural strings, translated for every pane locale.

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

* test: a running subagent row has a stop button

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

* feat: stop a running Claude subagent or workflow from its row

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

* chore: restore the catalog's base order after the merge

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

* test: stopping a subagent leaves its row closed, and a refused stop says so

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

* fix: a subagent's stop button waits for Claude and says when the stop failed

The button is off while Claude has the request and until the subagent
ends; a refused or unanswered stop (10 s) turns it into "Couldn't stop
<name>" and it can be tried again. The pane no longer drops the error.
Also moves the stop wiring above showSubagent's doc comment and corrects
two comments.

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

* style: format the subagent progress test

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

* fix: stop rejects without English the pane would never show

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

* chore: restore the catalog's base order after the merge

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

* chore(next): PR autofix at 2fe6cc48ea66

- vp fmt webviews/src/pages/home-channels/HomeChannelsPage.tsx

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38637059713

* test: the agent pane relays a subagent stop for its chat (cx-liswe)

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

* fix: the agent pane may stop a subagent of its chat (cx-liswe)

Adds _acpmux/subagent_stop to the pane's method allowlist: session-scoped,
params sessionId and subagentSessionId, no gesture (it stops work, like
_acpmux/kill). Without it the host refused every stop click.

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

* fix: mirror the subagent stop into the shared pane polic…
teamleaderleo added a commit that referenced this pull request Oct 21, 2026
…View's deinit (#23233)

#23050 added a generic NSHostingView subclass. Its isolated deinit crashes
swift-frontend in the Release compile (EarlyPerfInliner,
isCallerAndCalleeLayoutConstraintsCompatible, exit 65), the same crash #20408
fixed for other main-actor generic classes.


Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15bc4-cecd-72b1-b539-62a19ed43671&host=cosmic-gray

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
teamleaderleo pushed a commit that referenced this pull request Oct 21, 2026
…o nonisolated deinit

In a uiSwiftSettings target a generic class's implicit deinit is isolated, and
the Release compile's optimizer crashes on it (EarlyPerfInliner, swift-frontend
6.3, macOS 14 target): #23050's FirstMouseHostingView broke every Release
compile until #23233, the same crash #20408 fixed before. The crash-class rule
now requires `nonisolated deinit` there (a reviewed `// concurrency-allow:`
escapes it). Non-UI targets stay out of scope.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15ba2-4ce2-7e02-8fa2-5cd34f7648b1&host=cosmic-gray
teamleaderleo added a commit that referenced this pull request Oct 21, 2026
…o nonisolated deinit (the #23050 Release crash) (#23361)

* ci(next): a crash-class test for a generic main-actor class with no nonisolated deinit (red)

#23050's FirstMouseHostingView, a generic NSHostingView subclass in a
uiSwiftSettings target, had an implicit isolated deinit that crashed the
Release compile's optimizer (fixed in #23233). This fixture test expects
check-concurrency.sh to refuse that shape; the rule comes next.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15ba2-4ce2-7e02-8fa2-5cd34f7648b1&host=cosmic-gray

* ci(next): check-concurrency refuses a generic main-actor class with no nonisolated deinit

In a uiSwiftSettings target a generic class's implicit deinit is isolated, and
the Release compile's optimizer crashes on it (EarlyPerfInliner, swift-frontend
6.3, macOS 14 target): #23050's FirstMouseHostingView broke every Release
compile until #23233, the same crash #20408 fixed before. The crash-class rule
now requires `nonisolated deinit` there (a reviewed `// concurrency-allow:`
escapes it). Non-UI targets stay out of scope.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15ba2-4ce2-7e02-8fa2-5cd34f7648b1&host=cosmic-gray

* Merge feat-cmux-next; one registry entry for test_crash_class_fast_gate; braces in strings don't end a class

Base registered tests/test_crash_class_fast_gate.py (#23352) after the
pre-commit hook had registered it on this branch, so keep base's entry only.
Review: declares_nonisolated_deinit counted a "}" in a string literal; it now
scans main_actor_scope.strip(line), and the header names the rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15ba2-4ce2-7e02-8fa2-5cd34f7648b1&host=cosmic-gray

* ci(next): the generic-class rule ignores braces in strings; the header names it

Review: declares_nonisolated_deinit counted a "}" in a string literal and ended
the class before its deinit. Scan main_actor_scope.strip(line), as the scope
scan does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15ba2-4ce2-7e02-8fa2-5cd34f7648b1&host=cosmic-gray

---------

Co-authored-by: Leo Li <leoli.4u@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
lawrencecchen added a commit that referenced this pull request Oct 24, 2026
…init-only edits, plain deinits and context classes in CI (red)

Review of 7697d4d9007 (arch-b3). Each new test fails on the current rules:
- guardrails.py judges a class only when its declaration line changes: a push that turns an existing
  `nonisolated deinit {}` into `isolated deinit { ... }`, or deletes it (also in a class declared in an
  extension of a generic type in another file), passes. A deletion-only push has no changed head line, so
  debt_in returns before it reads context.
- guardrails.py takes a plain `deinit` as the fix. In a uiSwiftSettings target it takes the default
  isolation (#20408 215fe45; check-concurrency.sh's plain-deinit rule), and every fix that landed is
  `nonisolated deinit`. The passing fixture now uses `nonisolated deinit`; a plain one is refused.
- guardrails.py ignores the target's isolation while check-concurrency.sh reads it: a generic class in a
  daemonSwiftSettings target (nonisolated by default) is refused pre-push and passed by CI.
- check-concurrency.sh's CI crash-class rule matches only `class Name<`: ObservedMap.Slot's shape (a class
  nested in a generic type), a class in an extension of a generic type in another file, and an explicit
  `@MainActor` generic class in a non-UI target pass.
Two guards that pass today keep the fix from refusing old debt: a member edit or addition inside a class
that is base debt, and a deletion next to the `while` around an old sleep.

Ran (local, no build): python3 tests/test_cmux_next_guardrails.py RuleTests: 4 failures, 1 error;
python3 -I tests/test_check_concurrency_generic_deinit.py: 3 failures.
lawrencecchen added a commit that referenced this pull request Oct 24, 2026
…oth gates; a body edit or deletion is judged; only `nonisolated deinit` is the fix

Review of 7697d4d9007 (arch-b3), three findings with one root cause each:

1. guardrails.py judged a class only when its declaration line (or a generic opener above it) changed, and a
   deletion-only push left the head side with no changed line, so debt_in returned before reading context.
   Now implicit_deinit returns the class's own body span (declaration to closing brace) with its generic
   openers, and each side of a hunk that is empty on one side (a pure deletion or insertion) marks the
   unchanged line before and after it, on both sides (Version.touched). An edited or deleted deinit, or any
   member edit, judges the class; the base side marks the same lines, so a class that is already base debt
   pairs with itself and never blocks (the ratchet rule), and a deletion next to the `while` around an old
   sleep pairs too.
2. A plain `deinit` counted as the fix. It takes the target's default isolation, the main actor in a
   uiSwiftSettings target (#20408 215fe45; check-concurrency.sh's plain-deinit rule), and every fix
   that landed is `nonisolated deinit`; only that counts now. No Release build was run to test a plain deinit
   (code-only slice): the rule takes the side the repo's evidence supports, so it needs none.
3. The pre-push rule and the CI crash-class rule (check-concurrency.sh GENERIC_CLASS, `class Name<` only,
   UI targets only) were two detectors with different rules. scripts/cmux-next/swift_scope.py now holds the
   one detector, `judge`: a class in main-actor code (check-main-actor-work.py's lexical scopes over the
   target default from Package.swift, so a daemonSwiftSettings class is out unless `@MainActor`), generic by
   itself or by context (a generic type, function or `where` clause around it, or an extension of a type
   declared generic: anywhere in Sources for CI, in the push's files or the type's Name.swift pre-push), with
   no `nonisolated deinit` at its own depth. Both gates call it and honor both escapes (`generic-class-allow:`,
   `concurrency-allow:`). guardrails.py's other rules read code_of / escaped / enclosing_openers from the same
   module (moved, unchanged). The crash-class workflow's path filters include swift_scope.py.

Scope: CI still scans Sources only (no new CI gate); the pre-push rule reads Sources and Tests.

Ran (local, no build):
- python3 tests/test_cmux_next_guardrails.py: 50 tests OK (the 5 red ones included).
- python3 -I tests/test_check_concurrency_generic_deinit.py: 10 tests OK (the 3 red ones included).
- CHECK_CONCURRENCY_ONLY=crash-class scripts/cmux-next/check-concurrency.sh on this tree: ok (3 s); the
  full check prints the same 7 lines as before the change.
- The detector over every Sources and Tests file at this tree: 0 classes (the 7 the previous widened rule
  counted are all in daemonSwiftSettings targets, nonisolated by default).
- guardrails.py old vs new on the 40 latest CmuxNext commits of feat-cmux-next and 4 named ranges: the same
  refusals; 1.4 s for this branch's range.
lawrencecchen added a commit that referenced this pull request Oct 24, 2026
…init-only edits, plain deinits and context classes in CI (red)

Review of 7697d4d9007 (arch-b3). Each new test fails on the current rules:
- guardrails.py judges a class only when its declaration line changes: a push that turns an existing
  `nonisolated deinit {}` into `isolated deinit { ... }`, or deletes it (also in a class declared in an
  extension of a generic type in another file), passes. A deletion-only push has no changed head line, so
  debt_in returns before it reads context.
- guardrails.py takes a plain `deinit` as the fix. In a uiSwiftSettings target it takes the default
  isolation (#20408 215fe45; check-concurrency.sh's plain-deinit rule), and every fix that landed is
  `nonisolated deinit`. The passing fixture now uses `nonisolated deinit`; a plain one is refused.
- guardrails.py ignores the target's isolation while check-concurrency.sh reads it: a generic class in a
  daemonSwiftSettings target (nonisolated by default) is refused pre-push and passed by CI.
- check-concurrency.sh's CI crash-class rule matches only `class Name<`: ObservedMap.Slot's shape (a class
  nested in a generic type), a class in an extension of a generic type in another file, and an explicit
  `@MainActor` generic class in a non-UI target pass.
Two guards that pass today keep the fix from refusing old debt: a member edit or addition inside a class
that is base debt, and a deletion next to the `while` around an old sleep.

Ran (local, no build): python3 tests/test_cmux_next_guardrails.py RuleTests: 4 failures, 1 error;
python3 -I tests/test_check_concurrency_generic_deinit.py: 3 failures.
lawrencecchen added a commit that referenced this pull request Oct 24, 2026
…oth gates; a body edit or deletion is judged; only `nonisolated deinit` is the fix

Review of 7697d4d9007 (arch-b3), three findings with one root cause each:

1. guardrails.py judged a class only when its declaration line (or a generic opener above it) changed, and a
   deletion-only push left the head side with no changed line, so debt_in returned before reading context.
   Now implicit_deinit returns the class's own body span (declaration to closing brace) with its generic
   openers, and each side of a hunk that is empty on one side (a pure deletion or insertion) marks the
   unchanged line before and after it, on both sides (Version.touched). An edited or deleted deinit, or any
   member edit, judges the class; the base side marks the same lines, so a class that is already base debt
   pairs with itself and never blocks (the ratchet rule), and a deletion next to the `while` around an old
   sleep pairs too.
2. A plain `deinit` counted as the fix. It takes the target's default isolation, the main actor in a
   uiSwiftSettings target (#20408 215fe45; check-concurrency.sh's plain-deinit rule), and every fix
   that landed is `nonisolated deinit`; only that counts now. No Release build was run to test a plain deinit
   (code-only slice): the rule takes the side the repo's evidence supports, so it needs none.
3. The pre-push rule and the CI crash-class rule (check-concurrency.sh GENERIC_CLASS, `class Name<` only,
   UI targets only) were two detectors with different rules. scripts/cmux-next/swift_scope.py now holds the
   one detector, `judge`: a class in main-actor code (check-main-actor-work.py's lexical scopes over the
   target default from Package.swift, so a daemonSwiftSettings class is out unless `@MainActor`), generic by
   itself or by context (a generic type, function or `where` clause around it, or an extension of a type
   declared generic: anywhere in Sources for CI, in the push's files or the type's Name.swift pre-push), with
   no `nonisolated deinit` at its own depth. Both gates call it and honor both escapes (`generic-class-allow:`,
   `concurrency-allow:`). guardrails.py's other rules read code_of / escaped / enclosing_openers from the same
   module (moved, unchanged). The crash-class workflow's path filters include swift_scope.py.

Scope: CI still scans Sources only (no new CI gate); the pre-push rule reads Sources and Tests.

Ran (local, no build):
- python3 tests/test_cmux_next_guardrails.py: 50 tests OK (the 5 red ones included).
- python3 -I tests/test_check_concurrency_generic_deinit.py: 10 tests OK (the 3 red ones included).
- CHECK_CONCURRENCY_ONLY=crash-class scripts/cmux-next/check-concurrency.sh on this tree: ok (3 s); the
  full check prints the same 7 lines as before the change.
- The detector over every Sources and Tests file at this tree: 0 classes (the 7 the previous widened rule
  counted are all in daemonSwiftSettings targets, nonisolated by default).
- guardrails.py old vs new on the 40 latest CmuxNext commits of feat-cmux-next and 4 named ranges: the same
  refusals; 1.4 s for this branch's range.
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