Skip to content

ci: the devbox rebake publishes its manifest branch in two pushes - #27367

Merged
teamleaderleo merged 1 commit into
feat-cmux-nextfrom
dep-fleet/devbox-rebake-publish-ref
Oct 29, 2026
Merged

teamleaderleo merged 1 commit into
feat-cmux-nextfrom
dep-fleet/devbox-rebake-publish-ref

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

Every devbox image rebake since 10-28 baked, verified and smoke-tested its image, then failed at "Publish the manifest pull request": ! [remote rejected] HEAD -> devbox-rebake/4e7d84ee3095 (Unable to determine if workflow can be created or updated due to timeout; workflows scope may be required.) (runs 40640977877 and 40851484187). Nothing could be promoted, so staging Cloud machines still boot the 10-15 image. Its cmux-tui predates agent session tabs (agent-session-tabs-v1), so no agent chat opens on a new staging machine.

A single push of a new branch makes GitHub work out which commits are new against the whole repository, and here that times out. Since the push token has no workflows permission, GitHub refuses the push. The step now pushes twice: first the branch at the baked commit, which is already on the base and adds no commit, then the one manifest commit on top, which touches only web/services/vms/images/*.json. No token, permission or other step changes.

Changelog

none

Observed / Not run

  • The two failing publish logs are quoted above. The bake in run 40851484187 pinned cmux-tui 6a202d045fd6, which includes agent session tabs, and its verify and smoke steps passed.
  • actionlint on the file, tests/test_ci_runs_on_values.py, test_ci_workflow_permissions_are_job_scoped.py and test_ci_actionlint_covers_every_workflow.py pass.
  • Not run yet: the publish itself. I'll dispatch the rebake once this merges.

🤖 Generated with Claude Code

Agent session: agent session


Summary by cubic

Fixes the devbox image rebake workflow so its manifest branch publish no longer times out and gets refused by GitHub.

Every publish since 10-28 failed because a single push of a new devbox-rebake/* branch makes GitHub compute new commits against the whole repository, which times out on this repository. The step now pushes twice: first the branch at the baked commit (already on the base, adds no commit), then the one manifest commit on top. No token, permission, or other step changes.

Written for commit c9f0df8. Summary will update on new commits.

View guided diff Turn on auto-fix

Every publish since 10-28 was refused: a single push of a new
devbox-rebake/* branch makes GitHub compute the new commits against the
whole repository, which times out ("Unable to determine if workflow can
be created or updated due to timeout; `workflows` scope may be
required"). Push the branch at the baked commit first (no new commit),
then the one manifest commit on top.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-7a592ef92c66f080e3cc
@github-actions

github-actions Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

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

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

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

gh workflow run reload-build.yml -R manaflow-ai/cmux --ref feat-cmux-next -f tag=pr27367 -f ref=dep-fleet/devbox-rebake-publish-ref -f platform=macos -f backend_mode=local

red: cmux-next checks (god files, concurrency, crash safety, l10n); base red: the same failure on the base 82ebef2; does not block

@github-actions github-actions Bot added the risk:normal Risk tier (docs/ci/risk-tiers.md) label Oct 29, 2026
@blacksmith-sh

blacksmith-sh Bot commented Oct 29, 2026

Copy link
Copy Markdown

Found 6 test failures on Blacksmith runners:

Failures

Test View Logs
/tmp/tmp3esbq0ho/summary-fail.json (live):/summary-fail.json (live): View Logs
/tmp/tmphfxgjtsh/summary-missing.json (live):/summary-missing.json (live): View Logs
[new_workspaces] debug.new_workspace timed 0 of 50 new workspace rows/
[new_workspaces] debug.new_workspace timed 0 of 50 new workspace rows
View Logs
[new_workspaces] the window draws no frames/[new_workspaces] the window draws no frames View Logs
[tabs] debug.tab_switch timed 0 of 40 switches/
[tabs] debug.tab_switch timed 0 of 40 switches
View Logs
[tabs] the window draws no frames/[tabs] the window draws no frames View Logs

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

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

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

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (Codex gpt-6-astra, high, fast; independent): SHIP at c9f0df8

$COMMIT is the parent of the manifest commit. Both forced pushes go to the job's own branch. Tokens, masking and the PR-open steps are unchanged, and set -euo pipefail stops on either failed push. The red cmux-next checks step ("No new SwiftUI") is red the same way at the merge base 7e93d81; this PR changes no Swift.

@teamleaderleo teamleaderleo removed the risk:normal Risk tier (docs/ci/risk-tiers.md) label Oct 29, 2026
@teamleaderleo
teamleaderleo merged commit a830a9b into feat-cmux-next Oct 29, 2026
157 of 159 checks passed
@teamleaderleo
teamleaderleo deleted the dep-fleet/devbox-rebake-publish-ref branch October 29, 2026 16:04
@github-actions github-actions Bot added the risk:normal Risk tier (docs/ci/risk-tiers.md) label Oct 29, 2026
@github-actions

Copy link
Copy Markdown
Contributor

The feat-cmux-next push run https://github.com/manaflow-ai/cmux/actions/runs/40908190707 at e9f957f failed: cmux-next checks (god files, concurrency, crash safety, l10n).
Those jobs last passed at 13f725e. This pull request is one of 4 merged since: #27455, #27370, #27367, #25891.
If the failure is in your change, fix forward on feat-cmux-next. Pull requests run only the tiers their paths reach (docs/ci/cmux-next-tiers.md); label a batch PR full-ci to run them all before merging.

@github-actions

github-actions Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

teamleaderleo added a commit that referenced this pull request Oct 29, 2026
…es, and a one-time sheet (Labs) (#27223)

* cmux NEXT: Labs flag labs.machineJoin for the one-line machine join (off by default)

The one-line join's app surfaces (Add Device's "Any machine: one line" way, joined rows in the
Machines sidebar, This Mac's consent switches) sit behind this switch while the join routes answer
only on the development platform. Not agent-writable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT: Add Device's "Any machine: one line" way (Labs labs.machineJoin)

A fourth Add Device way, shown only with labs.machineJoin on: an optional name, Make Line mints a
single-use join line through the bundled CLI (cmux machine add --json --no-wait, the account's
access token on stdin), the line in the monospaced box with a countdown from expiresAt, Copy Line
on the concealed and transient pasteboard types, and the note that screen sharing and control stay
off. It flips to joined when cmux machine list carries a joined row with its joinTokenId, read when
the line shows, when the card gets the focus and on each mesh change: never on a timer. The
countdown is the one repeating task; it stops at expiry, on the flip, on close and on a new line.
The default way and the existing setup line are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT: join line: Foundation is a public import where its types are public

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT join line: review fixes (debug fixture behind the switch, bounded test wait, no Show in Machines yet)

The debug.add_device join fixture applies only while labs.machineJoin is on and a call with no
join clears it. The mesh test's wait for the subscription is bounded. Show in Machines moves to
the joined rows change, where the Machines section lists the joined machine.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT join line: the capture tour turns the Labs switch on through its settings file

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT join line: the card widens for its fourth way; the join checks have names

From the fleet captures: four ways at the card's 440 pt clipped the segment labels, so with the
Labs way the card is 540 pt and its segments fit their titles ("Any machine"); with three ways it
keeps its width and layout. The joined card's checks show readable step names (both doctor ends'
steps), and the line's help leaves once the machine joined or the line expired.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT join line: expiresAt is the platform's ISO time only

The platform sends expiresAt as Date.toISOString() (RFC 3339 UTC with
milliseconds and Z), never epoch milliseconds, so the epoch path goes
and the test pins the exact shape.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT: This Mac's join consent: Screen sharing and Control switches, and a one-time sheet

Leo: "just have this sort of thing for, I guess, general onboarding of adding remote machines and
whatnot and whatever, which includes, I guess, screen sharing potentially."

A Mac joined to a team with the one-line join (labs.machineJoin) shows, in Settings > Devices, what
it joined as and two switches, Screen sharing and Control, both off. As with Full access, an on
needs the person's own click in the page (spent once) and then a line acpmux proves as the person;
off works from any line. The first screen or control request acpmux refuses with consent_required
shows a non-activating sheet once (Deny / Allow; Allow writes through the same person path). Mac
Screen's per-request approval panel is unchanged.

The acpmux side is a seam: JoinConsentLine and JoinConsentServices, with fakes in tests. The app
calls _acpmux/consent_set, a stand-in until acpmux has its person-only consent method and refusal
event; until then every write reports failure and nothing turns on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* test(media): join consent capture tour (Settings > Devices on a joined Mac, the one-time sheet)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT: join consent: an on needs a joined, unrevoked Mac with Labs on; the RPC fixture never grants; a failed Allow keeps the sheet

Review (codex gpt-6-astra): the native model now refuses an on unless labs.machineJoin is on and
this Mac's join is present and not revoked (read fresh), while off always goes through; turning
Labs off closes an open sheet; the sheet stays up with the reason when Allow does not take; the
service closures run on the main actor; debug.join_consent starts both consents off and never
sets one; the panel holds its model weakly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT: join consent sits under This Mac in Settings > Devices; before/after capture tours

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT: join consent: an older status read never overwrites a newer one; expect without a key path

Review (codex gpt-6-astra) round 3: refresh() keeps only the newest read (a generation, as
AddDeviceModel.begin does). The test's allSatisfy key path became a closure (it compiled as a
throwing call inside #expect).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT: join consent: an unknown surface is refused before any gesture is spent; bounded wait in the test; format

Review (codex gpt-6-astra) round 4.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT: join consent: JoinConsentStrings is an instantiable struct like AddDeviceStrings (package conventions lint)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT: join consent: wire to acpmux's consent record and gate Mac Screen on it

The switches read `_acpmux/machine_consent_get` and write `_acpmux/machine_consent_set {surface, granted}`
over the person-proof line, under `machine-consent-v1`. An acpmux without that capability has granted
nothing: the switches stay off with the reason, and Mac Screen (app-owned, so acpmux cannot gate it)
refuses every session and shows the one-time sheet. With the Labs switch on, a joined Mac serves only
what its person allowed here; a refusal acpmux recorded shows the sheet when the app comes forward.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT: join consent: Mac Screen reads consent fresh per ticket and stops on a revoke; fixtures never authorize

Review (codex gpt-6-astra) round 6, and hb-join's seam detail:
- Each Mac Screen ticket waits for a fresh read (a join made since counts); a read that no longer
  allows the running session stops it.
- A capture fixture (debug.join_consent) never authorizes a live Mac Screen session.
- The app's activation loop reads consent, so it no longer depends on Settings or Mac Screen opening first.
- acpmux takes a revoke only from the person's connection too: the fakes say so.
- machine_consent_get's consentRequired, when sent, decides joined.
- A fake acpmux shows the line proves the person on the same connection before machine_consent_set,
  and that a set without the prove step (or with another key's proof) is refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT: join consent: a Mac Screen ticket decides from its own read; a revoke closes at once; Stop Sharing ends waiting tickets

Review (codex gpt-6-astra) round 7:
- A ticket's consent read decides that ticket, even when a newer read started meanwhile.
- A fixture set while a ticket's read runs never authorizes it.
- A consent change closes the running session here at once; the hub hears it after.
- Stop Sharing ends the tickets still waiting for their consent read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT: join consent: a revoke counts at once; a ticket serves only while its read and the current state both allow it

Review (codex gpt-6-astra) round 8:
- A revoke acpmux took applies locally before its confirming read (a running session stops now, and
  older reads in flight are discarded).
- A Mac Screen ticket serves only when its own read and what this Mac holds now both allow it, so a
  grant read before a revoke never authorizes after it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT: join consent: a failed consent read allows nothing; an older ticket never replaces a newer one

Review (codex gpt-6-astra) round 9:
- Mac Screen needs a consent record read from acpmux before anything, the "not joined" exemption included.
- Tickets whose consent reads answer out of order: the newest serves, an older one is ended as replaced.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT: join consent: only acpmux's consentRequired false counts as not joined; a Deny never lands over a pending Allow

Review (codex gpt-6-astra) round 10:
- Mac Screen serves as "not joined" only when acpmux's record says `consentRequired: false`; a join
  status read before it may be stale. A refusal shows the sheet only on a Mac read as joined.
- While the sheet's Allow writes, another press (a Deny included) does nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT: join consent: one generation-gated owner, fail closed, writes block the controls

The consent model is the one serial owner of consent state (main actor): every read, write, ticket
and press goes through it. Each request takes the next generation when it starts and only the newest
generation's result is applied; an older one is dropped, and reads made during a write are never
applied (the write's own read after it lands is). Fail closed: a Mac Screen ticket serves only when
its own read is still the newest, explicitly grants, and no write is in flight; "not joined" only on
acpmux's explicit consentRequired false. A revoke narrows the moment it starts. While a write is in
flight the sheet's buttons and the page's switches are disabled and another write or press does
nothing. A table-driven test runs reads, tickets, grants, revokes and the sheet's Deny in every
start and answer order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT: join consent: the sheet's Allow takes only the person's own press; a revoke drops a cached "not joined"

Review (codex gpt-6-astra) round 11, on the code before the rewrite, still applicable:
- Allow needs the person's own click or key on the sheet (the agent pane's person-input check, now
  shared as PersonInputCheck); an Accessibility press or a posted click grants nothing.
- A revoke also drops a cached consentRequired false, so the session stops at once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT: join consent: round 12, every finding: person press scoped to the sheet's dispatch, Labs changes and acpmux's folder followed, writes keep their confirmation

Review (codex gpt-6-astra) round 12, the exhaustive list:
- Allow answers only inside the sheet's own dispatch of a press the strict person check accepts
  (PersonInputCheck has no DEBUG automation exception); an Accessibility press, even right after a
  real one, a key or a programmatic click grants nothing.
- The Labs switch is followed: a change drops what was read (a running Mac Screen session stops when
  it turns on) and reads again. While it is on, acpmux's home folder is watched, so a join, a consent
  change or a refusal made elsewhere is read at once (event-driven).
- A read made during a write takes no generation and is never applied, so it cannot displace the
  write's own read; when that read fails, acpmux's answer to the write stands in, and an unread
  record says so on the page.
- A grant is never sent once the Labs switch is off.
- Only the Labs switch off, a join gone or revoked closes the sheet, never an unread status.
- Two deliveries of one Mac Screen session: the newer decides it, the older ends nothing.
- With the Labs switch off a capture fixture changes nothing, and debug.join_consent refuses.
- Tests: a deterministic scheduler that gates every acpmux boundary and interleaves starts and
  releases over 1,500 seeded runs, checking the invariants after every step; the sheet's press
  provenance; no retain cycles in the fakes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT: join consent: PersonInputCheck compiles (AppKit public import; the current-event check passes its default)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT: join consent: the interleavings test checks the model against acpmux's record, logs a write when the app sends it, and a Deny only over an open sheet

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT: join consent: Mac Screen is gated only on a read that got through; a write's answer shows on the switches only

The record acpmux answered a write with stands in for a failed read on the switches, but never lets a
running Mac Screen session go on: the gate takes the last applied read that got through, narrowed by a
revoke at its start. The interleavings test is back to its strict check (never more than a read can
see), and every served ticket is checked against its own read: it got through, it is the current
generation, it says granted, and no write is in flight.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT: join consent: the interleavings test reads the model's generation through a testable import

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT: join consent: round 13: Allow takes only the person's click on Allow, once; a change during a write is read before it ends; a grant is bound to its generation

- The sheet's Allow takes only the person's own click on Allow itself: the sheet dispatching the
  person's mouse-down on Allow with the person's mouse-up on Allow current, each click once. An
  Accessibility press or programmatic click inside the dispatch of another click, while Allow is held,
  or inside a nested event the check refuses, finds none.
- A read asked for while a write is in flight is dropped, and the write reads again before it ends, so
  a change acpmux made meanwhile is never lost behind the write's own read.
- A grant is sent only at its own generation: the Labs switch turning off and on again before the
  line opens still drops it.
- The Labs switch is followed at once (featureSwitched, synchronous); the read after it runs on its
  own, so a later change never waits behind it. The follow task is the model's and ends with it.
- With acpmux's record unknown the page disables both switches (known), and the message says what is
  refused. A write's answer shows on the switches when its read fails, even when that read's line
  could not open.
- The interleavings test takes each record read's reply either when asked or when released, adds
  acpmux changing on its own (join, read failures, a revoke elsewhere), the capability and the person
  at random, and 5,000 seeds; once every request is done after such a change the model must agree with
  no further read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT: join consent: the sheet's Allow takes only the HID system's input (source pid 0 or WindowServer, HID source state)

An agent with Control consent posting a click on Allow (the cmux-cua helper posts from a HID-state
source through SLEventPostToPid and CGEventPostToPid) is refused by its pid; a private or
combined-session source is refused even with pid 0. The Apple services the agent pane accepts and this
process's DEBUG automation don't count on the sheet. Also renames the panel's event seam (NSWindow
already has currentEvent).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT: join consent: a Settings switch turns consent on only with the HID system's input

An agent with Control consent could drive Settings > Devices with the cmux-cua helper and flip the
Screen sharing switch: the page sees a real event, and the app's line to acpmux is the person's. A
local event monitor (PersonInputLedger) now records whether the app's latest key or mouse input was the
person's (PersonInputCheck: source pid 0 or WindowServer, HID source state), and turning a join
consent on from the page needs the gesture behind it to be that input with nothing posted after it.
Turning one off takes any input. The sheet's Allow and this switch are the only paths that grant; no
menu item, palette command, shortcut or deep link does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT: join consent: the interleavings test's coverage floor matches what 5,000 seeds serve (about 105 tickets)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT: join consent: the interleavings test serves more tickets (drawn twice as often, acpmux granted more often) instead of a lower floor

The served-ticket floor keeps the strict serve invariant from passing by never firing. Round 13's wider
request mix (13 kinds, the capability and the person off at random, failing reads, changes acpmux
makes on its own, Labs off) made fewer tickets serve (105 in 5,000 seeds); the generator now draws
tickets twice as often and starts with Screen sharing granted three runs in four, and the floor is back
up, at more than 300.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT: join consent: the served-ticket floor at more than 200 (about 2.6 times the 105 the old mix served, by estimate)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT: join consent: the interleavings test prints how many served tickets it checked

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT: join consent: watch acpmux's machine-wide consent folder (the user's ~/.acpmux/machine-consent), folder and files

Consent is one per OS user, shared by every build (hb-join's seam): the folder comes from the user's
password entry (getpwuid(geteuid())), never HOME or ACPMUX_HOME. The folder is watched through a parent
until the first join makes it, and each of enrolled.json, consent.json and refusal.json through its own
watch, so a rewrite in place is seen too. A change only asks acpmux again (machine_consent_get); the
files are never read for a decision.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT: join consent: round 14: a grant is dropped by any settings load since it started; the consent watch asks once when armed; one action turns both off while the record is unknown

- A grant is sent only when no settings load came since it started (SettingsController.loadCount), so
  the Labs switch turned off and on again while its line opened drops it even when observation
  coalesces both changes. Tested through a real settings file.
- The machine consent watch arms the folder, then the files, then asks once, so a file made or replaced
  while it armed is never missed. The test covers the first ask, the folder made, a file made, rewritten
  in place, replaced by a rename, and the folder removed and made again, each from a quiet point.
- While acpmux's record can't be read, the page keeps the switches disabled and offers one action,
  "Turn Off Screen Sharing and Control", which sends both offs (off always works, from any input).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT: join consent: the watch test's step is a nested function (no non-Sendable closure sent)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT: Labs flag labs.machineJoin for the one-line machine join (off by default)

The one-line join's app surfaces (Add Device's "Any machine: one line" way, joined rows in the
Machines sidebar, This Mac's consent switches) sit behind this switch while the join routes answer
only on the development platform. Not agent-writable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT: Add Device's "Any machine: one line" way (Labs labs.machineJoin)

A fourth Add Device way, shown only with labs.machineJoin on: an optional name, Make Line mints a
single-use join line through the bundled CLI (cmux machine add --json --no-wait, the account's
access token on stdin), the line in the monospaced box with a countdown from expiresAt, Copy Line
on the concealed and transient pasteboard types, and the note that screen sharing and control stay
off. It flips to joined when cmux machine list carries a joined row with its joinTokenId, read when
the line shows, when the card gets the focus and on each mesh change: never on a timer. The
countdown is the one repeating task; it stops at expiry, on the flip, on close and on a new line.
The default way and the existing setup line are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT: join line: Foundation is a public import where its types are public

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT join line: review fixes (debug fixture behind the switch, bounded test wait, no Show in Machines yet)

The debug.add_device join fixture applies only while labs.machineJoin is on and a call with no
join clears it. The mesh test's wait for the subscription is bounded. Show in Machines moves to
the joined rows change, where the Machines section lists the joined machine.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT join line: the capture tour turns the Labs switch on through its settings file

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT join line: the card widens for its fourth way; the join checks have names

From the fleet captures: four ways at the card's 440 pt clipped the segment labels, so with the
Labs way the card is 540 pt and its segments fit their titles ("Any machine"); with three ways it
keeps its width and layout. The joined card's checks show readable step names (both doctor ends'
steps), and the line's help leaves once the machine joined or the line expired.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT join line: expiresAt is the platform's ISO time only

The platform sends expiresAt as Date.toISOString() (RFC 3339 UTC with
milliseconds and Z), never epoch milliseconds, so the epoch path goes
and the test pins the exact shape.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT: join consent: the device owner confirms every grant (Touch ID or the login password)

An event's source pid and source state can't be shown to be the window server's own: a process
allowed to post events (the cmux-cua helper is) may be able to set them. So every grant, from the
Settings switch and the sheet's Allow, now asks LocalAuthentication deviceOwnerAuthentication
(LocalDeviceOwnerAuthenticator, as the password store's reveal) right before it is sent, naming what it
grants. A posted event can't pass Touch ID or type into the secure password field. The person-input
checks stay as an early refusal and never grant on their own. A cancel or a failure sends nothing and
keeps the sheet up; a Mac that can't ask grants nothing and says so; with no confirm wired nothing is
granted. Revokes and Deny never ask. The interleavings scheduler answers each confirm (confirmed,
cancelled or unavailable, seeded) as one more boundary and checks no grant is sent without a confirm.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT join line: pass the fast gates (named sleep defaults, a bounded mesh stream, 21 languages)

- The countdown's sleep and the doctor default are named functions, not
  async closure literals as default arguments (crash ratchet, cx-bsue).
- The countdown's one-second step carries a reviewed wakeup-allow: it
  changes visible text every second while a line shows and ends at
  expiry, on the flip, on close and on a new mint.
- The mesh change stream keeps only the newest signal.
- Every new string has the 12 languages the tables gained (needs_review).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT join line: the join wiring lives in Add Device's App file; one debug fixture

The one-line join's CLI wiring moves into AddDeviceServices+App.swift
instead of a new App file, and the debug listing and join fixture are
one Fixture value, so the App gains no new file and no new static.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT join line: the join flow tests' fake is not main-actor isolated

MachineJoinService's closures are @Sendable, so a fake nested in the
@MainActor suite could not be touched from them and the test target did
not compile (the app compile does not build tests).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT join line: the nonisolated fake builds its models on the main actor

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux NEXT: join consent: the app capability check reads the bundled acpmux's capability list; test events made with no source

check-app-capability-references read only cmux-tui-core and its direct crates, so it refused the app's
gate on machine-consent-v1, which the bundled acpmux defines and advertises in initialize
(cmux-tui/crates/acpmux/src/capabilities.rs, from #27216). It now also reads that list; a gate on a
name acpmux does not define still fails (red/green in its test).

The strict person check's tests made CGEventSources, which can hang inside SkyLight on a headless fleet
Mac and cut the suite short; the events are now made with no source and their fields set by hand.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* Check a consent grant's ticket where its bytes go out, not before they are queued

Round 15 P1: the model checked the Labs switch, its generation and the settings epoch, then handed the
grant to AcpmuxCheckedLine, which only enqueues it on its own queue; Labs could turn off (or a settings
load come) between that check and the socket write, and the confirmed grant still reached acpmux.

A grant now takes a ticket from the model's JoinConsentGrantGate when it starts. featureSwitched() and,
in the app, every settings load (observed in the load itself, at snapshot's and loadCount's will-set)
invalidate the gate. The line writes a grant only through the ticket, on the queue that writes the
socket and under the gate's lock (AcpmuxCheckedLine.send(_:through:)): a stale ticket sends nothing,
closes the connection and the call answers nil. Revokes carry no ticket.

The fakes now record at the write point, after a seam (Fake.beforeWrite, the interleaving scheduler's
write boundary) that can flip Labs or load settings between the model's last check and the write.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* Hand the consent watch's caller the moment both watches are armed, and follow a folder made in it

Round 15 P2: MachineConsentChanges.events(armed:) runs `armed` after the folder watch (FolderChanges arms
in its events() call, on the folder or its nearest existing parent, not in its loop) and the file watches
are installed, before the first element and before the watch loop starts. The new test makes the folder
and its consent file inside that window, then writes the file in place (which only the file's own watch
sees) and needs an element for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* Show the consent sheet for a Mac Screen refusal only on a record that read

Round 15 P2: with machine-consent-v1 announced, the join status joined and machine_consent_get failing,
the joined check fell back to the status and a refused ticket showed the one-time sheet. The sheet now
needs the record read, or an acpmux without the capability (which has granted nothing), as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* Close the consent sheet when its surface is granted from Settings

Round 15 P3: a grant of the surface the open sheet asks for (Settings or the sheet's own Allow) closes
it once the write went through; another surface's grant, or one that failed or was cancelled, leaves it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* Read only acpmux's CAPABILITIES initializer, comments masked, in the capability check

Round 15 P3: the check took any `id: "..."` in capabilities.rs, so a commented-out entry, a doc line or
another constant could stand in for an advertised capability. It now blanks Rust comments (line, block,
nested; markers inside string and char literals are kept), finds the `CAPABILITIES` `&[...]` initializer
by bracket depth (brackets in strings skipped) and reads ids there alone. The test's red cases: an id in
a line comment, a nested block comment, a doc comment and another constant; green: an id after a string
holding a bracket and a comment marker.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* Test a click on the consent sheet's Allow through its own sendEvent and AppKit's button tracking

Round 15 P3: the mouse-down goes to JoinConsentAskPanel.sendEvent, the button tracks it with AppKit's
loop, which takes the queued mouse-up and makes it NSApp.currentEvent (the production eventInHand), and
sends its action inside the dispatch. A click the person check refuses, and one released on Deny, write
nothing; the person's click on Allow grants. The events' HID origin stays the strict check's own test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* Format the join consent's web test as vp fmt writes it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* Keep AcpmuxPersonLine's call(_:_:) for AgentDialogLine and send the grant gate as @Sendable

The ticketed write added call(_:_:through:) with a default, which no longer satisfies AgentDialogLine's
call(_:_:); the gate is now an overload beside it. The consent line's gate closure is spelled @Sendable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* Pass a grant ticket in the person line's tests, and show a stale one sends nothing over the real line

JoinConsentPersonLineTests still called the line's set without a ticket. The new test drives the app's
real AcpmuxPersonLine and AcpmuxCheckedLine against the fake acpmux: a ticket invalidated before the line
writes is dropped on the line's queue, acpmux sees only initialize and person_prove, and the connection
closes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* Drop the consent sheet's sendEvent click test: the panel is never key on a fleet host

The never-shown, non-activating panel takes the mouse-down without passing it to the button, so AppKit's
tracking never runs and the queued mouse-up stays (it failed the same way on the code before round 15).
The production event path stays a known gap in the PR body.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* cmux-tui: machine-join.sh says what a failed resume and an exit 3 need (#27456)

A resume whose hub cannot be reached now asks for the same line again
(recovery needs the original token) and names no origin, since a
tokenless resume asked the pending hub. An exit 3 that is not a
revocation shows acpmux's own reason, redacted, before the line.


Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

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

* cmux-tailnet: libtailscale's fds are close-on-exec, so spawned processes do not inherit them (#27455)

libtailscale created its listener and connection socketpairs without
SOCK_CLOEXEC and received accepted connections without MSG_CMSG_CLOEXEC,
so every process the daemon spawned held copies of those sockets for its
whole life. A copy keeps the socket open after we close ours: libtailscale
sees no EOF and keeps the tailnet listener or connection alive.

- patches/0002: SOCK_CLOEXEC and MSG_CMSG_CLOEXEC on Linux; elsewhere
  FD_CLOEXEC right after creation and receipt.
- node.rs: also set FD_CLOEXEC on every fd libtailscale hands us.
- tests/close_on_exec.rs: a spawned child must hold none of the sockets
  a tailnet listen creates (Linux, /proc/<pid>/fd).


Agent-Session: https://cmux.com/deeplink/agent?id=aid-1288bbb2b5d2f5c6b66a

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

* pair-e2e: run the Mac app under the rig's own keychain (#27370)

* pair-e2e: run the Mac app under the rig's own keychain

Step e2b11b0c's sample (the preparing evidence from #27345): the app waits in
MobileHostMacIdentityStore.keychainAdd -> SecItemAdd -> SecItemAdd_osx ->
StorageManager::defaultKeychainUI -> makeLoginAuthUI -> AuthorizationCopyRights,
authd asks for system.keychain.create.loginkc and SecurityAgent runs
loginKC:queryCreate; `security default-keychain` says the fleet GUI Mac's
console account has no default keychain. The create-login-keychain dialog is not
covered by the store's interactionNotAllowed context, so nothing answers it and
hostd never starts. DeviceHub.loadIdentity waits behind it on the
StorageManager lock.

The rig now creates its own file keychain next to the tagged app, puts it
first in the user search list and makes it the default for the run, and in
its finally and in forget_tag (the sim script's EXIT) puts the recorded list
and default back (an unset default unset again) and deletes it. The account's
own keychains and items are never read, changed or deleted. forget_keychain's
security calls get a timeout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* pair-e2e rig keychain: keep the record until the restore reads back; refuse a guessed default; delete items only in the rig's keychain

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* pair-e2e rig keychain: put the account's keychains back first in forget_tag, and even when other cleanup fails; bound defaults delete

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* pair-e2e rig keychain: the step's own HOME needs Library/Preferences; restore an empty list and unset default by delete-keychain

Step 8703f0e6: the rig's list-keychains -s and default-keychain -s exited 0
but the app still had no default keychain. A fleet step runs with its own
empty HOME (build-fleet buildEnv: HOME=<job>/home), the Security framework
keeps the user domain's keychain preferences under $HOME/Library/Preferences,
and with no such folder `security ... -s` reports success and keeps nothing
(checked on macOS 27 with a scratch HOME). That empty HOME is also the root
cause of the original prompt: the app sees no default keychain.

start() now makes that folder and reads back the list and default it set.
`security ... -s` with nothing after it changes nothing, so end() restores
only a recorded non-empty list or a set default, and leaves an empty list and
an unset default to delete-keychain. Paths compare canonically.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* pair-e2e: the link opens the phone app, so its first auth after the code presents it

Step d7afa734 (the rig's keychain in place): the page showed a code in 6 s, but
the phone had dialed the Mac at launch, before it had the code; its auth
answered the challenge with no token and waited on the Mac's Allow, and the
code opened into that app was never presented (the token goes out only in an
auth's challenge reply, LinkPairing). The rig now ends the app and lets the
link open it, as the Camera does; the Mac answers the earlier ask with the
code (prompts.forget). The summary says whether the phone took the code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* pair-e2e: an openurl timeout never prints the link

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* pair-e2e: the phone gets the code in its launch environment where its DEBUG app takes one

Steps d7afa734 and c8c0dec1: `simctl openurl` made the simulator ask "Open in
cmux?", nothing on the CI Mac answered, and the code never reached the app
(both phone-paired.png captures show the alert). On a ref whose DEBUG phone
app takes CMUX_MOBILE_PAIR_URL at launch, the rig restarts the app with the
code in SIMCTL_CHILD_CMUX_MOBILE_PAIR_URL (environment only, never argv or a
log); other refs keep openurl.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* pair-e2e: a failed launch with the code raises with neither the code nor argv

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* pair-e2e: the rig's Mac is listed as '0 e2e rig Mac', first in the phone's Devices list

Step 423a0bae paired, but its Settings > Devices captures showed only the
dev account's other Macs (100+, sorted by name). The DEBUG name override
puts the rig's Mac on top and keeps the host's name out of the captures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* pair-e2e rig keychain: only under a step's own HOME

Its keychain preferences are then this run's alone (one HOME per step), so
two runs can never record or restore each other's keychains.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

---------

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

* ci: the devbox rebake publishes its manifest branch in two pushes (#27367)

Every publish since 10-28 was refused: a single push of a new
devbox-rebake/* branch makes GitHub compute the new commits against the
whole repository, which times out ("Unable to determine if workflow can
be created or updated due to timeout; `workflows` scope may be
required"). Push the branch at the baked commit first (no new commit),
then the one manifest commit on top.


Agent-Session: https://cmux.com/deeplink/agent?id=aid-7a592ef92c66f080e3cc

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

* Wait for terminal native prompt upload state (#25891)

* Test terminal upload state races

Agent-Session: https://cmux.com/deeplink/agent?id=aid-e3d98319686ad3ee9cfa

* Wait for terminal native upload states

Agent-Session: https://cmux.com/deeplink/agent?id=aid-e3d98319686ad3ee9cfa

* Verify the shared host upload event route

Agent-Session: https://cmux.com/deeplink/agent?id=aid-e3d98319686ad3ee9cfa

* Test native upload readiness and cancellation

Agent-Session: https://cmux.com/deeplink/agent?id=aid-e3d98319686ad3ee9cfa

* Await upload subscriptions and cancel terminal waiters

Agent-Session: https://cmux.com/deeplink/agent?id=aid-e3d98319686ad3ee9cfa

* Test the upload adapter shared-host boundary

Agent-Session: https://cmux.com/deeplink/agent?id=aid-e3d98319686ad3ee9cfa

* Keep native uploads on the shared Chromium host

Agent-Session: https://cmux.com/deeplink/agent?id=aid-e3d98319686ad3ee9cfa

* Route native prompt upload errors through the pane's string table

pane-english failed on the Error texts promptUploadState.ts and
nativePromptUpload.ts added. They now use catalog strings: a new
upload.cancelled (all 21 languages) plus the existing upload.notCompleted,
upload.notStarted and error.nativeUnavailable. Error codes are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-ab1f035a409b977caceb

---------

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

* perf(ios): apply workspace deltas without full refetches (#27299)

* test(ios): cover title deltas without workspace refetch

Regression for workspace-sync title events: only the affected workspace changes and no new daemon request is sent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-21ea68086c64a500cde6

* perf(ios): apply workspace deltas off main actor

Workspace title and agent events now patch affected rows without fetching the complete workspace tree. Topology gaps still recover with a full snapshot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-21ea68086c64a500cde6

* test(ios): benchmark workspace delta stream

The same fixture reports main-thread parse time and full-fetch rates for the legacy and incremental event paths.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-21ea68086c64a500cde6

* test(ios): measure workspace sync main-thread cpu

Record thread CPU and wall elapsed time from the same workspace stream fixture used for the fetch-rate comparison.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-21ea68086c64a500cde6

* fix(ios): harden workspace delta projection recovery

Reject stale or cross-registry events, preserve row order, retain hidden tab metadata, and fall back to snapshots when a delta cannot be applied safely.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-21ea68086c64a500cde6

* fix(ios): preserve prior workspace delta metadata

Keep old hidden-tab indexes until replacement cleanup removes them, then rebuild metadata from the new row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-21ea68086c64a500cde6

* Fix workspace delta replay and setup recovery

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-21ea68086c64a500cde6

* Fix idempotent projection assertions

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-21ea68086c64a500cde6

* Answer resource requests in sync recovery test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-21ea68086c64a500cde6

* Fix projection indexes after row changes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-21ea68086c64a500cde6

* Keep metadata deltas incremental and fix replay harness

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-21ea68086c64a500cde6

* Preserve workspace consumers and verify snapshot recovery

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-21ea68086c64a500cde6

* Replay in-flight snapshot events and apply tab moves

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-21ea68086c64a500cde6

* Coalesce coarse workspace refresh bursts

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-21ea68086c64a500cde6

* Preserve selected tab across insertion and removal

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-21ea68086c64a500cde6

* Fix iOS benchmark test artifacts

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-21ea68086c64a500cde6

---------

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

* cmux-tui: remove ~290 panic-ratchet hits added since the Oct 22 baseline

Real panic risks that landed while the Rust panic ratchet was red:
- acpmux: 129 slice/array indexing hits and all 40 string slices
  (UTF-8 boundary panics on agent/user text), incl. a real escape-skip
  panic in linux_sandbox aliases and a missing BTreeMap key in watches.
- cmux-holder (48) and cmux-flow-trace (11): new crates, now at 0
  indexing hits; wire frames, ring and poll results use get/first_chunk.
- cmux-tui, cmux-tui-core, cmux-git, cmux-server-core, cmux-palette-ranker,
  cmux-app-host, cmux-browser-host: every new slice/string-slice hit.
- cmux-remote: 5 serde_json::to_value expects in admin dispatch now
  return IdentityError::Json.
Behavior on valid input is unchanged; bad input takes the existing error
or fallback path instead of panicking.

* rust panic baseline: lower fixed counts; raise string-key JSON indexing only

After removing the new slice/string-slice/expect hits, what is still over the
Oct 22 baseline is string-key serde_json::Value indexing (v["key"]), which
clippy counts as indexing_slicing but which never panics on read. Raise
indexing_slicing for those 8 crates to the measured counts; lower every
count that went down; add cmux-holder, cmux-flow-trace, cmux-process,
cmux-kitty-media and cmux-tailnet at deny. Measured on the Linux clippy run
40904978004. Also fix clippy explicit_auto_deref in acpmux agent_link.

* NEXT Home: attach any number of files, sent as messages within the owner's part limit (#27183)

* test(home): a send with 65 files reaches the owner within its part limit

Red first: the Channels composer stops at 10 files, the daemon client refuses
a send naming more than 10, and HomeStore refuses more than 16 parts.

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

* NEXT Home: any number of attachments, sent as messages within the owner part limit

Leo, 2026-10-26: "why the FUCK SHOULD THERE BE A FUCKING SIZE CAP ANYWAYS ... I ALREADY SAID
WE NEED ARBITARRILY LARGE FILES OF ANY TYPE".

The Channels composer took at most 10 files (MAX_ATTACHMENTS), the daemon client refused a
send naming more than 10, and the native composer and HomeStore refused past the owner 16
parts per message. That 16 is the conversation protocol bound on one message
(cmux-conversation MAX_PARTS), so a send with more files now goes as several messages, in
order, the text with the first; each has its own idempotency key and pending row.

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

* style(home): format the any-count change

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

* NEXT Home: the parts overload splits too, so the native composer sends any number of files

Review finding: the native composer (MessagesLab HomeProjection) sends through
HomeStore.send(conversation:parts:uploads:), which still threw tooManyParts past 16
parts. The split now lives there, as consecutive slices in order; the text overload
builds its parts and calls it, so there is one copy. A send past one message skips link
cards instead of being refused.

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

* NEXT Home: a send past one message goes strictly one message after another

Review finding: later messages of a split send kept uploading and submitting after an
earlier one failed, so they could commit first and the retry put the failed one after
them. Now the messages of one send form a chain: each starts only when the one before
it committed, and a failure holds the rest (out of the conversation queue, so other
sends still go) until its retry commits or it is discarded. The web store does the same.

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

* style(home): format

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

* fix(home): send-chain helpers inside the HomeStore extension

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

* NEXT Home: a split send keeps its order across reconnects, double retries and lost answers

Review findings at c57eb171:
- web: the chain now rides on each pending row (PendingSend.chain), so a reconnect keeps it;
  a Retry of any of its messages goes on from the earliest failed one, in order.
- web: a chain already going ignores another Retry (no concurrent same-key sends).
- web: a message the owner event settled before its answer failed counts as taken, so the
  rest of the send goes on instead of waiting behind it.
- native: chains are pruned even when no upload or queued send is left, and a reused key
  leaves any older chain, so a later split send never stalls after its first message.

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

* NEXT Home: a revoked or offline-started message never strands a split send

Review findings at 3d71bd3e: an owner revocation dropped log entries without their uploads,
queue places and chain links, so the rest of a split send never went; and a chain message
started while offline only set the reconnect flag, without the offline queue and deadline.
Revocation now drops orphaned jobs (which advances chains), and a chain head started offline
waits like any send made while offline (waitForReconnect, shared with queueUploadWhileOffline).

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

* NEXT Home: each message of a split send uploads its own link card pictures

Review finding at 5b5e5c2f: a link preview picture in a later message was uploaded with the
first message, so the later one had no upload job to redo after the owner swept the picture.
A link preview image now counts as a blob its own part names.

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

* fix(home): drop a stray brace

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

---------

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

* NEXT: paste a clipboard image of any size into a terminal (#26957)

* NEXT: test that a clipboard image over 10 MiB pastes its saved path

Red on base: TerminalPasteboard.saveImage returns nil for an image over
maxClipboardImageBytes (10 MiB), so the paste delivers nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-3f783e95b72165fe52b9

* NEXT: paste a clipboard image of any size into a terminal

Drop maxClipboardImageBytes (10 MiB). An image over it made saveImage
return nil, so Cmd-V pasted nothing and showed no message. The image is
already in memory as pasteboard Data; it is written whole to the temp
file and its path pasted, as for a small image.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-3f783e95b72165fe52b9

---------

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

* Never write a gated grant once its line was closed or timed out, even if its write was already queued

Round 16 P1: AcpmuxPersonLine.close() (the deadline's too) resumed its waiters at once, while the
connection's close queued behind a grant already on AcpmuxCheckedLine's queue, and the timeout left the
grant's ticket current. The model could report the failure, read again and take a revoke on another
connection, and then the old grant went out and granted again.

AcpmuxCheckedLine.cancel() now sets a cancelled flag at once, on the caller's thread, before close()
resumes anyone. A gated write checks it on the queue under the same lock, held through the gate's ticket
check and the write, so from the moment cancel() returns no gated write starts.

The test holds the line's queue (a test-only enqueue seam) across the deadline: the grant answers nil,
a revoke on a second connection goes through, then the queue runs; the fake acpmux (now several
connections over one record) sees the revoke alone and Screen sharing stays off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* feat(acpmux): route lifecycle controls through owner-authorized mesh senders (#27050)

* feat(acpmux): define mesh lifecycle control relay seam

Agent-Session: https://cmux.com/deeplink/agent?id=aid-ca805d5a919ddc878a43

* feat(acpmux): dispatch mesh lifecycle controls through peer rules

Agent-Session: https://cmux.com/deeplink/agent?id=aid-ca805d5a919ddc878a43

* fix(acpmux): validate control params through relay

Agent-Session: https://cmux.com/deeplink/agent?id=aid-ca805d5a919ddc878a43

* test(acpmux): cover mesh control receiver dispatch

Agent-Session: https://cmux.com/deeplink/agent?id=aid-ca805d5a919ddc878a43

* fix(acpmux): authorize mesh lifecycle controls

Agent-Session: https://cmux.com/deeplink/agent?id=aid-ca805d5a919ddc878a43

* fix(acpmux): satisfy mesh wire lint

Agent-Session: https://cmux.com/deeplink/agent?id=aid-ca805d5a919ddc878a43

* fix(acpmux): fence mesh control redelivery

Agent-Session: https://cmux.com/deeplink/agent?id=aid-ca805d5a919ddc878a43

* fix(acpmux): persist sender-bound control execution fences

Agent-Session: https://cmux.com/deeplink/agent?id=aid-ca805d5a919ddc878a43

* feat(acpmux): expose owner-authorized mesh lifecycle commands

Agent-Session: https://cmux.com/deeplink/agent?id=aid-ca805d5a919ddc878a43

* test(acpmux): isolate mesh lifecycle roots and sender boundaries

Agent-Session: https://cmux.com/deeplink/agent?id=aid-ca805d5a919ddc878a43

* test(acpmux): configure reviewed fake mesh start mode

Agent-Session: https://cmux.com/deeplink/agent?id=aid-ca805d5a919ddc878a43

* fix(acpmux): refuse controls without durable replay storage

Agent-Session: https://cmux.com/deeplink/agent?id=aid-ca805d5a919ddc878a43

* test(acpmux): keep mode refusal fixture durable

Agent-Session: https://cmux.com/deeplink/agent?id=aid-ca805d5a919ddc878a43

* fix(next): cancel notification watchers on teardown (#27266)

Agent-Session: https://cmux.com/deeplink/agent?id=aid-22c041acd29280605b25

* Keep a reply to a closed client from killing the test process in the fake acpmux

Without the round 16 fix, the timed-out grant reached the fake, whose reply then hit SIGPIPE and took
the test process down. SO_NOSIGPIPE turns that into a failed send, so the test fails on its own
expectations instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6

* ci(signing): the owned-Mac signing cap skips queued runs and retries a 5xx, so it stops failing every pick closed (#27461)

owned_signing_jobs read every unfinished run status and gave up when one
listing passed a page. nightly.yml and cmux-next-dev-daily.yml each keep over
100 queued runs, so every nightly-next pick since the nightly slots got their
pin label printed "could not be counted, so none is offered" (runs
40897459413, 40905688542), and one more gave up on a single 502
(40899583793). A queued run has started no job, so none of its jobs has a
pick or a pin: it is no longer read. A GitHub 5xx is retried twice; a 4xx or
a lasting 5xx still fails closed.


Agent-Session: https://cmux.com/deeplink/agent?id=aid-85ca59f74f02d81734c3

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

* cmux-tailnet: tailscale_accept keeps the fd of a record read in parts (#27463)

libtailscale's TsnetAccept reads each 64-byte accept record in a loop, and
after the first read it drops the control-message buffer and overwrites
oobn with the next read's. When the first read comes back short, the
SCM_RIGHTS fd it carried is lost: it leaks, and the accept fails with "got
0 control messages". An EINTR with nothing read also added -1 to the byte
count, and the next slice expression panicked.

patches/0003 parses the control message of every read, collects the fds
it receives, and closes them if the accept fails. tests/accept_partial_record.rs
writes records into libtailscale's end of a real listener in two sends,
the fd on the first, and checks that accept returns that fd and that a
failed accept leaks none.


Agent-Session: https://cmux.com/deeplink/agent?id=aid-1288bbb2b5d2f5c6b66a

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

* fix(next): choose model and effort together in picker rows (#26521)

* fix(next): choose model and effort together in picker rows

Agent-Session: https://cmux.com/deeplink/agent?id=aid-0ae37658532734c6f9bc

* fix(next): give inline effort choices room beside models

Agent-Session: https://cmux.com/deeplink/agent?id=aid-0ae37658532734c6f9bc

* fix(next): gate combined effort choices on caller support

Agent-Session: https://cmux.com/deeplink/agent?id=aid-0ae37658532734c6f9bc

* fix(next): retain session ultracode in inline effort row

Agent-Session: https://cmux.com/deeplink/agent?id=aid-75194ce2fd161b7e7e1c

* test(next): cover inline ultracode effort option

Agent-Session: https://cmux.com/deeplink/agent?id=aid-75194ce2fd161b7e7e1c

* fix(next): route current effort picks through option handler

Agent-Session: https://cmux.com/deeplink/agent?id=aid-0ae37658532734c6f9bc

* fix(next): preserve effort fallback for provider rows

Agent-Session: https://cmux.com/deeplink/agent?id=aid-0ae37658532734c6f9bc

* test(next): separate provider effort fallback coverage

Agent-Session: https://cmux.com/deeplink/agent?id=aid-0ae37658532734c6f9bc

* test(next): use distinct provider offer keys

Agent-Session: https://cmux.com/deeplink/agent?id=aid-0ae37658532734c6f9bc

* fix(next): focus prompt after inline effort picks

Agent-Session: https://cmux.com/deeplink/agent?id=aid-0ae37658532734c6f9bc

* test(next): isolate ModelPicker effort focus regression

Agent-Session: https://cmux.com/deeplink/agent?id=aid-0ae37658532734c6f9bc

* fix(next): keep picker width stable across harness changes

Agent-Session: https://cmux.com/deeplink/agent?id=aid-0ae37658532734c6f9bc

* fix(next): scope effort rail sizing to left placement

Agent-Session: https://cmux.com/deeplink/agent?id=aid-0ae37658532734c6f9bc

* test(next): load menu after JSDOM setup

Agent-Session: https://cmux.com/deeplink/agent?id=aid-0ae37658532734c6f9bc

* test(next): assert translated effort fallback label

Agent-Session: https://cmux.com/deeplink/agent?id=aid-0ae37658532734c6f9bc

* fix(next): opt in to inline model effort controls

Agent-Session: https://cmux.com/deeplink/agent?id=aid-0ae37658532734c6f9bc

* test(next): cover explicit empty effort capabilities in picker

Agent-Session: https://cmux.com/deeplink/agent?id=aid-0ae37658532734c6f9bc

* fix(next): honor explicit empty effort capabilities

Agent-Session: https://cmux.com/deeplink/agent?id=aid-0ae37658532734c6f9bc

* test(next): share model picker regression setup

Agent-Session: https://cmux.com/deeplink/agent?id=aid-0ae37658532734c6f9bc

* fix(next): preserve live effort capability states

Agent-Session: https://cmux.com/deeplink/agent?id=aid-0ae37658532734c6f9bc

* fix(picker): preserve omitted effort capabilities

Agent-Session: https://cmux.com/deeplink/agent?id=aid-2942206402f80d650591

* fix(picker): carry effort capability provenance

Agent-Session: https://cmux.com/deeplink/agent?id=aid-2942206402f80d650591

* fix(picker): reserve inline effort width

Agent-Session: https://cmux.com/deeplink/agent?id=aid-2942206402f80d650591

* test(picker): size probe with real effort segments

Agent-Session: https://cmux.com/deeplink/agent?id=aid-2942206402f80d650591

* fix(picker): preserve omitted effort capabilities

Agent-Session: https://cmux.com/deeplink/agent?id=aid-75194ce2fd161b7e7e1c

* fix(picker): preserve effort capability provenance

Agent-Session: https://cmux.com/deeplink/agent?id=aid-75194ce2fd161b7e7e1c

* fix(picker): style width probe segments separately

Agent-Session: https://cmux.com/deeplink/agent?id=aid-75194ce2fd161b7e7e1c

* fix(picker): keep live effort capabilities

Agent-Session: https://cmux.com/deeplink/agent?id=aid-75194ce2fd161b7e7e1c

* test(picker): preserve unmarked effort fixture

Agent-Session: https://cmux.com/deeplink/agent?id=aid-75194ce2fd161b7e7e1c

* fix(picker): preserve fast on current effort picks

Agent-Session: https://cmux.com/deeplink/agent?id=aid-2942206402f80d650591

* test(picker): follow inline effort controls

Agent-Session: https://cmux.com/deeplink/agent?id=aid-2942206402f80d650591

* test(picker): wait for a row's effort segments, not the hidden width probe

The width probe also carries .acpmux-quick-efforts and renders first, but it is
visibility:hidden, so waitFor on the first match never resolved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-ab1f035a409b977caceb

* test(picker): follow the inline effort control in gallery plays and drop a fixed wait

The Effort submenu is gone when the current model's row carries its levels, so
composer#reasoning-menu, composer-pickers#effort-focus and #claude-effort-up
(and effort-menu, effort-press-drag-release) now read the inline segments; the
composer helper still opens the submenu where a row has none. The ModelPicker
focus test waits for the prompt's focus with a bounded poll instead of one
setTimeout(0), which the test determinism gate flagged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-ab1f035a409b977caceb

* fix(picker): send a searched Fast effort pick on the running model as one combo

Review BLOCK at bf161bc5: the current-model shortcut turned Fast on through the
toggle, which restores a remembered ultrafast tier. A…

This branch was previously deployed

1 inactive deployment
ci-routing-agent — c9f0df8e Deployed Oct 29, 2026 by teamleaderleo via wait for same-key tree publisher #76231
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:normal Risk tier (docs/ci/risk-tiers.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant