Skip to content

Require trusted page context for Settings agent updates - #21439

Merged
teamleaderleo merged 16 commits into
feat-cmux-nextfrom
codex/settings-agent-update-context-b603108f
Oct 19, 2026
Merged

teamleaderleo merged 16 commits into
feat-cmux-nextfrom
codex/settings-agent-update-context-b603108f

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 18, 2026 •

Copy link
Copy Markdown
Collaborator

Settings agent updates could reach the terminal handoff without a user gesture. Preserve the provider's trusted page context and require a real gesture or native confirmation for Update and Update All. Params cannot supply that authorization.

Validation: all four regression methods, including 13 parameter cases, pass on the fleet and fail before the fix. Swift syntax and scoped conventions pass. No localization keys changed; existing localization and package-group checks still fail.

Owner-bound job UI adoption is separate. Stacked on #21436.

Changelog

Fixed: Settings agent updates require a user gesture or native confirmation.

@coderabbitai

coderabbitai Bot commented Oct 18, 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: b2b0a4a7-cc49-4ddf-b4dc-553a0310a6af

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.

@blacksmith-sh

blacksmith-sh Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

Found 8 test failures on Blacksmith runners:

Failures

Test View Logs
test/agent-pane-mode-keys.test.ts/agent-pane-mode-keys.test View Logs
test/agent-pane-mode-keys.test.ts/agent-pane-mode-keys.test View Logs
test/check-system-blue.test.ts/check-system-blue.test View Logs
test/check-system-blue.test.ts/check-system-blue.test View Logs
test/composer-footer-plain.test.ts/composer-footer-plain.test View Logs
test/composer-footer-plain.test.ts/composer-footer-plain.test View Logs
test/theme-colors.test.ts/theme-colors.test View Logs
test/theme-colors.test.ts/theme-colors.test View Logs

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

@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

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

Unclear: web / react-apps-check failed: no known signature; failed step: Check webview test shards. This PR merged before its CI finished: fix forward on main.

CI failed on 6460e0a6e9 (run 39219474269 attempt 1): 2 code, 1 unknown.
macOS jobs did not run: compile and app tests were skipped: Fast static checks skipped.

Job Verdict Why
web / react-apps-check (1) code a web test failed
web / react-apps-check (2) code a web test failed
web / react-apps-check unknown no known signature; failed step: Check webview test shards

Failures:

  • seen on other PRs check-system-blue.test.ts check-system-blue > this checkout is within its baseline: failed (job)
  • seen on other PRs composer-footer-plain.test.ts at 760 px (light): off, the chips draw as today: failed (job)
  • seen on other PRs composer-footer-plain.test.ts at 760 px (light): on, plain text at rest, same labels, no overlap: failed (job)
  • seen on other PRs composer-footer-plain.test.ts at 760 px (dark): off, the chips draw as today: failed (job)
  • seen on other PRs composer-footer-plain.test.ts at 760 px (dark): on, plain text at rest, same labels, no overlap: failed (job)
  • seen on other PRs composer-footer-plain.test.ts at 480 px (light): off, the chips draw as today: failed (job)
  • seen on other PRs composer-footer-plain.test.ts at 480 px (light): on, plain text at rest, same labels, no overlap: failed (job)
  • seen on other PRs composer-footer-plain.test.ts at 480 px (dark): off, the chips draw as today: failed (job)
  • seen on other PRs agent-pane-mode-keys.test.ts the trigger and every menu row draw the mode's own shield: failed (job)
Matched log lines
web / react-apps-check (1): (fail) check-system-blue > this checkout is within its baseline [1702.02ms]
web / react-apps-check (2): (fail) the trigger and every menu row draw the mode's own shield [1102.05ms]

Not re-run automatically: web / react-apps-check (1), web / react-apps-check (2), web / react-apps-check are not machine failures.

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.

CI fast guards failed on bff87ce37b (https://github.com/manaflow-ai/cmux/actions/runs/39217645785). It does not block the merge; a red guard merged into main breaks it for every open PR.
The log named no failed step; see the run.

Agents: python3 scripts/ci/guard_attribution.py fix applies the mechanical fixes locally.

@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

CI fast guards failed on 0309542cbc (https://github.com/manaflow-ai/cmux/actions/runs/39209824991). It does not block the merge; a red guard merged into main breaks it for every open PR.
The log named no failed step; see the run.

Agents: python3 scripts/ci/guard_attribution.py fix applies the mechanical fixes locally. This comment is updated in place on each push.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Classified the current red guard checks as repository-wide baseline drift, not a #21439 source failure. The exact head 5134a81 changes only Settings page-context plumbing/tests, while Fast static checks report 34,175 existing localization parity needs_review entries, three stale localization-default exceptions, and workspace package-group drift. No unrelated generated baselines were changed.

@teamleaderleo
teamleaderleo force-pushed the codex/action-caller-context-058b3d8e branch from 6e4fce1 to afbdc13 Compare October 19, 2026 00:00
@teamleaderleo
teamleaderleo force-pushed the codex/settings-agent-update-context-b603108f branch from 5134a81 to 448c207 Compare October 19, 2026 00:00
@teamleaderleo
teamleaderleo changed the base branch from codex/action-caller-context-058b3d8e to feat-cmux-next October 19, 2026 03:01
@teamleaderleo
teamleaderleo force-pushed the codex/settings-agent-update-context-b603108f branch from 448c207 to 6f6b531 Compare October 19, 2026 03:02
@teamleaderleo
teamleaderleo temporarily deployed to cmux-tui-merge-tree October 19, 2026 03:02 — with GitHub Actions Inactive
@github-actions

github-actions Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Receipt for 6460e0a (this PR's head; a new push replaces it)

  • Visual review: HQ feed card · gallery before/after runs
  • Checks: this head
  • App of this head (app.zip, kept 1 day): gh workflow run reload-build.yml -R manaflow-ai/cmux --ref feat-cmux-next -f tag=pr21439 -f ref=codex/settings-agent-update-context-b603108f -f platform=macos -f backend_mode=local

@github-actions

github-actions Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

red: web / Web status; do not merge until green; see lane-rules
red: web / react-apps-check; do not merge until green; see lane-rules
red: web / react-apps-check (1); do not merge until green; see lane-rules
red: web / react-apps-check (2); do not merge until green; see lane-rules

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Bead cx-6tdnp created for this PR by the cmux coordinator.

@teamleaderleo

teamleaderleo commented Oct 19, 2026 •

Copy link
Copy Markdown
Collaborator Author

Visual preview of bff87ce: in the maintainers' private review feed.

@teamleaderleo
teamleaderleo force-pushed the codex/settings-agent-update-context-b603108f branch from 6f6b531 to 3595fdd Compare October 19, 2026 03:44
teamleaderleo added a commit that referenced this pull request Oct 19, 2026
Replays #21439's change onto current feat-cmux-next as one commit. Turning Update Automatically on now needs a gesture or native confirmation too, since acpmux runs the update after its next check (review finding); SettingsHarnesses is compacted so CmuxNextApp's source lines don't grow (size cap). The health read defaults to the named AcpmuxEnvironment.harnessHealth rather than an async closure literal (crash ratchet).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@teamleaderleo
teamleaderleo temporarily deployed to cmux-tui-merge-tree October 19, 2026 03:44 — with GitHub Actions Inactive
teamleaderleo and others added 6 commits October 18, 2026 20:46
Replays #21398's change onto current feat-cmux-next as one commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replays #21401's change onto current feat-cmux-next as one commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replays #21402's change onto current feat-cmux-next as one commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replays #21414's change onto current feat-cmux-next as one commit. Keeps the crash ratchet flat: poisoned locks are recovered instead of unwrapped (only a held job lock is busy), and the job status parses its decimal strings with radix: 10.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replays #21429's change onto current feat-cmux-next as one commit, written without growing CmuxNextApp's source lines (size cap).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replays #21436's change onto current feat-cmux-next as one commit. ControlRequest takes callerApp in its init so CmuxNextApp's source lines don't grow (size cap).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@teamleaderleo
teamleaderleo force-pushed the codex/settings-agent-update-context-b603108f branch from 3595fdd to 36bc24b Compare October 19, 2026 03:46
@teamleaderleo
teamleaderleo temporarily deployed to cmux-tui-merge-tree October 19, 2026 03:46 — with GitHub Actions Inactive
teamleaderleo added a commit that referenced this pull request Oct 19, 2026
Replays #21439's change onto current feat-cmux-next as one commit. Turning Update Automatically on now needs a gesture or native confirmation too, since acpmux runs the update after its next check (review finding); SettingsHarnesses is compacted so CmuxNextApp's source lines don't grow (size cap). The health read defaults to the named AcpmuxEnvironment.harnessHealth rather than an async closure literal (crash ratchet).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replays #21439's change onto current feat-cmux-next as one commit. Turning Update Automatically on now needs a gesture or native confirmation too, since acpmux runs the update after its next check (review finding); SettingsHarnesses is compacted so CmuxNextApp's source lines don't grow (size cap). The health read defaults to the named AcpmuxEnvironment.harnessHealth rather than an async closure literal (crash ratchet).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@teamleaderleo
teamleaderleo force-pushed the codex/settings-agent-update-context-b603108f branch from 36bc24b to f8390f6 Compare October 19, 2026 04:30
@teamleaderleo
teamleaderleo temporarily deployed to cmux-tui-merge-tree October 19, 2026 04:30 — with GitHub Actions Inactive
@teamleaderleo
teamleaderleo temporarily deployed to cmux-tui-merge-tree October 19, 2026 06:12 — with GitHub Actions Inactive
- regenerated plans/cmux-next/action-surfaces.json
- regenerated schemas/defaults/cmux-next.json
- vp fmt webviews/src/agent-session/acpmux/replyHref.ts
- vp fmt webviews/src/pages/markdown/links.ts
- vp fmt webviews/test/markdown-cmux-app-links.test.ts

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/39208575467
# Conflicts:
#	webviews/src/agent-session/acpmux/replyHref.ts
#	webviews/src/pages/markdown/links.ts
@teamleaderleo
teamleaderleo temporarily deployed to cmux-tui-merge-tree October 19, 2026 07:58 — with GitHub Actions Inactive
@teamleaderleo
teamleaderleo merged commit 6960d2a into feat-cmux-next Oct 19, 2026
@teamleaderleo
teamleaderleo deleted the codex/settings-agent-update-context-b603108f branch October 19, 2026 08:04
teamleaderleo added a commit that referenced this pull request Oct 19, 2026
Resolved the #21439 add/add conflicts against its merged head (6460e0a) as
the base, and took feat-cmux-next for the acpmux SessionRail and strings files
this PR does not own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
teamleaderleo added a commit that referenced this pull request Oct 20, 2026
* acpmux: require version evidence for already-current updates

Replays #21398's change onto current feat-cmux-next as one commit.

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

* agents: refresh inventory versions without probing or updating

Replays #21401's change onto current feat-cmux-next as one commit.

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

* agents: connect explicit version refresh to the selected owner

Replays #21402's change onto current feat-cmux-next as one commit.

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

* Add owner update terminal jobs with durable replay

Replays #21414's change onto current feat-cmux-next as one commit. Keeps the crash ratchet flat: poisoned locks are recovered instead of unwrapped (only a held job lock is busy), and the job status parses its decimal strings with radix: 10.

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

* Preserve app mutation keys through shared action runs

Replays #21429's change onto current feat-cmux-next as one commit, written without growing CmuxNextApp's source lines (size cap).

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

* Preserve app callers through the shared action registry

Replays #21436's change onto current feat-cmux-next as one commit. ControlRequest takes callerApp in its init so CmuxNextApp's source lines don't grow (size cap).

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

* Share agent CLI update UI across Settings and actions

Replays #21447's change onto current feat-cmux-next as one commit, together
with its follow-up fix.

Settings and the palette run agent updates on their captured owner route
instead of typing a health argv into a focused pane. The update owner
prepares only npm and native installations, and refuses a family when any
of its rows is another channel or already current, so both surfaces submit
only those families and Settings shows Update only for rows whose channel
the owner runs.

A start the owner refuses (agent_cli_job.*, answered before it journals a
creation) leaves no "unknown" job that blocks that CLI. One family's failure
does not stop the rest of a batch; the batch reports every failure. A lost
reply still keeps its unknown job, since the terminal may exist.

Update, Update all and turning Update Automatically on still need a gesture
or a confirmed user, as #21439 requires.

The update action, its owner route and the health-row reader live in a new
CmuxNextAgentUpdates module, leaving thin app glue in CmuxNextApp.

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

* Require trusted page context for Settings agent updates

Replays #21439's change onto current feat-cmux-next as one commit. Turning Update Automatically on now needs a gesture or native confirmation too, since acpmux runs the update after its next check (review finding); SettingsHarnesses is compacted so CmuxNextApp's source lines don't grow (size cap). The health read defaults to the named AcpmuxEnvironment.harnessHealth rather than an async closure literal (crash ratchet).

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

* chore(next): PR autofix at 0aae74c

- regenerated plans/cmux-next/action-surfaces.json
- regenerated schemas/defaults/cmux-next.json
- vp fmt webviews/src/agent-session/acpmux/replyHref.ts
- vp fmt webviews/src/pages/markdown/links.ts
- vp fmt webviews/test/markdown-cmux-app-links.test.ts

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

* Bind the Settings update scope with withValueInTask

TaskLocal.withValue frees its marker with the task allocator even when no
task is current (cx-bpcj); the crash-class gate bans it. Use the shared
withValueInTask helper, as ActionRunScope does.

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

* chore(next): PR autofix at 45adaef

- regenerated plans/cmux-next/action-surfaces.json
- regenerated web/data/cmux-shortcuts.generated.json
- regenerated cmux-tui/crates/cmux-app-host/generated/cmux-app.d.ts
- regenerated schemas/defaults/cmux-next.json

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

* test(next): agent CLI updates survive reconnects, unknown exits, owner mismatches and palette failures

Regression tests for four defects in the shared agent CLI update action:
a reconnect to the same owner hides and blocks the job, a new daemon
session leaves a job that blocks the CLI forever, an exit with an unknown
outcome blocks the CLI forever, a receipt for another owner leaves a
blocking job, and a palette run's failure is never shown.

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

* fix(next): agent CLI update jobs follow their owner across reconnects and settle on any end

- Showing, refreshing and blocking now share one owner rule: a job whose
  route went stale is re-captured when the app still routes the same
  machine and session (its receipts stay observable after a reconnect),
  and otherwise neither shows nor blocks the CLI.
- An exit with an unknown process result stays state "exited" with
  outcome "unknown", so it settles instead of blocking forever.
- A receipt for another owner drops its job before the error.
- A palette run (no caller capturing work) shows its failure through the
  registry's refusal instead of dropping it.
- Nothing-to-update and still-updating refusals get their own localized
  text instead of the generic add/remove agents message.

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

* test(next): agent update survives a reconnect mid-batch and a lost start reply

A reconnect while one family's start waits must not start the next family on
the re-captured owner and leave it with a job that blocks that CLI. A lost
start reply must be replayed by its key on refresh, so the job gets its
receipt or the owner's refusal clears it.

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

* fix(next): agent update start keeps its captured owner and replays lost replies

start() re-read intent.route after every await, while showing or refreshing
the jobs can swap it for a re-capture after a reconnect. A batch then went
on to start the next family on the new connection, whose route had no open
carrier, so that family kept an unknown job with no receipt that blocked its
CLI for good. start() now captures the route once and stops when it retires,
and a route opens its carrier on start as well as on inventory and observe.

A lost start reply left an unknown job that blocked the CLI until restart.
The intent keeps the child key, and refresh sends it again on the current
owner: the owner replays the key, so the job gets its receipt, or its
refusal clears the job.

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

* test(next): a busy answer to a replayed agent CLI update start keeps its job

The owner answers agent_cli_job.busy before it looks the key up, so on a
replay busy usually means the key's original request is still running.

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

* fix(next): a busy answer to a replayed agent CLI update start keeps its job

Review of #21447: refresh treated agent_cli_job.busy on a replay as the
key creating nothing, dropping the job while its original request could
still create the terminal, which then went untracked and unblocked.

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

* chore(next): PR autofix at 99a29e6

- plans/cmux-next/action-surfaces.json
- plans/cmux-next/actions.md

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

* chore(next): PR autofix at 3b5a7ca

- Packages/macOS/CmuxNext/ci-target-graph.json
- schemas/defaults/cmux-next.json

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

* test(next): settled owner failures and ended jobs never block agent CLI updates (red)

Regressions for the #21447 review:

- The owner journals a failed creation under its key and answers every
  replay with the same failure (operation.failed, mutation.indeterminate).
  Such an answer, on the first start or on a replay of a lost reply,
  must settle the job instead of leaving it unknown and blocking the CLI
  for the rest of the session.
- A job that already ended must not block the next update of its CLI
  just because nothing refreshed Settings > Agents in between; a job
  that still runs keeps refusing it.

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

* fix(next): settle agent CLI updates the owner answered, and check ended jobs before refusing

- Any coded owner answer to a start settles its key. agent_cli_job.*
  comes before a creation is journaled; any other code (a failed
  creation, operation.failed, or an interrupted one,
  mutation.indeterminate) is the key's journaled outcome and the owner
  answers every replay the same way. Such an answer, on the first start
  or on a replay, now drops the job instead of leaving it unknown and
  blocking the CLI for the rest of the session. Only a failure with no
  owner answer (a lost reply) is replayed; busy on a replay still keeps
  the job.
- A start that finds an earlier job of the same CLI still unended
  refreshes once before refusing, so a job that already exited no longer
  blocks the next palette or Settings update until someone opens
  Settings > Agents. A job that still runs keeps refusing.

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

* test(next): a coded answer to a replayed agent CLI update start keeps the lost job (red)

Regression for the delta review of 0ad8305: a replay can get a coded
answer the owner sends before it reads its journal (daemon_shutdown_pending
during a handoff, operation.failed from a registry error), while the
lost first start may have created the update terminal. Dropping the job
then hides a running update and unblocks a second one of the same CLI.

A first start's fresh key has nothing running, so any coded answer there
still settles; its cases are now operation.failed and
daemon_shutdown_pending, with a fixture message that matches.

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

* fix(next): only a refusal settles a replayed agent CLI update start

A replay of a lost start now drops its job only on an agent_cli_job.*
refusal, as before 0ad8305. Another coded answer can come before the
owner reads its journal (daemon_shutdown_pending during a handoff, or a
registry error mapped to operation.failed) while the lost first start
created the terminal, so the job keeps blocking its CLI and the next
refresh replays the same key. A first start's fresh key still settles on
any coded answer, which is what keeps a failed creation from blocking the
CLI for the session.

A lost reply whose creation then failed is still replayed until the
owner can say so; #21469's journal resolve settles that case.

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

* test(next): an indeterminate first agent CLI update start keeps blocking its CLI (red)

From the second delta review: the owner answers a fresh key with
mutation.indeterminate when a failed creation left its reserved terminal
possibly live (AmbiguousLive in creation_settlement.rs). Dropping that
job would let a second update of the same CLI start beside it.

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

* fix(next): an indeterminate first agent CLI update start keeps its job

`answered` no longer settles mutation.indeterminate: the owner says a
failed creation's effect may have run, so the job stays unknown and keeps
blocking its CLI, like a lost reply, until it is replayed or its owner
session retires it. The comments no longer claim a coded first-start
answer always means nothing runs.

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>
teamleaderleo added a commit that referenced this pull request Oct 20, 2026
…s every tier on any branch (#22325)

* ci(cmux-next): test that a same-repo PR runs its routed Mac tiers and a branch dispatch runs every tier (red)

Refs https://github.com/manaflow-ai/cmux/issues/22303

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

* ci(cmux-next): a same-repo PR runs its routed Mac tiers, and dispatch runs every tier on any ref (green)

A same-repository pull request into feat-cmux-next no longer needs the full-ci
label for the Mac tiers the router picks (web bundles, Swift canary, swift test,
daemon tests, generated files, Release compile, app scheme compile). A fork PR
keeps the full-ci gate and still never places an owned runner.

workflow_dispatch runs every tier on any ref, not only feat-cmux-next and
next-batch/*, so a branch gets full CI without a side next-batch push. A newer
plain dispatch on the same working branch cancels the older one.

Fixes https://github.com/manaflow-ai/cmux/issues/22303

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

* ci(cmux-next): side-lane placement test expects a branch dispatch to place its Mac jobs

Refs https://github.com/manaflow-ai/cmux/issues/22303

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

* ci(cmux-next): test that a branch dispatch's deferred tree still runs its daemon tests and web bundles follow the route (red)

Refs https://github.com/manaflow-ai/cmux/issues/22303

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

* ci(cmux-next): a branch dispatch's deferred tree still runs its daemon tests; web bundles follow the route (green)

The tree wait marker records a working-branch dispatch as origin dispatch, so
the same-tree run cmux_next_tree_notify.py starts for it runs daemon-test (a
push's skips it). web-bundles waits for path routing and runs on a pull request
only when swift, daemon or generated is routed.

Refs https://github.com/manaflow-ai/cmux/issues/22303

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

* ci(cmux-next): test that a tag dispatch's deferred tree jobs start on the tag (red)

Refs https://github.com/manaflow-ai/cmux/issues/22303

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

* ci(cmux-next): a tag dispatch's deferred tree jobs start on the tag (green)

The tree wait marker of a dispatch records whether its ref is a branch or a
tag. cmux_next_tree_notify.py takes a tag marker as current (a tag has no
branch head to look up) and dispatches same-tree mode on the tag.

Refs https://github.com/manaflow-ai/cmux/issues/22303

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

* ci(next): fast tier builds the Swift packages and the cmux-tui tests side by side (#22330)

The fast tier built CmuxNext (~160 s) and then the cmux-tui test binaries
(~115 s) one after the other, about 290 of its ~320 s on a warm mini. The two
builds share no outputs, so they now run concurrently, each group in order,
with their output prefixed by build. A failed build still fails the tier
before any entry runs.

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

* ci(next): a fast-tier timeout kills the entry's whole process group (#22357)

* test(next): a timed-out fast-tier entry must not leave its children running

cmux19-glaeda-1, run 39360407187 job 118427686199: three entries timed out
twice each, and the runner's post-job cleanup then killed six orphan
xctest processes. The timeout killed only swift test, not the xctest it
spawned.

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

* ci(next): a fast-tier timeout kills the entry's whole process group

swift test runs the tests in an xctest child. On a timeout,
subprocess.run killed only swift test, so the xctest kept running (and
loading the host) through the rest of the tier: cmux19-glaeda-1 run
39360407187 ended with six orphan xctest processes. Each entry now runs
in its own session, and a timeout (or the entry's end) kills the group.

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

* test(next): a fast-tier entry's child in its own process group dies too

From review: SwiftPM starts xctest in a process group of its own, so
killing the entry's group misses it, and draining the output then waits
for it.

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

* ci(next): a fast-tier timeout kills every process below the entry

From review: SwiftPM starts xctest in a process group of its own, so a
group kill missed it and the output drain then waited on it. The timeout
now kills the entry's group, every descendant and each descendant's
group (never the tier's own), drains with a 5 s cap, and tolerates a
group already gone or zombie-only (EPERM on macOS).

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

---------

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

* cmux NEXT: full screen no longer lags (AppKit's titlebar window is not a page)

In full screen AppKit makes its titlebar window (NSToolbarFullScreenWindow,
a plain NSWindow) a child of the window. WindowOverlayHost.isPageWindow
counted every non-panel child as a Chromium page, so a full screen window
stayed in page mode: overlay planes moved into the overlay panel, divider
mouse-catcher panels covered every split, pages re-synced after every
layout, and the child order was checked on every event. Divider drags,
sidebar hovers, group toggles and tab switches all lagged.

AppKit's own child windows are now AppKit chrome: never pages, allowed by
ChildWindowPolicy, and never re-added by reassertOrder.

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

* cmux NEXT: tests, a full screen window's AppKit titlebar child keeps it out of page mode

Red on the base (FullScreenTitlebarPageModeTests: the titlebar child put the
window in .overlayWindow page mode), green with the fix.

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

* cmux NEXT: a background chat or New Tab page can't take the keyboard (#22448)

* test(next): a hidden agent page cannot take the keyboard by itself

A pane keeps the tabs it does not show parked in place, hidden. A parked chat or New Tab page in the focused pane that calls element.focus() makes WebKit ask for first responder, and AgentPaneView.refusesSelfFocus lets it through because the page sits in the focused pane. Fails until the fix.

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

* fix(next): a hidden agent page cannot take the keyboard by itself

AgentPaneView.refusesSelfFocus refused only a page in another pane. A chat or New Tab page parked hidden in the focused pane passed: its element.focus() (the composer mounting as a New Tab ask turned into the chat, a reconnect) made it first responder, the focus model filed that as the same pane's content and re-applied nothing, and typing went into the hidden page. A page that does not show is now refused too, outside input events and the focus model's own moves.

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

* chore(next): PR autofix at 49641b36c868

- schemas/defaults/cmux-next.json

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

---------

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 New Tab: the field takes the keyboard back when you return to the page (#22456)

* test(next): the New Tab field takes the keyboard back with its page

After a press on the New Tab page's background nothing on it has focus. Switching to another tab or pane and back gave the page the keyboard with nothing in it: no caret, and Backspace, the arrows or Cmd-V went nowhere. Fails in Chromium and WebKit until the fix.

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

* fix(next): the New Tab field takes the keyboard back with its page

The New Tab page focused its field only when it appeared and on Cmd-L. After a press on the page's background, a tab or pane switch back gave the page the keyboard with nothing in it focused. The field now takes an idle keyboard when the window takes focus, as the chat composer does (composerFocus.ts), keeping its caret; a focused control keeps it. Not with focusField off (an omnibar owns the keyboard).

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

* test(next): the New Tab field's reclaim keeps the page's scroll

Review: focus() scrolls the field into view, so a scrolled New Tab page jumped back up when the field took the keyboard back. Fails until the follow-up fix.

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

* fix(next): the New Tab field's reclaim keeps the page's scroll

Review follow-up: focus the field with preventScroll, as the pane's other focus returns do.

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

---------

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

* cmux NEXT composer: a click anywhere on the card puts the cursor in the prompt (#22475)

* test(next): a press on the composer card around the text focuses the prompt

Only the editable text took a press: the field's padding, the gap around the button row and the card's edge left nothing focused. Codex and Claude Code treat the whole input card as the text area. Fails in Chromium and WebKit until the fix; the button case passes on both.

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

* fix(next): a press on the composer card around the text focuses the prompt

The composer card takes a press outside its text, controls and attachments (the field's padding, the gap around the button row, the card's edge) and focuses the prompt where its caret was, or the shell field in shell mode. Codex and Claude Code treat the whole input card as the text area.

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

* test(next): a press inside a menu the composer card opened stays the menu's

Review: the card's press handler cancelled presses in the + menu's padding (and React hands up presses from what the card portals). Fails on 64dc9976a3cc in Chromium and WebKit; passes on feat-cmux-next.

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

* fix(next): the composer card leaves presses in its menus, dialogs and scrollbar alone

Review follow-up: focusFromCard skips presses from outside the card's own DOM (portals), in .ui-popup menus and dialogs, on a long prompt's scrollbar, and Control-presses (the macOS context menu).

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

---------

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

* cmux NEXT New Tab: the composer stays where it first painted (#22365)

* test(newtab): the New Tab composer stays put across loading, typing, attachments and Now Playing

Fails on base: the composer template centers the card on everything under
it, and a pasted image's chips push the field's text down.

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

* cmux NEXT New Tab: the composer stays where it first painted

The composer template no longer centers the page's content, which moved
the card with every row under it, each pasted image and each line of a
long draft; the card sits at a fixed place about the middle, where
centering put the resting page. Pasted or dropped attachments go under
the field instead of above it, so the typed text keeps its place in
every template, and they take the chat composer's tile size. Gallery:
composer-holds-its-place and composer-template-holds-its-place.

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

* cmux NEXT New Tab: the attachment's remove badge is the chat composer's

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

* cmux NEXT New Tab: review fixes for the composer-stays-put test

The rect log compares whole rects, so a field at 100,180 after 100,18
counts as a move; the long-draft step starts once Now Playing is drawn.
The New Tab box drops an attachment variable its strip padding overrides.

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

---------

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

* fix(next): show one notice at a time above the composer (#22370)

* fix(next): show one notice at a time above the composer

Notices above the composer used to stack: an agent CLI update card could
sit on top of a chat error. They now share one slot (NoticeSlot): of every
notice that wants the line, only the highest tier draws (error > status >
hint > update), and the rest wait and show once it clears.

The agent CLI update offer is no longer a banner. The model chip wears a
small dot; the full picker offers Update and Skip This Version, and the
compact model menu ends with an Update row. Only an
update the person started shows its progress line, at the lowest tier, so
it never covers an error. A signed-out agent CLI is an error.

Refs manaflow-ai/cmuxterm-hq#2569 (#12).

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

* fix(next): the update rows show their text without a tooltip

tooltip-rules: an element whose text shows carries no title. The model picker's and the compact menu's update rows draw their text, so they drop the title. The compact menu's rail tab keeps its tooltip: it draws only the harness's mark or initial.

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

---------

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

* cmux NEXT: Switch to tab on the New Tab page selects and focuses the tab (#22300)

* cmux NEXT: test that New Tab's Switch to tab selects and focuses the tab

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

* cmux NEXT: Switch to tab on the New Tab page selects and focuses that tab

The jump went through the palette's tab source, which selected the tab
through its pane controller. For a tab in another workspace the window
swaps that workspace in on a later turn, so the pane was not there: the
jump landed on the workspace's own focus. It now runs tab.focus, which
shows the workspace and screen, selects the tab and focuses its pane.

Another workspace with one open tab listed as that workspace (named by
the tab, labelled Switch to workspace). It lists as the tab now.

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

* chore(next): PR autofix at 12d677d94d24

- schemas/defaults/cmux-next.json

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

* cmux NEXT: New Tab jump test reads the pane on the main actor

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

* chore(next): PR autofix at 426a6b23aa29

- schemas/defaults/cmux-next.json

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

---------

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 agent chat: the composer takes the keyboard when its pane gets focus (#22527)

* test(next): agent chat takes the host's focusContent into its composer

On the shared page host the agent chat is a PageWebView, which sends focusContent on cmux.page.command each time its web view becomes first responder (pane-focus-DESIGN.md P18). The page never subscribes to that stream, so the signal is dropped. Fails until the page takes it: the composer prompt gets the keyboard, and a focused field or a text selection in the chat keeps it.

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

* fix(next): agent chat takes the host's focusContent into its composer

On the shared page host the agent page now subscribes to cmux.page.command and claims an idle keyboard for the composer on focusContent, the same claim the window's focus event and the composer's ready event make (pane-focus-DESIGN.md P18). The claim now also leaves a text selection alone: a transcript selection kept while the pane was away stays to be copied, and a printed key still goes into the composer.

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

* fix(next): agent chat's selection guard covers only text outside the composer

Review: a selection inside the composer's own prompt, with focus on the body, no longer blocks the claim. The test's stand-in host drops a subscription on unsub, so a delivered focusContent proves a live listener.

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

* docs(next): pane-focus P18, the agent chat takes focusContent on the page host

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

---------

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

* cmux NEXT New Tab: keys typed after opening a page from another pane's + reach it (#22528)

* cmux NEXT New Tab: regression for held keys of a page in a pane focus has not reached

A New Tab page opened cold in a pane the focus model does not name (the + of
another pane, no prewarmed spare) holds the keys typed after it opens. At the
page's readiness the hold's makeFirstResponder is refused by the window
(AgentPaneView.refusesSelfFocus), so the keys wait for the hold's limit and
then go to the pane that still has focus. This test fails until the fix.

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

* cmux NEXT New Tab: held keys reach a page opened in a pane focus has not reached

The key hold gives the New Tab field the keyboard with makeFirstResponder at
the page's readiness. The window refuses an agent page's web view outside an
input event and the focus model's own moves while the model names another
pane (AgentPaneView.refusesSelfFocus), so a page opened cold in a pane the
model had not reached (another pane's +; no spare, so no focusPane) never
drained: its keys waited for the hold's 2 s limit, then went to the old pane.

The hold now moves the focus model to the page's pane (programmatic: no
intent generation bump) before it focuses the field, once, and only when the
opening may change the view (not a script run without focus) and focus is
still on the pane it had at the opening (a click elsewhere since then wins).
didShow and cancel become one-liners so CmuxNextApp's source lines don't grow.

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

* chore(next): PR autofix at f877a9af6943

- Packages/macOS/CmuxNext/ci-target-graph.json
- schemas/defaults/cmux-next.json

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

---------

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

* agent pane: permission asks wait in line instead of hiding each other (#22463)

* agent pane: failing test for a second permission ask hiding the first

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

* agent pane: permission asks wait in line instead of hiding each other

acpmux keeps every pending ask, but the pane held one: a second ask that
joined no permission group replaced the shown card, and answering it left
the first with no card while its agent still waited. The direct client now
keeps the asks behind the shown one in arrival order (live and from the
attach's pending list), the card says how many more wait, and answering one
shows the next.

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

* agent pane: failing test for a reopened chat whose pending asks come in hash order

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

* agent pane: a reopened chat shows its waiting asks oldest first

acpmux lists a session's pending asks from a hash map, so the attach's
order is arbitrary. The client now orders them by the record that asked
(else by the time-ordered permission id), so the card keeps the ask the
replay showed and the rest wait behind it in the order they came.

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

---------

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

* agent pane: a permission card shows a command only for a command ask (#22465)

* agent pane: failing test for a permission card that shows its title as a command

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

* agent pane: a permission card shows a command only for a command ask

The single permission card fell back to the ask's title, minus a leading
Run, for its command block, so an edit, a fetch or an MCP call drew its
title twice: as the label and again as code. It now reads a command from
the request, or from the title only for an execute ask (or an ask without
a kind whose title says Run, as codex-acp titles commands).

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

* agent pane: failing test for a permission ask whose command is an argv array

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

* agent pane: a permission ask reads an argv command the way the transcript does

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

---------

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

* agent pane: a peer's running subagent counts its time on the session's clock (#22485)

* agent pane: failing test for a peer subagent's time read on this computer's clock

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

* agent pane: a peer's running subagent counts its time on the session's clock

A subagent's start and end are the session's event times, but the group
line, its rows, the agents pill and the summary list counted a running one
against this computer's clock, so on a peer whose clock ran ahead it read
0s the whole time, and one behind read too long. useNow now returns the
session's time (SubagentClockContext, the snapshot's clockOffsetMs), as the
turn's own clock already does.

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

---------

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

* cmux-next: the agents pill nests a subagent under the one that spawned it (#22521)

* cmux-next: failing test for nested agents in the agents pill list

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

* cmux-next: the agents pill lists a nested agent right under the one that spawned it

The list was flat by start time, so an agent another one spawned read as a
peer of every top-level agent. It now sits under its parent, moved in a
step per level, when both are in the same section.

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

* cmux-next: cover nestAgents' edge cases; the pill's indent follows the reading direction

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

---------

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

* cmux-next: running subagent groups start open; each chat keeps its open folds (#22525)

* cmux-next: failing test for subagent groups that start open while their agents run

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

* cmux-next: the subagent groups test starts its second batch after a reply

A read joins the open batch; the session's own text ends it.

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

* cmux-next: a group of running subagents starts open; a chat's open folds are its own

Every subagent group started closed, so a live batch of agents showed one
summary line until the person clicked it. A group with an agent still
running in the working turn now opens in the render that first draws it,
once: a group the person closes stays closed. A finished chat's groups, and
an agent a cancel left "running", still start closed.

The open folds were one set for every chat the pane showed, and row ids
such as subagents-<seq> repeat across chats, so a group opened in one chat
showed open in the next at the same place. A chat switch now clears them.

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

* cmux-next: subagent groups test covers a switch back and an earlier turn's stale agent

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

* cmux-next: each chat keeps its open folds; only the latest turn's running groups open

Switching chats cleared the open folds, so a running group the person had
closed opened again on the way back, and a group left open closed. Each
chat now keeps its own set for a switch back. An earlier turn's group with
an agent still running, or one a cancel left running, no longer opens when
the next turn starts. The gallery's running entry checks the group starts
open.

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

---------

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

* cmux-next: the Subagent of pill names the chat as the chat list does (#22526)

* cmux-next: failing test for the Subagent of pill of an untitled chat

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

* cmux-next: an untitled chat's Subagent of pill names the chat

With no title and no name yet, the pill read "Subagent of" with nothing
after it. It now names the chat as the pane's header and label do.

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

* cmux-next: the Subagent of test expects the chat list's name, generated names included

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

* cmux-next: the Subagent of pill names the chat as the chat list does

acpmux names an untitled session after its agent (claude-2), and the pill
showed that while the chat list showed the first prompt or New chat. The
pill now reads sessionTitle, as the list does. A parent subagent with an
empty name falls back to the chat too.

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

---------

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

* cmux-next: answered and cancelled questions leave a record in the transcript (#22531)

* cmux-next: failing test for answered and cancelled question records in the transcript

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

* cmux-next: an answered or cancelled question leaves a record in the transcript

Once a question was answered its card went and nothing in the chat said
what was chosen. The decision acpmux logs (permission_decision) now draws
a compact record where the card was: "Auth method: OAuth 2.0", or "Not
answered: ..." for one cancelled or declined. It renders through the
card's own answered and cancelled states, stays out of the turn's Worked
for fold, and comes back on a reload because both records are in the log.

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

* cmux-next: question record tests for work after the answer and comma-joined Claude answers

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

* cmux-next: work after an answer draws below its record; Claude answers split only for multi-select

The record left the open activity row and reply segment open, so the next
tool call joined the AskUserQuestion's row above the record. The record now
closes them, as a background task's line does. A single-select Claude
answer is read whole, Other text included, and a multi-select is read
longest label first, so a label with a comma stays one choice.

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

* cmux-next: failing test for a resync that replays a question's answer

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

* cmux-next: a resync that replays a question's answer leaves the reply streaming whole

After a lag the pane rebuilds the transcript, then replays the live
records after its cursor. The replayed answer found its record drawn
already but closed the stream again, so the reply streaming on split in
two. A record drawn already is now left as it is.

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

---------

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

* cmux NEXT Settings: the Chiefs card names each Chief's backend as Home does (#22420)

* test(settings): Chiefs card names a Chief's backend as Home does

Red on the base: the row prints the record's raw harness id ("codex",
"default") where Home's chip and the Chief editor say Codex and Claude.

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

* fix(settings): Chiefs card names a Chief's backend as Home does

The row printed the record's raw harness id and fell back to Default
whenever no harness was set, even with a model chosen. It now uses
chiefBackendLabel (absent or "default" is the default engine) and
shows the model and effort whenever any engine field is set.

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

* fix(settings): a Chiefs row with only "default" named reads Default

Review follow-up: {harness: "default"} and no engine at all mean the
same, so both read "Default (Chief's usual)"; a model with no harness
still names Claude. Adds the no-harness, model-only case the PR leads
with.

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

---------

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

* cmux-next Settings: tabbing through the background picker previews only the tile you stop on (#22441)

* test(settings): tabbing across background tiles previews only the one you rest on

Red on the base: keyboard focus previews each tile at once (no dwell),
so Tab across the grid repaints every window per tile.

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

* fix(settings): keyboard focus on a background tile previews after the dwell

Focus previewed at once, so Tab across the grid repainted every window
and loaded full-size art per tile; it now waits the 150 ms the pointer
waits, and moving on cancels it.

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

* test(settings): the rested tile is the one previewed; docs name the focus dwell

Review follow-up.

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

---------

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

* cmux NEXT Chief Settings: clearing the name shows the Chief's name again (#22445)

* test(home): clearing the Chief's name in Chief Settings shows it again

Red on the base: leaving the field blank renames nothing but leaves the
field empty, so the card shows no name for a Chief that kept its own.

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

* fix(home): a blank Chief name shows the kept name again

Leaving the name field blank renamed nothing but left it empty. It now
shows the name the Chief keeps, and a saved name shows trimmed.

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

* chore(next): PR autofix at b1d9352299f1

- schemas/defaults/cmux-next.json

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

---------

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 Settings: This Workspace's Opacity over Art shows only over a picture (#22454)

* test(settings): This Workspace's Opacity over Art shows only over a picture

Red on the base: the slider shows for a workspace with No wallpaper, or
one following an app-wide None, where the window paints no tint over art
and the slider changes nothing.

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

* fix(settings): Opacity over Art shows only while the workspace shows a picture

The window tints a workspace only over its picture, so with No wallpaper
(its own, or the All Workspaces None it follows) the slider changed
nothing. It now shows only while the workspace has a picture.

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

* fix(settings): a following workspace keeps Opacity over Art

Review follow-up: a workspace that follows an app-wide None can still
show a window's own picture (appearance.set scope=window), which the page
does not see, and its tint applies there. Only the workspace's own No
wallpaper hides the slider.

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

---------

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

* cmux NEXT Chief Settings: why a change did not take stays said, and VoiceOver hears it (#22494)

* test(home): Chief Settings harness picks reset model and effort; a refused pick reverts

Red on the base: a harness pick writes only the harness, so the old
harness's model stays chosen, and a pick the brain refuses stays
selected in its popup.

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

* fix(home): a Chief Settings harness pick resets model and effort; a refused pick reverts

The popover sent only the harness, and the brain keeps a field it is not
sent, so a Claude model stayed chosen after a switch to Codex. The Chief
editor's backend picker clears both; the popover now sends the harness
with model and effort cleared in one chief.engine.set
(HomeChiefEngineSource.set takes several fields). A pick the brain
refuses shows its last answer again instead of leaving the refused value
selected.

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

* fix(home): picking the harness Default runs keeps the model and effort

Review follow-up: compare a harness pick with the harness the turns run
on, so naming the harness Default already runs is not a change. Drops
the unused single-field set convenience, and the refused-pick test no
longer races the card's own first read on the failure note.

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

* chore(next): PR autofix at 2dde177073f0

- Packages/macOS/CmuxNext/ci-target-graph.json
- schemas/defaults/cmux-next.json

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

* test(home): a refused Chief Settings pick stays said through a refresh

Red on the base: the engine watch refreshes the card on every typing or
transcript change, and each refresh hides the note, so a refused pick's
reason flashes and is gone.

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

* fix(home): Chief Settings keeps the note about a refused change, and VoiceOver says it

A refresh (the engine watch runs one on every typing or transcript
change) hid the note, so a refused pick's reason flashed and was gone,
and a picture that could not be used was never seen: its open panel had
closed the card. The note from the person's own change now stays until
their next change or another Chief, a picture failure also beeps, and
every note is announced to VoiceOver.

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

* refactor(home): Chief Settings' engine rows move to HomeChiefSettingsView+Engine.swift

check-no-godfiles: the view's file is over its 400-line budget on the
base (402) and this PR grew it; the engine section (read, pick, say,
show, the pickers) moves to its own extension file unchanged.

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

* fix(home): Chief Settings announces a note once, and closing the card clears it

Review follow-ups: a brain that still does not answer no longer
re-announces its note on every refresh; a closed card's announcement
comes from the main window; closing the card clears a change's note (a
picture's, set after its open panel closed the card, waits for the next
open); a late pick for the Chief's other host is not shown.

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

* chore(next): PR autofix at c5890b6f3f27

- Packages/macOS/CmuxNext/ci-target-graph.json
- schemas/defaults/cmux-next.json

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

---------

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 Chief Settings: effort levels by name, plural counts, one Diagnostics control (#22503)

* test(home): Chief Settings names effort levels, counts in plural, one Diagnostics control

Red on the base: efforts show as the brain's ids ("xhigh"), the last
turn reads "1 tool calls", and VoiceOver and Tab find Diagnostics twice
(the triangle and its title).

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

* fix(home): Chief Settings names effort levels, counts in plural, one Diagnostics control

Effort pickers showed the brain's ids ("xhigh"); they show names, with
an unknown id as it is. The last turn read "1 tool calls" with an
English $ and %; the counts are plural strings and the numbers follow
the reader's locale. The Diagnostics title was a second VoiceOver and
Tab stop beside its triangle; the triangle is the one control. All new
strings are translated in the Home catalog's 21 locales.

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

* fix(home): review follow-ups for the Chief Settings words

German and French write Minimal the same as English (identity record);
ja Extra High is no longer the same as Max; es one tool call is
singular; ru and uk failures read N with an error; the cache share
rounds half up as before.

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

---------

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

* cmux-next palette: the Background row names your own image or a macOS wallpaper (#22512)

* test(settings): the palette's Background row names an image or wallpaper it does not list

Red on the base: your own image or a macOS wallpaper marks no option
current, so the row shows the raw value in quotes, and a retired
painting id marks nothing current either.

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

* fix(settings): the palette's Background row names what the window shows

Your own image or a macOS wallpaper is one more option, current, by its
name (BackdropSelection.title), instead of the raw value in quotes; a
retired painting id marks the work that replaced it.

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

* chore(next): PR autofix at a99856264938

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

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

---------

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 Chief Dock: keyboard and VoiceOver fold it cleanly, the mascot's hover stops flickering (#22422)

* test(chief-dock): folded cards take no Tab stop, controls name what they do, Escape folds

Red at feat-cmux-next: 5 of 7 fail (folded card actions are Tab stops,
rail and close labels repeat Agent Chat/New chat/Move to tab, Escape does
nothing, the badge stops at the four drawn cards).

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

* fix(chief-dock): keyboard and VoiceOver fold the dock cleanly, the mascot's hover stops flickering

Folded, the deck is inert, so its invisible Move to tab and close buttons
are no longer Tab stops that pop their actions over the folded cards.
Escape folds an open dock and puts focus back on the mascot. The rail's
toggle is one Detach button with aria-pressed, the chevron says Expand or
Collapse with aria-expanded, and a card's close says Collapse, instead of
three controls sharing Agent Chat, New chat and Move to tab. The badge
counts every chat that waits, not only the four drawn. The hover lift is on
the mascot image, so the button's hit box no longer moves out from under
a pointer at its bottom edge.

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

* fix(chief-dock): a card's close hands focus to the mascot; a modified Escape is left alone

Review follow-ups: the close button folded the deck with focus still on
it, inside the now inert cards, so WebKit dropped focus to the page.
Escape skips Cmd/Ctrl/Option/Shift and IME composition as
useEscapeToDismiss does. Drops the mascot button's dead transform
transition, names translate and scale in will-change, and uses Abdocken
(de) and Desacoplar (pt-BR) for Detach.

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

* fix(chief-dock): Escape goes through ui/KeyboardScope, not a raw key handler

ui-rules: page code outside src/ui has no raw key handlers. The dock's
controls sit in a KeyboardScope (display: contents, so the layout is
unchanged) whose capture handler folds the dock on Escape.

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

---------

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

* cmux NEXT Chief pet: VoiceOver can press the character and each notice (#22432)

* test(chief-pet): VoiceOver presses the character and the notices

Expected red at feat-cmux-next: the character is an image with no press
action, and every capsule of a folded deck reads as a button although
only the front one takes a click and none of them presses.

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

* fix(chief-pet): VoiceOver can press the character and each notice

The panel never takes the keys, so VoiceOver's press is the only way in
that is not the pointer, and neither the character (an image) nor a
notice capsule (a button with no press action) took one. The character
is now a button whose press is a click on it (the reply box), and a
capsule's press opens its notice. A folded deck reads as its front
notice only, the one a click can open; unfolded, every notice reads.

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

* chore(next): PR autofix at 54b0aeca15c6

- schemas/defaults/cmux-next.json

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

---------

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 New Tab: a page you switch back to replays no recent-chat entrance (#22464)

* cmux NEXT New Tab: failing test, switching away and back replays recent chats' entrance

pageSwitch (webviews/test/support) hides and shows the page, counts fetches
and entrance animations. A chat list change that lands as the page shows
again fades and slides its cards on the first frame back.

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

* cmux NEXT New Tab: a page you switch back to starts no entrance for changes made while away

shared/pageReveal.ts owns "may an entrance run now": not while the page is
hidden, and not until its first frame back has passed. useListMotion asks it,
so a chat list change that lands as the page shows again takes its places at
once. The page entry installs the settle, which finishes every finite
animation when the page hides and when it shows.

plans/cmux-next/shared-projections.md writes down the pattern: one projection
per data source above the tab, snapshot rendering, no refetch on remount or
switch, and entrances on data-change diffs only.

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

* cmux NEXT New Tab: review follow-ups for the switch-back helper

The animate stub carries finished, id and currentTime, so Web Animations code
that awaits finished can use the helper. An attribute change counts as an
entrance only when the changed attribute is what starts it. dispose removes
the visibility getters. The motion-off test no longer asserts on the global
document.

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

---------

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

* Run Settings and palette agent updates on their captured owner (#21447)

* acpmux: require version evidence for already-current updates

Replays #21398's change onto current feat-cmux-next as one commit.

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

* agents: refresh inventory versions without probing or updating

Replays #21401's change onto current feat-cmux-next as one commit.

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

* agents: connect explicit version refresh to the selected owner

Replays #21402's change onto current feat-cmux-next as one commit.

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

* Add owner update terminal jobs with durable replay

Replays #21414's change onto current feat-cmux-next as one commit. Keeps the crash ratchet flat: poisoned locks are recovered instead of unwrapped (only a held job lock is busy), and the job status parses its decimal strings with radix: 10.

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

* Preserve app mutation keys through shared action runs

Replays #21429's change onto current feat-cmux-next as one commit, written without growing CmuxNextApp's source lines (size cap).

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

* Preserve app callers through the shared action registry

Replays #21436's change onto current feat-cmux-next as one commit. ControlRequest takes callerApp in its init so CmuxNextApp's source lines don't grow (size cap).

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

* Share agent CLI update UI across Settings and actions

Replays #21447's change onto current feat-cmux-next as one commit, together
with its follow-up fix.

Settings and the palette run agent updates on their captured owner route
instead of typing a health argv into a focused pane. The update owner
prepares only npm and native installations, and refuses a family when any
of its rows is another channel or already current, so both surfaces submit
only those families and Settings shows Update only for rows whose channel
the owner runs.

A start the owner refuses (agent_cli_job.*, answered before it journals a
creation) leaves no "unknown" job that blocks that CLI. One family's failure
does not stop the rest of a batch; the batch reports every failure. A lost
reply still keeps its unknown job, since the terminal may exist.

Update, Update all and turning Update Automatically on still need a gesture
or a confirmed user, as #21439 requires.

The update action, its owner route and the health-row reader live in a new
CmuxNextAgentUpdates module, leaving thin app glue in CmuxNextApp.

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

* Require trusted page context for Settings agent updates

Replays #21439's change onto current feat-cmux-next as one commit. Turning Update Automatically on now needs a gesture or native confirmation too, since acpmux runs the update after its next check (review finding); SettingsHarnesses is compacted so CmuxNextApp's source lines don't grow (size cap). The health read defaults to the named AcpmuxEnvironment.harnessHealth rather than an async closure literal (crash ratchet).

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

* chore(next): PR autofix at 0aae74c95fc8

- regenerated plans/cmux-next/action-surfaces.json
- regenerated schemas/defaults/cmux-next.json
- vp fmt webviews/src/agent-session/acpmux/replyHref.ts
- vp fmt webviews/src/pages/markdown/links.ts
- vp fmt webviews/test/markdown-cmux-app-links.test.ts

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

* Bind the Settings update scope with withValueInTask

TaskLocal.withValue frees its marker with the task allocator even when no
task is current (cx-bpcj); the crash-class gate bans it. Use the shared
withValueInTask helper, as ActionRunScope does.

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

* chore(next): PR autofix at 45adaef20fc3

- regenerated plans/cmux-next/action-surfaces.json
- regenerated web/data/cmux-shortcuts.generated.json
- regenerated cmux-tui/crates/cmux-app-host/generated/cmux-app.d.ts
- regenerated schemas/defaults/cmux-next.json

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

* test(next): agent CLI updates survive reconnects, unknown exits, owner mismatches and palette failures

Regression tests for four defects in the shared agent CLI update action:
a reconnect to the same owner hides and blocks the job, a new daemon
session leaves a job that blocks the CLI forever, an exit with an unknown
outcome blocks the CLI forever, a receipt for another owner leaves a
blocking job, and a palette run's failure is never shown.

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

* fix(next): agent CLI update jobs follow their owner across reconnects and settle on any end

- Showing, refreshing and blocking now share one owner rule: a job whose
  route went stale is re-captured when the app still routes the same
  machine and session (its receipts stay observable after a reconnect),
  and otherwise neither shows nor blocks the CLI.
- An exit with an unknown process result stays state "exited" with
  outcome "unknown", so it settles instead of blocking forever.
- A receipt for another owner drops its job before the error.
- A palette run (no caller capturing work) shows its failure through the
  registry's refusal instead of dropping it.
- Nothing-to-update and still-updating refusals get their own localized
  text instead of the generic add/remove agents message.

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

* test(next): agent update survives a reconnect mid-batch and a lost start reply

A reconnect while one family's start waits must not start the next family on
the re-captured owner and leave it with a job that blocks that CLI. A lost
start reply must be replayed by its key on refresh, so the job gets its
receipt or the owner's refusal clears it.

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

* fix(next): agent update start keeps its captured owner and replays lost replies

start() re-read intent.route after every await, while showing or refreshing
the jobs can swap it for a re-capture after a reconnect. A batch then went
on to start the next family on the new connection, whose route had no open
carrier, so that family kept an unknown job with no receipt that blocked its
CLI for good. start() now captures the route once and stops when it retires,
and a route opens its carrier on start as well as on inventory and observe.

A lost start reply left an unknown job that blocked the CLI until restart.
The intent keeps the child key, and refresh sends it again on the current
owner: the owner replays the key, so the job gets its receipt, or its
refusal clears the job.

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

* test(next): a busy answer to a replayed agent CLI update start keeps its job

The owner answers agent_cli_job.busy before it looks the key up, so on a
replay busy usually means the key's original request is still running.

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

* fix(next): a busy answer to a replayed agent CLI update start keeps its job

Review of #21447: refresh treated agent_cli_job.busy on a replay as the
key creating nothing, dropping the job while its original request could
still create the terminal, which then went untracked and unblocked.

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

* chore(next): PR autofix at 99a29e6f4d3a

- plans/cmux-next/action-surfaces.json
- plans/cmux-next/actions.md

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

* chore(next): PR autofix at 3b5a7cad2bea

- Packages/macOS/CmuxNext/ci-target-graph.json
- schemas/defaults/cmux-next.json

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

* test(next): settled owner failures and ended jobs never block agent CLI updates (red)

Regressions for the #21447 review:

- The owner journals a failed creation under its key and answers every
  replay with the same failure (operation.failed, mutation.indeterminate).
  Such an answer, on the first start or on a replay of a lost reply,
  must settle the job instead of leaving it unknown and blocking the CLI
  for the rest of the session.
- A job that already ended must not block the next update of its CLI
  just because nothing refreshed Settings > Agents in between; a job
  that still runs keeps refusing it.

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

* fix(next): settle agent CLI updates the owner answered, and check ended jobs before refusing

- Any coded owner answer to a start settles its key. agent_cli_job.*
  comes before a creation is journaled; any other code (a failed
  creation, operation.failed, or an interrupted one,
  mutation.indeterminate) is the key's journaled outcome and the owner
  answers every replay the same way. Such an answer, on the first start
  or on a replay, now drops the job instead of leaving it unknown and
  blocking the CLI for the rest of the session. Only a failure with no
  owner answer (a lost reply) is replayed; busy on a replay still keeps
  the job.
- A start that finds an earlier job of the same CLI still unended
  refreshes once before refusing, so a job that already exited no longer
  blocks the next palette or Settings update until someone opens
  Settings > Agents. A job that still runs keeps refusing.

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

* test(next): a coded answer to a replayed agent CLI update start keeps the lost job (red)

Regression for the delta review of 0ad8305a5bbf: a replay can get a coded
answer the owner sends before it reads its journal (daemon_shutdown_pending
during a handoff, operation.failed from a registry error), while the
lost first start may have created the update terminal. Dropping the job
then hides a running update and unblocks a second one of the same CLI.

A first start's fresh key has nothing running, so any coded answer there
still settles; its cases are now operation.failed and
daemon_shutdown_pending, with a fixture message that matches.

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

* fix(next): only a refusal settles a replayed agent CLI update start

A replay of a lost start now drops its job only on an agent_cli_job.*
refusal, as before 0ad8305a5bbf. Another coded answer can come before the
owner reads its journal (daemon_shutdown_pending during a handoff, or a
registry error mapped to operation.failed) while the lost first start
created the terminal, so the job keeps blocking its CLI and the next
refresh replays the same key. A first start's fresh key still settles on
any coded answer, which is what keeps a failed creation from blocking the
CLI for the session.

A lost reply whose creation then failed is still replayed until the
owner can say so; #21469's journal resolve settles that case.

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

* test(next): an indeterminate first agent CLI update start keeps blocking its CLI (red)

From the second delta review: the owner answers a fresh key with
mutation.indeterminate when a failed creation left its reserved terminal
possibly live (AmbiguousLive in creation_settlement.rs). Dropping that
job would let a second update of the same CLI start beside it.

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

* fix(next): an indeterminate first agent CLI update start keeps its job

`answered` no longer settles mutation.indeterminate: the owner says a
failed creation's effect may have run, so the job stays unknown and keeps
blocking its CLI, like a lost reply, until it is replayed or its owner
session retires it. The comments no longer claim a coded first-start
answer always means nothing runs.

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>

* cmux-next: 512 classic workspaces leave no terminal without a PTY, failing first

The 512-workspace simulation now allows no "PTY capacity exhausted"
failure (about 40-53 today: macOS hands out at most kern.tty.ptmx_max,
511, PTYs), and a new simulation checks that a workspace that came over
in the background has its terminal tabs parked (dead, relaunch.on_show)
and that showing it starts them in the same tabs. Red: the test, the
park-tabs request and capability (tab-park-v1) and TabRelaunch.onShow;
no daemon serves park-tabs and the migration parks nothing.

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

* cmux-next: classic workspaces in the background come over parked; each terminal starts on first show

macOS hands out at most kern.tty.ptmx_max (511) PTYs, shared with every
process, so a classic session at its limits (512 workspaces) left 40-53
workspaces with no terminal. Now:
- cmux-tui: park-tabs (tab-park-v1) ends the shells of terminal tabs and
  keeps the tabs as keep-layout tabs, recorded first; the kept_tabs row
  gets on_show (additive column), and relaunch reads {cwd, on_show: true}.
- The migration parks each workspace it makes that is not its window's
  selected one, once its terminals are in the store. A workspace whose
  classic terminals carry scrollback or an agent keeps running: its shell
  prints the scrollback at its first prompt.
- A window that shows a workspace restarts its parked tabs in place
  (restart-tab, DaemonService.wakeParkedTabs); the connect-time kept-tab
  relaunch skips them, and they draw as the terminals they become.

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

* cmux-next: wakeParkedTabs imports CmuxNextDaemon publicly (its parameter is WorkspaceModel)

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

* cmux-next: only the launch's classic run parks; the merge test waits for the new workspace's group

Import Workspaces from Classic cmux is the person asking now: its
terminals open live, as before (ClassicImportTests type into them). The
re-upgrade test read the tree before the daemon's event with the merged
workspace's group landed; it now waits for it, up to 5 s.

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

* cmux-next: tab-park-v1 is listed as an optional daemon capability

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

* fix(cmux-next): put new chrome fonts on the type scale; Chromium swipe arrows (#22483)

* fix(cmux-next): put new chrome fonts on the type scale; Chromium swipe arrows

The Screenshot section hint and the Home sidebar's Requests button use
TypeScale steps and the emphasis weight instead of 13/11 pt medium; the
file grid's duration badge uses the scale's semibold instead of medium.
Sleepy Mode's scene (a 96 pt moon, a 64 pt thin clock, animated Zs)
is illustration, not chrome, so its four raw values are baselined.

The swipe-navigation pill draws Chrome's toolbar Back/Forward icons from
the pack instead of SF Symbol arrows.

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

* chore(next): PR autofix at 9255418df048

- schemas/defaults/cmux-next.json

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

* fix(cmux-next): review fixes: skip Sleepy Mode's scene, size the swipe arrows

- The type-scale ratchet skips SleepySceneView.swift as illustration
  (like the DEBUG text probes) instead of raising its baseline rows,
  which the ratchet never allows.
- Chrome's arrow path spans about 60% of its box, so the swipe pill
  draws it at 22/24 pt to keep the old 14/16 pt SF arrows' size.
- schemas/defaults/cmux-next.json goes back to the base: the autofix
  line belongs to the remote-hosts lane's reservation.

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>

* docs: merge on a passing review, not a person's approval (#22560)

* docs: merge on a passing review, not a person's approval

Agents were holding reviewed perf and UI PRs for a day waiting on the
"into main, explicit approval after dogfood or a direct merge directive"
rule. On either branch a PR now merges when an independent review
subagent passes it and it causes no red of its own; base reds, flakes
and pending checks don't hold it. Risky user-visible behavior ships
behind a setting or Debug toggle.

A red base is now priority zero to fix directly. The risk:high tier,
merge freezes, never --admin, gh-merge-green on main and ready-to-land
on feat-cmux-next stay as they were.

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

* docs: keep gh-merge-green's gate on main and the take-back hold

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: Leo <cheerleaderleo@outlook.com>
Co-authored-by: Abdulaziz Albahar <67667005+azooz2003-bit@users.noreply.github.com>
Co-authored-by: cmux-autofix[bot] <cmux-autofix[bot]@users.noreply.github.com>
Co-authored-by: lawrencecchen <54008264+lawrencecchen@users.noreply.github.com>
teamleaderleo added a commit that referenced this pull request Oct 20, 2026
* acpmux: require version evidence for already-current updates

Replays #21398's change onto current feat-cmux-next as one commit.

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

* agents: refresh inventory versions without probing or updating

Replays #21401's change onto current feat-cmux-next as one commit.

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

* agents: connect explicit version refresh to the selected owner

Replays #21402's change onto current feat-cmux-next as one commit.

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

* Add owner update terminal jobs with durable replay

Replays #21414's change onto current feat-cmux-next as one commit. Keeps the crash ratchet flat: poisoned locks are recovered instead of unwrapped (only a held job lock is busy), and the job status parses its decimal strings with radix: 10.

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

* Preserve app mutation keys through shared action runs

Replays #21429's change onto current feat-cmux-next as one commit, written without growing CmuxNextApp's source lines (size cap).

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

* Preserve app callers through the shared action registry

Replays #21436's change onto current feat-cmux-next as one commit. ControlRequest takes callerApp in its init so CmuxNextApp's source lines don't grow (size cap).

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

* Share agent CLI update UI across Settings and actions

Replays #21447's change onto current feat-cmux-next as one commit, together
with its follow-up fix.

Settings and the palette run agent updates on their captured owner route
instead of typing a health argv into a focused pane. The update owner
prepares only npm and native installations, and refuses a family when any
of its rows is another channel or already current, so both surfaces submit
only those families and Settings shows Update only for rows whose channel
the owner runs.

A start the owner refuses (agent_cli_job.*, answered before it journals a
creation) leaves no "unknown" job that blocks that CLI. One family's failure
does not stop the rest of a batch; the batch reports every failure. A lost
reply still keeps its unknown job, since the terminal may exist.

Update, Update all and turning Update Automatically on still need a gesture
or a confirmed user, as #21439 requires.

The update action, its owner route and the health-row reader live in a new
CmuxNextAgentUpdates module, leaving thin app glue in CmuxNextApp.

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

* Recover agent updates across app restarts

Replays #21469's change onto current feat-cmux-next as one commit, on top
of the owner-channel fix, with these changes so a retained intent always
has a way out:

- A start the owner refuses before journaling (agent_cli_job.*) settles
  its intent, in a batch and on Resume, instead of leaving a Resume that
  is refused again.
- Any terminal exit settles, including one whose outcome is unknown, and
  a failed creation state settles on recovery.
- A person may dismiss an unstarted (not_found, prepared) or indeterminate
  intent from Settings; dismissing stops nothing. Pending stays fenced.
  Resume and Dismiss need a gesture, like Update.
- The Rust ledger gains agent-cli-intents-settle, which deletes rows.
  Recording drops rows older than seven days and keeps the newest 256 per
  actor instead of failing at a cap.

Recovery, the ledger port and the owner route live in the
CmuxNextAgentUpdates module; the app keeps only its daemon glue.

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

* Require trusted page context for Settings agent updates

Replays #21439's change onto current feat-cmux-next as one commit. Turning Update Automatically on now needs a gesture or native confirmation too, since acpmux runs the update after its next check (review finding); SettingsHarnesses is compacted so CmuxNextApp's source lines don't grow (size cap). The health read defaults to the named AcpmuxEnvironment.harnessHealth rather than an async closure literal (crash ratchet).

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

* Let every retained agent update end

Review follow-ups on #21469, so a retained intent always has a way out.

- A closed update terminal settles its job. terminal.wait_exit answers
  terminal.closed, then selector.not_found for the tombstoned terminal; both
  mean the process ended. A missing session or connection still keeps it.
- Ledger reads apply the seven-day age bound, not only recording, which a
  blocked family never reaches.
- A row from a session no route can reach (a reinstalled server) no longer
  blocks the current session's update; it waits out the age bound. Rows of
  the same session, or of a session recovery reattached, still block.
- A failed record clears the batch's jobs, since nothing started.
- A creation still pending ten minutes after a recovery first saw it may be
  dismissed.
- agent_cli_job.busy no longer settles: the owner may be running that key.
- A test covers adding recorded_at to a ledger created without it.

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

* chore(next): PR autofix at 0aae74c

- regenerated plans/cmux-next/action-surfaces.json
- regenerated schemas/defaults/cmux-next.json
- vp fmt webviews/src/agent-session/acpmux/replyHref.ts
- vp fmt webviews/src/pages/markdown/links.ts
- vp fmt webviews/test/markdown-cmux-app-links.test.ts

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

* Keep other sessions' agent update rows blocking

A row from a session recovery cannot reach blocks the CLI on that machine
again. "No current route" cannot tell a dead session from a second live
server on the same machine that may still be running the updater, so
unblocking it could start two global installs at once. The ledger's
seven-day read bound retires true orphans.

Also notes at the closed-terminal settle that Rust answers terminal.closed
before it queues the runtime signal, so the updater may still be dying in
its close grace.

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

* chore(next): PR autofix at 953137a

- vp fmt webviews/src/pages/markdown/README.md
- cargo fmt in cmux-tui

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

* Bind the Settings update scope with withValueInTask

TaskLocal.withValue frees its marker with the task allocator even when no
task is current (cx-bpcj); the crash-class gate bans it. Use the shared
withValueInTask helper, as ActionRunScope does.

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

* chore(next): PR autofix at 45adaef

- regenerated plans/cmux-next/action-surfaces.json
- regenerated web/data/cmux-shortcuts.generated.json
- regenerated cmux-tui/crates/cmux-app-host/generated/cmux-app.d.ts
- regenerated schemas/defaults/cmux-next.json

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

* chore(next): PR autofix at 1ee01b5

- regenerated plans/cmux-next/action-surfaces.json
- regenerated web/data/cmux-shortcuts.generated.json
- regenerated cmux-tui/crates/cmux-app-host/generated/cmux-app.d.ts
- regenerated schemas/defaults/cmux-next.json

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

* Translate the agent update intent argument into every locale

check-l10n flagged argument.agent.harness.intent as missing 12 of the 21 locales.

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

* test(next): agent CLI updates survive reconnects, unknown exits, owner mismatches and palette failures

Regression tests for four defects in the shared agent CLI update action:
a reconnect to the same owner hides and blocks the job, a new daemon
session leaves a job that blocks the CLI forever, an exit with an unknown
outcome blocks the CLI forever, a receipt for another owner leaves a
blocking job, and a palette run's failure is never shown.

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

* fix(next): agent CLI update jobs follow their owner across reconnects and settle on any end

- Showing, refreshing and blocking now share one owner rule: a job whose
  route went stale is re-captured when the app still routes the same
  machine and session (its receipts stay observable after a reconnect),
  and otherwise neither shows nor blocks the CLI.
- An exit with an unknown process result stays state "exited" with
  outcome "unknown", so it settles instead of blocking forever.
- A receipt for another owner drops its job before the error.
- A palette run (no caller capturing work) shows its failure through the
  registry's refusal instead of dropping it.
- Nothing-to-update and still-updating refusals get their own localized
  text instead of the generic add/remove agents message.

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

* test(next): agent update survives a reconnect mid-batch and a lost start reply

A reconnect while one family's start waits must not start the next family on
the re-captured owner and leave it with a job that blocks that CLI. A lost
start reply must be replayed by its key on refresh, so the job gets its
receipt or the owner's refusal clears it.

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

* fix(next): agent update start keeps its captured owner and replays lost replies

start() re-read intent.route after every await, while showing or refreshing
the jobs can swap it for a re-capture after a reconnect. A batch then went
on to start the next family on the new connection, whose route had no open
carrier, so that family kept an unknown job with no receipt that blocked its
CLI for good. start() now captures the route once and stops when it retires,
and a route opens its carrier on start as well as on inventory and observe.

A lost start reply left an unknown job that blocked the CLI until restart.
The intent keeps the child key, and refresh sends it again on the current
owner: the owner replays the key, so the job gets its receipt, or its
refusal clears the job.

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

* test(next): a busy answer to a replayed agent CLI update start keeps its job

The owner answers agent_cli_job.busy before it looks the key up, so on a
replay busy usually means the key's original request is still running.

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

* fix(next): a busy answer to a replayed agent CLI update start keeps its job

Review of #21447: refresh treated agent_cli_job.busy on a replay as the
key creating nothing, dropping the job while its original request could
still create the terminal, which then went untracked and unblocked.

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

* test(next): retained agent update rows no owner can reach are shown and dismissable (red)

Regressions for the #21469 review:

- A ledger row whose machine/session no route can capture blocks that CLI on
  that machine for up to seven days, but state never shows it and dismiss
  cannot reach it. The tests expect it shown as state "unknown" with
  can_dismiss, a gesture-only dismiss that deletes the row, and the block
  lifting after it (recovery, actions and the AppServices journey).
- A failed receipt attach after a successful start aborts the whole batch;
  the test expects it reported while the other families still start.
- Rust: one ledger row that fails validation fails the whole list; the test
  expects the bad rows skipped and the good one returned.

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

* fix(next): show and let a person dismiss agent update rows no owner can reach

A retained ledger row whose machine/session no route captures blocked that
CLI on that machine for up to seven days while state never showed it and
dismiss could not reach it.

- state now lists, after the jobs of reachable intents, every unsettled
  retained row none of them shows, as state "unknown" with can_dismiss.
  What start blocks on is therefore always shown.
- dismiss(key) on such a row settles it locally (a ledger delete, plus any
  hidden in-memory job), still behind a gesture; it stops nothing.
- An unreadable ledger clears the retained rows instead of showing stale ones.
- A failed receipt attach after a successful start is reported with the
  batch's other failures instead of aborting the remaining families.
- Rust: read_rows skips and logs a row that fails validation instead of
  failing the whole list.
- rustfmt the merged agent_cli_job.rs lock line.

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

* test(next): busy first starts, dismiss during a replay, and resolve-then-replay (red)

Regressions for the #21469 review at 217ba19:

- A first start the owner answers busy created nothing (busy comes before
  the key lookup), but its row was kept: it blocked a retry click and a later
  refresh started the update after the person saw a failure.
- A dismiss while a replay is in flight deleted the row the replayed
  receipt then could not attach to, leaving an untracked updater.
- A lost reply whose key the owner has nothing for (not_found, prepared) was
  replayed by refresh with no fresh gesture instead of offering Resume.
- A batch that stops early should name the families it never sent.

The replayed-row test now uses an owner that cannot resolve the key, where
the replay is the only path.

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

* chore(next): PR autofix at 99a29e6

- plans/cmux-next/action-surfaces.json
- plans/cmux-next/actions.md

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

* fix(next): settle busy first starts, hold replays against dismiss, and resume after not_found

- A first start the owner answers busy settles. The owner answers busy from its
  inventory reservation or execution lock before it looks up the key, and send()
  only ever sends a fresh key, so nothing was created for it. Busy still keeps
  the row on a replay or resume, where the key may really be in flight.
- A replay holds its intent busy, so a dismiss mid-replay is refused instead of
  deleting the row the replayed receipt attaches to. Settling a key with its
  intent also drops it from the unsent replays.
- When resolve answers not_found or prepared for a lost reply, refresh no longer
  replays it: replaying is an execution request with no fresh gesture. The row
  offers Resume. Keys resolve cannot answer are still replayed.
- A batch that stops early names every family it never sent in its failure.

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

* chore(next): PR autofix at 3b5a7ca

- Packages/macOS/CmuxNext/ci-target-graph.json
- schemas/defaults/cmux-next.json

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

* test(next): settled owner failures and ended jobs never block agent CLI updates (red)

Regressions for the #21447 review:

- The owner journals a failed creation under its key and answers every
  replay with the same failure (operation.failed, mutation.indeterminate).
  Such an answer, on the first start or on a replay of a lost reply,
  must settle the job instead of leaving it unknown and blocking the CLI
  for the rest of the session.
- A job that already ended must not block the next update of its CLI
  just because nothing refreshed Settings > Agents in between; a job
  that still runs keeps refusing it.

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

* fix(next): settle agent CLI updates the owner answered, and check ended jobs before refusing

- Any coded owner answer to a start settles its key. agent_cli_job.*
  comes before a creation is journaled; any other code (a failed
  creation, operation.failed, or an interrupted one,
  mutation.indeterminate) is the key's journaled outcome and the owner
  answers every replay the same way. Such an answer, on the first start
  or on a replay, now drops the job instead of leaving it unknown and
  blocking the CLI for the rest of the session. Only a failure with no
  owner answer (a lost reply) is replayed; busy on a replay still keeps
  the job.
- A start that finds an earlier job of the same CLI still unended
  refreshes once before refusing, so a job that already exited no longer
  blocks the next palette or Settings update until someone opens
  Settings > Agents. A job that still runs keeps refusing.

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

* test(next): a coded answer to a replayed agent CLI update start keeps the lost job (red)

Regression for the delta review of 0ad8305: a replay can get a coded
answer the owner sends before it reads its journal (daemon_shutdown_pending
during a handoff, operation.failed from a registry error), while the
lost first start may have created the update terminal. Dropping the job
then hides a running update and unblocks a second one of the same CLI.

A first start's fresh key has nothing running, so any coded answer there
still settles; its cases are now operation.failed and
daemon_shutdown_pending, with a fixture message that matches.

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

* fix(next): only a refusal settles a replayed agent CLI update start

A replay of a lost start now drops its job only on an agent_cli_job.*
refusal, as before 0ad8305. Another coded answer can come before the
owner reads its journal (daemon_shutdown_pending during a handoff, or a
registry error mapped to operation.failed) while the lost first start
created the terminal, so the job keeps blocking its CLI and the next
refresh replays the same key. A first start's fresh key still settles on
any coded answer, which is what keeps a failed creation from blocking the
CLI for the session.

A lost reply whose creation then failed is still replayed until the
owner can say so; #21469's journal resolve settles that case.

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

* fix(next): a first agent CLI update start settles on any owner answer

From #21447's review: a first start that the owner answers with a coded
failure other than an agent_cli_job.* refusal (operation.failed for a
failed creation, daemon_shutdown_pending during a handoff) left its
ledger row unknown and unsent. `send` now settles the intent on any
coded answer, since a first start's fresh key cannot have anything
running. Replays and resumes still settle only on a refusal: a code the
owner sends before reading its journal proves nothing there, and
recovery's resolve settles a journaled failure.

Makes #21447's aFirstStartTheOwnerAnswersWithAFailureLeavesNoBlockingJob
pass on this branch.

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

* test(next): an indeterminate first agent CLI update start keeps blocking its CLI (red)

From the second delta review: the owner answers a fresh key with
mutation.indeterminate when a failed creation left its reserved terminal
possibly live (AmbiguousLive in creation_settlement.rs). Dropping that
job would let a second update of the same CLI start beside it.

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

* fix(next): an indeterminate first agent CLI update start keeps its job

`answered` no longer settles mutation.indeterminate: the owner says a
failed creation's effect may have run, so the job stays unknown and keeps
blocking its CLI, like a lost reply, until it is replayed or its owner
session retires it. The comments no longer claim a coded first-start
answer always means nothing runs.

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

* fix(next): an indeterminate first agent CLI update start keeps its ledger row

`answered` no longer settles mutation.indeterminate: the owner says a
failed creation's effect may have run, so the row stays unknown and
blocking, and recovery's resolve lets a person dismiss it.

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

* test(next): a failed first agent CLI update start keeps its ledger row until the journal settles it (red)

From #21469's review: a coded failure other than an agent_cli_job.*
refusal can follow a durable creation (a launch activation failing after
the commit maps to operation.failed), so deleting the ledger row on such
an answer can hide a live update terminal and unblock a second one. The
row must stay until recovery's resolve reads the owner's journal; a
failed creation then settles. #21447's any-code variant of this test is
replaced here; the fake owner can now answer resolve.

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

* fix(next): a first agent CLI update start settles its ledger row only on a refusal

Reverts this branch's `answered` rule from 1db61b6 and 2506184.
A first start settles only on an agent_cli_job.* refusal or busy, as the
branch had it; any other coded answer keeps the ledger row, because it
can follow a durable creation. Recovery's resolve then reads the owner's
journal: a failed creation settles, an indeterminate one becomes
dismissable, and a created one reattaches.

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>
teamleaderleo added a commit that referenced this pull request Oct 21, 2026
* acpmux: require version evidence for already-current updates

Replays #21398's change onto current feat-cmux-next as one commit.

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

* agents: refresh inventory versions without probing or updating

Replays #21401's change onto current feat-cmux-next as one commit.

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

* agents: connect explicit version refresh to the selected owner

Replays #21402's change onto current feat-cmux-next as one commit.

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

* Add owner update terminal jobs with durable replay

Replays #21414's change onto current feat-cmux-next as one commit. Keeps the crash ratchet flat: poisoned locks are recovered instead of unwrapped (only a held job lock is busy), and the job status parses its decimal strings with radix: 10.

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

* Preserve app mutation keys through shared action runs

Replays #21429's change onto current feat-cmux-next as one commit, written without growing CmuxNextApp's source lines (size cap).

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

* Preserve app callers through the shared action registry

Replays #21436's change onto current feat-cmux-next as one commit. ControlRequest takes callerApp in its init so CmuxNextApp's source lines don't grow (size cap).

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

* Share agent CLI update UI across Settings and actions

Replays #21447's change onto current feat-cmux-next as one commit, together
with its follow-up fix.

Settings and the palette run agent updates on their captured owner route
instead of typing a health argv into a focused pane. The update owner
prepares only npm and native installations, and refuses a family when any
of its rows is another channel or already current, so both surfaces submit
only those families and Settings shows Update only for rows whose channel
the owner runs.

A start the owner refuses (agent_cli_job.*, answered before it journals a
creation) leaves no "unknown" job that blocks that CLI. One family's failure
does not stop the rest of a batch; the batch reports every failure. A lost
reply still keeps its unknown job, since the terminal may exist.

Update, Update all and turning Update Automatically on still need a gesture
or a confirmed user, as #21439 requires.

The update action, its owner route and the health-row reader live in a new
CmuxNextAgentUpdates module, leaving thin app glue in CmuxNextApp.

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

* Recover agent updates across app restarts

Replays #21469's change onto current feat-cmux-next as one commit, on top
of the owner-channel fix, with these changes so a retained intent always
has a way out:

- A start the owner refuses before journaling (agent_cli_job.*) settles
  its intent, in a batch and on Resume, instead of leaving a Resume that
  is refused again.
- Any terminal exit settles, including one whose outcome is unknown, and
  a failed creation state settles on recovery.
- A person may dismiss an unstarted (not_found, prepared) or indeterminate
  intent from Settings; dismissing stops nothing. Pending stays fenced.
  Resume and Dismiss need a gesture, like Update.
- The Rust ledger gains agent-cli-intents-settle, which deletes rows.
  Recording drops rows older than seven days and keeps the newest 256 per
  actor instead of failing at a cap.

Recovery, the ledger port and the owner route live in the
CmuxNextAgentUpdates module; the app keeps only its daemon glue.

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

* Require trusted page context for Settings agent updates

Replays #21439's change onto current feat-cmux-next as one commit. Turning Update Automatically on now needs a gesture or native confirmation too, since acpmux runs the update after its next check (review finding); SettingsHarnesses is compacted so CmuxNextApp's source lines don't grow (size cap). The health read defaults to the named AcpmuxEnvironment.harnessHealth rather than an async closure literal (crash ratchet).

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

* Show agent update outcomes automatically

Replays #21480's change onto current feat-cmux-next as one commit, on top
of the rebuilt #21469.

Settings, palette and recovered jobs follow their retained terminals.
terminal-registry-changed events from the captured control connection only
trigger a bounded read of the original receipt; they never dispatch work
or imply completion. Completed batches stop observing.

AgentCLIUpdateActions+Observation.swift lives in the CmuxNextAgentUpdates
module beside the rest of the action, and the owner route captures the
event stamp there. As in #21469, any exit, including one with an unknown
outcome, settles its intent in the ledger.

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

* Retire obsolete sessions when a paired server is reinstalled

Replays #21487's change onto current feat-cmux-next as one commit, on top
of the rebuilt #21480.

A host reinstalled behind the same link socket is a replacement owner: the
reach diff replaces its session when the paired installation changes, not
only when the route does. A closed session is retired, so callbacks that
still hold it cannot connect or wake it again.

ServerMachineSession.close() is written on one line, like disconnect(), so
CmuxNextApp gains no source lines.

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

* Let every retained agent update end

Review follow-ups on #21469, so a retained intent always has a way out.

- A closed update terminal settles its job. terminal.wait_exit answers
  terminal.closed, then selector.not_found for the tombstoned terminal; both
  mean the process ended. A missing session or connection still keeps it.
- Ledger reads apply the seven-day age bound, not only recording, which a
  blocked family never reaches.
- A row from a session no route can reach (a reinstalled server) no longer
  blocks the current session's update; it waits out the age bound. Rows of
  the same session, or of a session recovery reattached, still block.
- A failed record clears the batch's jobs, since nothing started.
- A creation still pending ten minutes after a recovery first saw it may be
  dismissed.
- agent_cli_job.busy no longer settles: the owner may be running that key.
- A test covers adding recorded_at to a ledger created without it.

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

* chore(next): PR autofix at 0aae74c

- regenerated plans/cmux-next/action-surfaces.json
- regenerated schemas/defaults/cmux-next.json
- vp fmt webviews/src/agent-session/acpmux/replyHref.ts
- vp fmt webviews/src/pages/markdown/links.ts
- vp fmt webviews/test/markdown-cmux-app-links.test.ts

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

* Keep other sessions' agent update rows blocking

A row from a session recovery cannot reach blocks the CLI on that machine
again. "No current route" cannot tell a dead session from a second live
server on the same machine that may still be running the updater, so
unblocking it could start two global installs at once. The ledger's
seven-day read bound retires true orphans.

Also notes at the closed-terminal settle that Rust answers terminal.closed
before it queues the runtime signal, so the updater may still be dying in
its close grace.

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

* chore(next): PR autofix at 953137a

- vp fmt webviews/src/pages/markdown/README.md
- cargo fmt in cmux-tui

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

* Bind the Settings update scope with withValueInTask

TaskLocal.withValue frees its marker with the task allocator even when no
task is current (cx-bpcj); the crash-class gate bans it. Use the shared
withValueInTask helper, as ActionRunScope does.

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

* chore(next): PR autofix at 45adaef

- regenerated plans/cmux-next/action-surfaces.json
- regenerated web/data/cmux-shortcuts.generated.json
- regenerated cmux-tui/crates/cmux-app-host/generated/cmux-app.d.ts
- regenerated schemas/defaults/cmux-next.json

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

* chore(next): PR autofix at a51fde3

- regenerated plans/cmux-next/action-surfaces.json
- regenerated web/data/cmux-shortcuts.generated.json
- regenerated cmux-tui/crates/cmux-app-host/generated/cmux-app.d.ts
- regenerated schemas/defaults/cmux-next.json

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

* chore(next): PR autofix at 226720e

- regenerated plans/cmux-next/action-surfaces.json
- regenerated web/data/cmux-shortcuts.generated.json
- regenerated cmux-tui/crates/cmux-app-host/generated/cmux-app.d.ts
- regenerated schemas/defaults/cmux-next.json

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

* chore(next): PR autofix at 1ee01b5

- regenerated plans/cmux-next/action-surfaces.json
- regenerated web/data/cmux-shortcuts.generated.json
- regenerated cmux-tui/crates/cmux-app-host/generated/cmux-app.d.ts
- regenerated schemas/defaults/cmux-next.json

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

* Translate the agent update intent argument into every locale

check-l10n flagged argument.agent.harness.intent as missing 12 of the 21 locales.

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

* cmux-tui: drop the std::sync qualification the base's clippy fix removed (unused_qualifications)

* chore(next): PR autofix at 52aaa2c

- Packages/macOS/CmuxNext/ci-target-graph.json
- plans/cmux-next/action-surfaces.json
- plans/cmux-next/actions.md

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

* chore(next): PR autofix at cac5e24

- Packages/macOS/CmuxNext/ci-target-graph.json
- plans/cmux-next/action-surfaces.json
- plans/cmux-next/actions.md

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

* chore(next): PR autofix at a746f81

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

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

* chore(next): PR autofix at d86b10d

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

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

* chore(next): PR autofix at 6714366

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

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

* Leave ci-target-graph.json to the base autofix

Only the base autofix writes it; generated drift refused the hand edit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15bbf-a30f-7992-abba-3e3ca6f5adc1&host=cosmic-gray

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: cmux-autofix[bot] <cmux-autofix[bot]@users.noreply.github.com>
teamleaderleo added a commit that referenced this pull request Oct 21, 2026
* acpmux: require version evidence for already-current updates

Replays #21398's change onto current feat-cmux-next as one commit.

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

* agents: refresh inventory versions without probing or updating

Replays #21401's change onto current feat-cmux-next as one commit.

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

* agents: connect explicit version refresh to the selected owner

Replays #21402's change onto current feat-cmux-next as one commit.

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

* Add owner update terminal jobs with durable replay

Replays #21414's change onto current feat-cmux-next as one commit. Keeps the crash ratchet flat: poisoned locks are recovered instead of unwrapped (only a held job lock is busy), and the job status parses its decimal strings with radix: 10.

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

* Preserve app mutation keys through shared action runs

Replays #21429's change onto current feat-cmux-next as one commit, written without growing CmuxNextApp's source lines (size cap).

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

* Preserve app callers through the shared action registry

Replays #21436's change onto current feat-cmux-next as one commit. ControlRequest takes callerApp in its init so CmuxNextApp's source lines don't grow (size cap).

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

* Share agent CLI update UI across Settings and actions

Replays #21447's change onto current feat-cmux-next as one commit, together
with its follow-up fix.

Settings and the palette run agent updates on their captured owner route
instead of typing a health argv into a focused pane. The update owner
prepares only npm and native installations, and refuses a family when any
of its rows is another channel or already current, so both surfaces submit
only those families and Settings shows Update only for rows whose channel
the owner runs.

A start the owner refuses (agent_cli_job.*, answered before it journals a
creation) leaves no "unknown" job that blocks that CLI. One family's failure
does not stop the rest of a batch; the batch reports every failure. A lost
reply still keeps its unknown job, since the terminal may exist.

Update, Update all and turning Update Automatically on still need a gesture
or a confirmed user, as #21439 requires.

The update action, its owner route and the health-row reader live in a new
CmuxNextAgentUpdates module, leaving thin app glue in CmuxNextApp.

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

* Recover agent updates across app restarts

Replays #21469's change onto current feat-cmux-next as one commit, on top
of the owner-channel fix, with these changes so a retained intent always
has a way out:

- A start the owner refuses before journaling (agent_cli_job.*) settles
  its intent, in a batch and on Resume, instead of leaving a Resume that
  is refused again.
- Any terminal exit settles, including one whose outcome is unknown, and
  a failed creation state settles on recovery.
- A person may dismiss an unstarted (not_found, prepared) or indeterminate
  intent from Settings; dismissing stops nothing. Pending stays fenced.
  Resume and Dismiss need a gesture, like Update.
- The Rust ledger gains agent-cli-intents-settle, which deletes rows.
  Recording drops rows older than seven days and keeps the newest 256 per
  actor instead of failing at a cap.

Recovery, the ledger port and the owner route live in the
CmuxNextAgentUpdates module; the app keeps only its daemon glue.

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

* Require trusted page context for Settings agent updates

Replays #21439's change onto current feat-cmux-next as one commit. Turning Update Automatically on now needs a gesture or native confirmation too, since acpmux runs the update after its next check (review finding); SettingsHarnesses is compacted so CmuxNextApp's source lines don't grow (size cap). The health read defaults to the named AcpmuxEnvironment.harnessHealth rather than an async closure literal (crash ratchet).

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

* Show agent update outcomes automatically

Replays #21480's change onto current feat-cmux-next as one commit, on top
of the rebuilt #21469.

Settings, palette and recovered jobs follow their retained terminals.
terminal-registry-changed events from the captured control connection only
trigger a bounded read of the original receipt; they never dispatch work
or imply completion. Completed batches stop observing.

AgentCLIUpdateActions+Observation.swift lives in the CmuxNextAgentUpdates
module beside the rest of the action, and the owner route captures the
event stamp there. As in #21469, any exit, including one with an unknown
outcome, settles its intent in the ledger.

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

* Let every retained agent update end

Review follow-ups on #21469, so a retained intent always has a way out.

- A closed update terminal settles its job. terminal.wait_exit answers
  terminal.closed, then selector.not_found for the tombstoned terminal; both
  mean the process ended. A missing session or connection still keeps it.
- Ledger reads apply the seven-day age bound, not only recording, which a
  blocked family never reaches.
- A row from a session no route can reach (a reinstalled server) no longer
  blocks the current session's update; it waits out the age bound. Rows of
  the same session, or of a session recovery reattached, still block.
- A failed record clears the batch's jobs, since nothing started.
- A creation still pending ten minutes after a recovery first saw it may be
  dismissed.
- agent_cli_job.busy no longer settles: the owner may be running that key.
- A test covers adding recorded_at to a ledger created without it.

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

* chore(next): PR autofix at 0aae74c

- regenerated plans/cmux-next/action-surfaces.json
- regenerated schemas/defaults/cmux-next.json
- vp fmt webviews/src/agent-session/acpmux/replyHref.ts
- vp fmt webviews/src/pages/markdown/links.ts
- vp fmt webviews/test/markdown-cmux-app-links.test.ts

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

* Keep other sessions' agent update rows blocking

A row from a session recovery cannot reach blocks the CLI on that machine
again. "No current route" cannot tell a dead session from a second live
server on the same machine that may still be running the updater, so
unblocking it could start two global installs at once. The ledger's
seven-day read bound retires true orphans.

Also notes at the closed-terminal settle that Rust answers terminal.closed
before it queues the runtime signal, so the updater may still be dying in
its close grace.

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

* chore(next): PR autofix at 953137a

- vp fmt webviews/src/pages/markdown/README.md
- cargo fmt in cmux-tui

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

* Bind the Settings update scope with withValueInTask

TaskLocal.withValue frees its marker with the task allocator even when no
task is current (cx-bpcj); the crash-class gate bans it. Use the shared
withValueInTask helper, as ActionRunScope does.

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

* chore(next): PR autofix at 45adaef

- regenerated plans/cmux-next/action-surfaces.json
- regenerated web/data/cmux-shortcuts.generated.json
- regenerated cmux-tui/crates/cmux-app-host/generated/cmux-app.d.ts
- regenerated schemas/defaults/cmux-next.json

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

* chore(next): PR autofix at a51fde3

- regenerated plans/cmux-next/action-surfaces.json
- regenerated web/data/cmux-shortcuts.generated.json
- regenerated cmux-tui/crates/cmux-app-host/generated/cmux-app.d.ts
- regenerated schemas/defaults/cmux-next.json

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

* chore(next): PR autofix at 1ee01b5

- regenerated plans/cmux-next/action-surfaces.json
- regenerated web/data/cmux-shortcuts.generated.json
- regenerated cmux-tui/crates/cmux-app-host/generated/cmux-app.d.ts
- regenerated schemas/defaults/cmux-next.json

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

* Translate the agent update intent argument into every locale

check-l10n flagged argument.agent.harness.intent as missing 12 of the 21 locales.

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

* cmux-tui: drop the std::sync qualification the base's clippy fix removed (unused_qualifications)

* chore(next): PR autofix at 52aaa2c

- Packages/macOS/CmuxNext/ci-target-graph.json
- plans/cmux-next/action-surfaces.json
- plans/cmux-next/actions.md

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

* chore(next): PR autofix at d86b10d

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

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

* Leave ci-target-graph.json to the base autofix

Only the base autofix writes it; generated drift refused the hand edit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15bbf-a30f-7992-abba-3e3ca6f5adc1&host=cosmic-gray

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: cmux-autofix[bot] <cmux-autofix[bot]@users.noreply.github.com>
teamleaderleo added a commit that referenced this pull request Oct 21, 2026
* acpmux: require version evidence for already-current updates

Replays #21398's change onto current feat-cmux-next as one commit.

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

* agents: refresh inventory versions without probing or updating

Replays #21401's change onto current feat-cmux-next as one commit.

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

* agents: connect explicit version refresh to the selected owner

Replays #21402's change onto current feat-cmux-next as one commit.

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

* Add owner update terminal jobs with durable replay

Replays #21414's change onto current feat-cmux-next as one commit. Keeps the crash ratchet flat: poisoned locks are recovered instead of unwrapped (only a held job lock is busy), and the job status parses its decimal strings with radix: 10.

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

* Preserve app mutation keys through shared action runs

Replays #21429's change onto current feat-cmux-next as one commit, written without growing CmuxNextApp's source lines (size cap).

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

* Preserve app callers through the shared action registry

Replays #21436's change onto current feat-cmux-next as one commit. ControlRequest takes callerApp in its init so CmuxNextApp's source lines don't grow (size cap).

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

* Share agent CLI update UI across Settings and actions

Replays #21447's change onto current feat-cmux-next as one commit, together
with its follow-up fix.

Settings and the palette run agent updates on their captured owner route
instead of typing a health argv into a focused pane. The update owner
prepares only npm and native installations, and refuses a family when any
of its rows is another channel or already current, so both surfaces submit
only those families and Settings shows Update only for rows whose channel
the owner runs.

A start the owner refuses (agent_cli_job.*, answered before it journals a
creation) leaves no "unknown" job that blocks that CLI. One family's failure
does not stop the rest of a batch; the batch reports every failure. A lost
reply still keeps its unknown job, since the terminal may exist.

Update, Update all and turning Update Automatically on still need a gesture
or a confirmed user, as #21439 requires.

The update action, its owner route and the health-row reader live in a new
CmuxNextAgentUpdates module, leaving thin app glue in CmuxNextApp.

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

* Recover agent updates across app restarts

Replays #21469's change onto current feat-cmux-next as one commit, on top
of the owner-channel fix, with these changes so a retained intent always
has a way out:

- A start the owner refuses before journaling (agent_cli_job.*) settles
  its intent, in a batch and on Resume, instead of leaving a Resume that
  is refused again.
- Any terminal exit settles, including one whose outcome is unknown, and
  a failed creation state settles on recovery.
- A person may dismiss an unstarted (not_found, prepared) or indeterminate
  intent from Settings; dismissing stops nothing. Pending stays fenced.
  Resume and Dismiss need a gesture, like Update.
- The Rust ledger gains agent-cli-intents-settle, which deletes rows.
  Recording drops rows older than seven days and keeps the newest 256 per
  actor instead of failing at a cap.

Recovery, the ledger port and the owner route live in the
CmuxNextAgentUpdates module; the app keeps only its daemon glue.

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

* Require trusted page context for Settings agent updates

Replays #21439's change onto current feat-cmux-next as one commit. Turning Update Automatically on now needs a gesture or native confirmation too, since acpmux runs the update after its next check (review finding); SettingsHarnesses is compacted so CmuxNextApp's source lines don't grow (size cap). The health read defaults to the named AcpmuxEnvironment.harnessHealth rather than an async closure literal (crash ratchet).

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

* Keep Bun agent updates on the selected installation

Replays #21568's change onto current feat-cmux-next as one commit, on top
of the rebuilt #21469.

acpmux binds a Bun installation's observed package and bin roots and its
global config selection in fixed argv, so automatic, requested and owner-job
updates all update the installation that was observed. Caller environment
and argv stay refused. The private prepared argv bound becomes nine
arguments; public inventory and RPC schemas are unchanged.

Bun is now a channel the update owner runs, so Settings and the palette
offer and submit Bun families as they do npm and native ones (ownerCanRun
and the Settings ownerUpdate flag). Homebrew still needs explicit
installation binding and stays unoffered.

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

* Let every retained agent update end

Review follow-ups on #21469, so a retained intent always has a way out.

- A closed update terminal settles its job. terminal.wait_exit answers
  terminal.closed, then selector.not_found for the tombstoned terminal; both
  mean the process ended. A missing session or connection still keeps it.
- Ledger reads apply the seven-day age bound, not only recording, which a
  blocked family never reaches.
- A row from a session no route can reach (a reinstalled server) no longer
  blocks the current session's update; it waits out the age bound. Rows of
  the same session, or of a session recovery reattached, still block.
- A failed record clears the batch's jobs, since nothing started.
- A creation still pending ten minutes after a recovery first saw it may be
  dismissed.
- agent_cli_job.busy no longer settles: the owner may be running that key.
- A test covers adding recorded_at to a ledger created without it.

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

* chore(next): PR autofix at 0aae74c

- regenerated plans/cmux-next/action-surfaces.json
- regenerated schemas/defaults/cmux-next.json
- vp fmt webviews/src/agent-session/acpmux/replyHref.ts
- vp fmt webviews/src/pages/markdown/links.ts
- vp fmt webviews/test/markdown-cmux-app-links.test.ts

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

* Keep other sessions' agent update rows blocking

A row from a session recovery cannot reach blocks the CLI on that machine
again. "No current route" cannot tell a dead session from a second live
server on the same machine that may still be running the updater, so
unblocking it could start two global installs at once. The ledger's
seven-day read bound retires true orphans.

Also notes at the closed-terminal settle that Rust answers terminal.closed
before it queues the runtime signal, so the updater may still be dying in
its close grace.

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

* chore(next): PR autofix at 953137a

- vp fmt webviews/src/pages/markdown/README.md
- cargo fmt in cmux-tui

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

* Bind the Settings update scope with withValueInTask

TaskLocal.withValue frees its marker with the task allocator even when no
task is current (cx-bpcj); the crash-class gate bans it. Use the shared
withValueInTask helper, as ActionRunScope does.

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

* chore(next): PR autofix at 45adaef

- regenerated plans/cmux-next/action-surfaces.json
- regenerated web/data/cmux-shortcuts.generated.json
- regenerated cmux-tui/crates/cmux-app-host/generated/cmux-app.d.ts
- regenerated schemas/defaults/cmux-next.json

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

* chore(next): PR autofix at 85b92c6

- regenerated plans/cmux-next/action-surfaces.json
- regenerated web/data/cmux-shortcuts.generated.json
- regenerated cmux-tui/crates/cmux-app-host/generated/cmux-app.d.ts
- regenerated schemas/defaults/cmux-next.json

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

* chore(next): PR autofix at 1ee01b5

- regenerated plans/cmux-next/action-surfaces.json
- regenerated web/data/cmux-shortcuts.generated.json
- regenerated cmux-tui/crates/cmux-app-host/generated/cmux-app.d.ts
- regenerated schemas/defaults/cmux-next.json

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

* Translate the agent update intent argument into every locale

check-l10n flagged argument.agent.harness.intent as missing 12 of the 21 locales.

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

* test(next): agent CLI updates survive reconnects, unknown exits, owner mismatches and palette failures

Regression tests for four defects in the shared agent CLI update action:
a reconnect to the same owner hides and blocks the job, a new daemon
session leaves a job that blocks the CLI forever, an exit with an unknown
outcome blocks the CLI forever, a receipt for another owner leaves a
blocking job, and a palette run's failure is never shown.

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

* fix(next): agent CLI update jobs follow their owner across reconnects and settle on any end

- Showing, refreshing and blocking now share one owner rule: a job whose
  route went stale is re-captured when the app still routes the same
  machine and session (its receipts stay observable after a reconnect),
  and otherwise neither shows nor blocks the CLI.
- An exit with an unknown process result stays state "exited" with
  outcome "unknown", so it settles instead of blocking forever.
- A receipt for another owner drops its job before the error.
- A palette run (no caller capturing work) shows its failure through the
  registry's refusal instead of dropping it.
- Nothing-to-update and still-updating refusals get their own localized
  text instead of the generic add/remove agents message.

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

* test(next): agent update survives a reconnect mid-batch and a lost start reply

A reconnect while one family's start waits must not start the next family on
the re-captured owner and leave it with a job that blocks that CLI. A lost
start reply must be replayed by its key on refresh, so the job gets its
receipt or the owner's refusal clears it.

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

* fix(next): agent update start keeps its captured owner and replays lost replies

start() re-read intent.route after every await, while showing or refreshing
the jobs can swap it for a re-capture after a reconnect. A batch then went
on to start the next family on the new connection, whose route had no open
carrier, so that family kept an unknown job with no receipt that blocked its
CLI for good. start() now captures the route once and stops when it retires,
and a route opens its carrier on start as well as on inventory and observe.

A lost start reply left an unknown job that blocked the CLI until restart.
The intent keeps the child key, and refresh sends it again on the current
owner: the owner replays the key, so the job gets its receipt, or its
refusal clears the job.

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

* test(next): a busy answer to a replayed agent CLI update start keeps its job

The owner answers agent_cli_job.busy before it looks the key up, so on a
replay busy usually means the key's original request is still running.

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

* fix(next): a busy answer to a replayed agent CLI update start keeps its job

Review of #21447: refresh treated agent_cli_job.busy on a replay as the
key creating nothing, dropping the job while its original request could
still create the terminal, which then went untracked and unblocked.

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

* test(next): retained agent update rows no owner can reach are shown and dismissable (red)

Regressions for the #21469 review:

- A ledger row whose machine/session no route can capture blocks that CLI on
  that machine for up to seven days, but state never shows it and dismiss
  cannot reach it. The tests expect it shown as state "unknown" with
  can_dismiss, a gesture-only dismiss that deletes the row, and the block
  lifting after it (recovery, actions and the AppServices journey).
- A failed receipt attach after a successful start aborts the whole batch;
  the test expects it reported while the other families still start.
- Rust: one ledger row that fails validation fails the whole list; the test
  expects the bad rows skipped and the good one returned.

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

* fix(next): show and let a person dismiss agent update rows no owner can reach

A retained ledger row whose machine/session no route captures blocked that
CLI on that machine for up to seven days while state never showed it and
dismiss could not reach it.

- state now lists, after the jobs of reachable intents, every unsettled
  retained row none of them shows, as state "unknown" with can_dismiss.
  What start blocks on is therefore always shown.
- dismiss(key) on such a row settles it locally (a ledger delete, plus any
  hidden in-memory job), still behind a gesture; it stops nothing.
- An unreadable ledger clears the retained rows instead of showing stale ones.
- A failed receipt attach after a successful start is reported with the
  batch's other failures instead of aborting the remaining families.
- Rust: read_rows skips and logs a row that fails validation instead of
  failing the whole list.
- rustfmt the merged agent_cli_job.rs lock line.

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

* test(next): busy first starts, dismiss during a replay, and resolve-then-replay (red)

Regressions for the #21469 review at 217ba19:

- A first start the owner answers busy created nothing (busy comes before
  the key lookup), but its row was kept: it blocked a retry click and a later
  refresh started the update after the person saw a failure.
- A dismiss while a replay is in flight deleted the row the replayed
  receipt then could not attach to, leaving an untracked updater.
- A lost reply whose key the owner has nothing for (not_found, prepared) was
  replayed by refresh with no fresh gesture instead of offering Resume.
- A batch that stops early should name the families it never sent.

The replayed-row test now uses an owner that cannot resolve the key, where
the replay is the only path.

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

* chore(next): PR autofix at 99a29e6

- plans/cmux-next/action-surfaces.json
- plans/cmux-next/actions.md

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

* chore(next): PR autofix at 8c68c4f

- Packages/macOS/CmuxNext/ci-target-graph.json
- plans/cmux-next/action-surfaces.json
- plans/cmux-next/actions.md

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

* fix(next): settle busy first starts, hold replays against dismiss, and resume after not_found

- A first start the owner answers busy settles. The owner answers busy from its
  inventory reservation or execution lock before it looks up the key, and send()
  only ever sends a fresh key, so nothing was created for it. Busy still keeps
  the row on a replay or resume, where the key may really be in flight.
- A replay holds its intent busy, so a dismiss mid-replay is refused instead of
  deleting the row the replayed receipt attaches to. Settling a key with its
  intent also drops it from the unsent replays.
- When resolve answers not_found or prepared for a lost reply, refresh no longer
  replays it: replaying is an execution request with no fresh gesture. The row
  offers Resume. Keys resolve cannot answer are still replayed.
- A batch that stops early names every family it never sent in its failure.

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

* chore(next): PR autofix at 19f6a8c

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

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

* chore(next): PR autofix at 3b5a7ca

- Packages/macOS/CmuxNext/ci-target-graph.json
- schemas/defaults/cmux-next.json

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

* test(next): settled owner failures and ended jobs never block agent CLI updates (red)

Regressions for the #21447 review:

- The owner journals a failed creation under its key and answers every
  replay with the same failure (operation.failed, mutation.indeterminate).
  Such an answer, on the first start or on a replay of a lost reply,
  must settle the job instead of leaving it unknown and blocking the CLI
  for the rest of the session.
- A job that already ended must not block the next update of its CLI
  just because nothing refreshed Settings > Agents in between; a job
  that still runs keeps refusing it.

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

* fix(next): settle agent CLI updates the owner answered, and check ended jobs before refusing

- Any coded owner answer to a start settles its key. agent_cli_job.*
  comes before a creation is journaled; any other code (a failed
  creation, operation.failed, or an interrupted one,
  mutation.indeterminate) is the key's journaled outcome and the owner
  answers every replay the same way. Such an answer, on the first start
  or on a replay, now drops the job instead of leaving it unknown and
  blocking the CLI for the rest of the session. Only a failure with no
  owner answer (a lost reply) is replayed; busy on a replay still keeps
  the job.
- A start that finds an earlier job of the same CLI still unended
  refreshes once before refusing, so a job that already exited no longer
  blocks the next palette or Settings update until someone opens
  Settings > Agents. A job that still runs keeps refusing.

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

* test(next): a coded answer to a replayed agent CLI update start keeps the lost job (red)

Regression for the delta review of 0ad8305: a replay can get a coded
answer the owner sends before it reads its journal (daemon_shutdown_pending
during a handoff, operation.failed from a registry error), while the
lost first start may have created the update terminal. Dropping the job
then hides a running update and unblocks a second one of the same CLI.

A first start's fresh key has nothing running, so any coded answer there
still settles; its cases are now operation.failed and
daemon_shutdown_pending, with a fixture message that matches.

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

* fix(next): only a refusal settles a replayed agent CLI update start

A replay of a lost start now drops its job only on an agent_cli_job.*
refusal, as before 0ad8305. Another coded answer can come before the
owner reads its journal (daemon_shutdown_pending during a handoff, or a
registry error mapped to operation.failed) while the lost first start
created the terminal, so the job keeps blocking its CLI and the next
refresh replays the same key. A first start's fresh key still settles on
any coded answer, which is what keeps a failed creation from blocking the
CLI for the session.

A lost reply whose creation then failed is still replayed until the
owner can say so; #21469's journal resolve settles that case.

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

* fix(next): a first agent CLI update start settles on any owner answer

From #21447's review: a first start that the owner answers with a coded
failure other than an agent_cli_job.* refusal (operation.failed for a
failed creation, daemon_shutdown_pending during a handoff) left its
ledger row unknown and unsent. `send` now settles the intent on any
coded answer, since a first start's fresh key cannot have anything
running. Replays and resumes still settle only on a refusal: a code the
owner sends before reading its journal proves nothing there, and
recovery's resolve settles a journaled failure.

Makes #21447's aFirstStartTheOwnerAnswersWithAFailureLeavesNoBlockingJob
pass on this branch.

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

* test(next): an indeterminate first agent CLI update start keeps blocking its CLI (red)

From the second delta review: the owner answers a fresh key with
mutation.indeterminate when a failed creation left its reserved terminal
possibly live (AmbiguousLive in creation_settlement.rs). Dropping that
job would let a second update of the same CLI start beside it.

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

* fix(next): an indeterminate first agent CLI update start keeps its job

`answered` no longer settles mutation.indeterminate: the owner says a
failed creation's effect may have run, so the job stays unknown and keeps
blocking its CLI, like a lost reply, until it is replayed or its owner
session retires it. The comments no longer claim a coded first-start
answer always means nothing runs.

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

* fix(next): an indeterminate first agent CLI update start keeps its ledger row

`answered` no longer settles mutation.indeterminate: the owner says a
failed creation's effect may have run, so the row stays unknown and
blocking, and recovery's resolve lets a person dismiss it.

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

* test(next): a failed first agent CLI update start keeps its ledger row until the journal settles it (red)

From #21469's review: a coded failure other than an agent_cli_job.*
refusal can follow a durable creation (a launch activation failing after
the commit maps to operation.failed), so deleting the ledger row on such
an answer can hide a live update terminal and unblock a second one. The
row must stay until recovery's resolve reads the owner's journal; a
failed creation then settles. #21447's any-code variant of this test is
replaced here; the fake owner can now answer resolve.

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

* fix(next): a first agent CLI update start settles its ledger row only on a refusal

Reverts this branch's `answered` rule from 1db61b6 and 2506184.
A first start settles only on an agent_cli_job.* refusal or busy, as the
branch had it; any other coded answer keeps the ledger row, because it
can follow a durable creation. Recovery's resolve then reads the owner's
journal: a failed creation settles, an indeterminate one becomes
dismissable, and a created one reattaches.

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>

This branch was previously deployed

2 inactive (1 outdated) deployments
cmux-tui-merge-tree — 6460e0a6 Deployed Oct 19, 2026 by teamleaderleo
artifacts — 0309542c Deployed Oct 19, 2026 by manaflow-cmux-pr-autofix[bot]
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