Skip to content

base red: Takes back #29497: the remote-start refusal table, with its older-daemon codes, moves to CmuxNextDaemonService (app_lines +51) - #29590

Merged
cmux-bot[bot] merged 2 commits into
feat-cmux-nextfrom
base-red/remote-start-refusal-move
Oct 31, 2026
Merged

cmux-bot[bot] merged 2 commits into
feat-cmux-nextfrom
base-red/remote-start-refusal-move

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Base red, priority zero (tip arch red; lead 18:5xZ). 270912b (#29497, "a first chat an older daemon refused updates the machine and starts once more") adds 51 CmuxNextApp lines with no exception row. The repo rule run on that commit alone refuses it (+51). It is one of the three culprits of the tip's app_lines +139 over b6eb849; #29566 covers #28686, and the #28786 PR covers #28786.

Takes back #29497: its own refusal codes leave CmuxNextApp, together with the refusal table they belong to. The behavior is unchanged.

  • cmux NEXT Cloud: a first chat an older daemon refused updates the machine and starts once more #29497's AgentTabRemoteHost.olderDaemonRefusals moves, with its doc, onto the refusal type as AgentRemoteStartRefusal.olderDaemonCodes.
  • AgentRemoteStartRefusal moves unchanged in behavior from CmuxNextApp/AgentTabs+RemoteStart.swift to CmuxNextDaemonService, together with AgentTabRefusedHost.
    • It is the one table from an agent-session-start refusal to the person's words, through RefusalStrings.
    • Its new place is next to RefusalStrings, whose Refusals table it already reads: same bundle, same keys.
    • It has public access and an explicit init with the same defaults.
  • The App reaches both through its CmuxNextDaemonService re-export.
  • The defaults guard tracks AgentRemoteStartRefusal.folderNeededCode by its file path, because CloudStartSession.startFailure references it. So the App keeps static let folderNeededCode = "app.folder_needed" in AgentTabs+RemoteStart.swift, as a one-line extension of the moved type. The moved type uses an internal appFolderNeeded with the same string, and the new AgentRemoteStartRefusalCodeTests asserts that the two are equal.
  • AgentTabRemoteHost.updateMachine's doc comment is shorter, because the list of codes now lives on olderDaemonCodes.

CmuxNextApp -86.

Testing

The watcher's command (arch-ratchet.py, row app_lines):

check-app-size.sh --base origin/feat-cmux-next: ok. The namespace lint: OK. prepush-gate.py: no new violations.

Observed / Not run

Observed:

  • The checks above.
  • At 87d6fb0: the compile (job 7077d6a64720681f83dc7ae9) passed. AgentTabRemoteStartTests 8/8, CloudStartSessionTests 25/25 and CloudAppLinkOwnerAnswerTests 5/5 passed (step 2234b0ab9ef002f3df34d066).
  • At this head bc518d0: compile job 17566911cb1bfb2458fb6ac2 and suite step 1526e5dfee67930529706d0f (with AgentRemoteStartRefusalCodeTests) are pending. Their results are posted as a comment.
    Not run: no local Swift build (fleet only). No tagged build: no behavior change.

Changelog

none

Proof

No capture: no visible change; a refusal table moves between targets with the same strings.

… older-daemon codes, moves to CmuxNextDaemonService (app_lines)

270912b (#29497, "a first chat an older daemon refused updates the
machine and starts once more") adds 51 CmuxNextApp Swift lines with no
exception row; arch-ratchet's app_lines row refuses it alone (--base
270912b^ --head 270912b: +51), part of the tip's red (STATUS, lead
18:5xZ).

- #29497's own refusal codes (AgentTabRemoteHost.olderDaemonRefusals) move,
  with their doc, onto the refusal type as
  AgentRemoteStartRefusal.olderDaemonCodes.
- The refusal type itself (AgentRemoteStartRefusal: the one table from an
  `agent-session-start` refusal to the person's words, through RefusalStrings)
  and AgentTabRefusedHost move unchanged in behavior from
  CmuxNextApp/AgentTabs+RemoteStart.swift to CmuxNextDaemonService, next to
  RefusalStrings, whose Refusals table they already read (same bundle, same
  keys). Public access, an explicit init with the same defaults.
The App reaches them through its CmuxNextDaemonService re-export; the tests
are unchanged.
@lawrencecchen lawrencecchen added the risk:low Risk tier (docs/ci/risk-tiers.md) label Oct 31, 2026
@github-actions

github-actions Bot commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

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

Review bc518d0: 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=pr29590 -f ref=base-red/remote-start-refusal-move -f platform=macos -f backend_mode=local

Default changes

No defaults change at bc518d0aa993.

Shipped in:

@github-actions github-actions Bot added risk:normal Risk tier (docs/ci/risk-tiers.md) and removed risk:low Risk tier (docs/ci/risk-tiers.md) labels Oct 31, 2026
@lawrencecchen

Copy link
Copy Markdown
Contributor Author

Review (claude-review-subagent): SHIP at 87d6fb0

Correctness first. Static review only: no Swift compile or test on this machine (fleet only).

Equivalence

  • I compared the moved block with HEAD~1:.../CmuxNextApp/AgentTabs+RemoteStart.swift mechanically, with the public keywords removed. The only differences are the 3 imports, the explicit init, the AgentTabRefusedHost init and olderDaemonCodes. Every member, every errorDescription case, key and default string is the same, byte for byte.
  • The init (code:machine:folder:detail:keyedRestartSafe:) has the same order and nil defaults as the old memberwise init. All call sites (App: 4 files; tests: keyedRestartSafe: true, folderNeededCode, teamMachine(machine:)) match.
  • olderDaemonCodes = [AgentSessionAttachError.teamMachine, "agent_session.host_unverified"]. This is the same set as the old olderDaemonRefusals. The catch in AgentTabRemoteHost uses it. No references to olderDaemonRefusals remain.
  • Localization: RefusalStrings.text uses table: "Refusals", bundle: .module in CmuxNextDaemonService. Thus the lookup bundle is the same. agentRemoteStart occurs only in the new file and in CmuxNextDaemonService/Resources/Refusals.xcstrings. No script under scripts/ keys these calls by source path: localize_changes.py gets the catalog of an existing key from the key, not from the path.

Compile (static reading)

  • CmuxNextDaemonService already depends on CmuxNextAgentPane and CmuxNextDaemon and uses uiSwiftSettings (MainActor default isolation, InternalImportsByDefault). The struct is nonisolated, which is the same as before. RefusalStrings is public nonisolated.
  • public import CmuxNextAgentPane and public import Foundation cover the public signatures (AgentPaneHostRefusal, AgentPaneHostProviding, AgentPaneHandshake, LocalizedError). CmuxNextDaemon types (AgentSessionAttachError.teamMachine, AgentSessionAttachClient.startUnsupported, both public static let) occur only in bodies and in static initializers, so an internal import is sufficient.
  • machineName is a protocol requirement, and the public implementation of the struct is its witness. Sendable is explicit through Error and AgentPaneHostProviding, and the stored properties are all Sendable. The tuple-assign init pattern is already used in CmuxNextAgentPane.
  • The App and the @testable import CmuxNextApp tests see both types through @_exported import CmuxNextDaemonService (DaemonServiceExport.swift). The repo has exactly one declaration of each name, so there is no clash.

Gates

  • arch-ratchet.py --base HEAD~1 --head HEAD: app_lines -86, no architecture count rises. Over 270912bba17^..HEAD: app_lines -35, no architecture count rises.
  • arch_rules/layer_gate.py --base HEAD~1 --head HEAD: ok (Package.swift did not change).
  • prepush-gate.py --base HEAD~1 --head HEAD: no new violations. This ran on Linux, so the swiftformat and swiftlint layers may not have run.
  • PR body: the title starts with base red: and a line starts with Takes back #29497: <why>, so it matches risk_tier.TAKES_BACK. The PR has the label risk:normal and touches no high-tier path.

Non-blocking notes

  • The defaults guard (scripts/ci/defaults_guard.py check --base HEAD~1 --head HEAD) reports 2 "changed defaults". The key moved from tunable-source.CmuxNextApp/AgentTabs+RemoteStart.swift:folderNeededCode to tunable-source.CmuxNextDaemonService/AgentRemoteStartRefusal.swift:folderNeededCode, and the hash is the same (2d15d2b0...). Only the path moved, and the value did not change. A trusted SHIP receipt on the head clears the guard; this comment is that receipt.
  • The layer map (architecture-layers.json) has this plan for CmuxNextDaemonService: "drop AgentPane ...". This PR adds a 5th DaemonService file that imports CmuxNextAgentPane. The edge exists already and no ratchet count rises, but the move makes it a little harder to drop that edge later.
  • The doc comment of olderDaemonCodes names AgentTabRemoteHost.updateMachine, which is an App symbol, from a service file. This is prose only.

@lawrencecchen

Copy link
Copy Markdown
Contributor Author

Fleet evidence at 87d6fb0: compile with tests job 7077d6a64720681f83dc7ae9 (CmuxNext 240 s, app, cmux-tui passed). Suites step 2234b0ab9ef002f3df34d066: AgentTabRemoteStartTests 8/8, CloudStartSessionTests 25/25, CloudAppLinkOwnerAnswerTests 5/5 passed.

…e moved refusal uses its own constant, a test keeps them equal

The defaults guard tracks AgentRemoteStartRefusal.folderNeededCode by its
file path (it is referenced from CloudStartSession.startFailure) and reported
its move as a changed default. The App keeps
`static let folderNeededCode = "app.folder_needed"` in
AgentTabs+RemoteStart.swift, as an extension of the moved type; the moved
type uses an internal appFolderNeeded for folderNeeded(machine:) and its
message. AgentRemoteStartRefusalCodeTests asserts the two are the same code.
@lawrencecchen

Copy link
Copy Markdown
Contributor Author

Review (claude-review-subagent): SHIP at bc518d0

I reviewed the full diff over the merge base d208989 (87d6fb0 and bc518d0). The review is static only. I did no Swift compile or test on this machine.

Behavior: the same as the base

  • The moved AgentRemoteStartRefusal and AgentTabRefusedHost are the base code with public added and explicit inits that keep the same order and defaults. All errorDescription cases and keys are the same. olderDaemonCodes is the same set as olderDaemonRefusals (AgentSessionAttachError.teamMachine, agent_session.host_unverified), and the retry catch ... where uses it.
  • The folder-needed code: folderNeeded(machine:) builds the refusal with appFolderNeeded = "app.folder_needed". CloudStartSession.startFailure's case AgentRemoteStartRefusal.folderNeededCode reads the App's "app.folder_needed". The two strings are equal, so the case still matches. The only producer of the code is AgentTabRemoteStart line 75 (folderNeeded(machine:)).
  • Risk of the duplicate: low. Both are literals, and the new test fails if one changes. The webviews already hold a third copy of the same literal (startRefusal.ts and its test), so the wire string was already shared by value and not by symbol.
  • The defaults guard claim is true: schemas/defaults has tunable-source.CmuxNextApp/AgentTabs+RemoteStart.swift:folderNeededCode.

Compile, from the code

  • An App extension of a public type from another module can declare an internal static let: static stored properties are allowed in extensions, and the moved type has no member of that name. Its users are @MainActor (CloudStartSession, the CloudStartSessionTests suite, and the new test under the test target's main-actor default). So the static compiles whether the compiler makes it nonisolated (from the nonisolated type) or main-actor.
  • The internal appFolderNeeded is used in the bodies of public non-inlinable members (folderNeeded, errorDescription). The module has no library evolution, so this is allowed. It is nonisolated because the type is nonisolated.
  • The test: @testable import CmuxNextApp gives the internal folderNeededCode. The moved type comes in through the App's re-export of CmuxNextDaemonService. A plain struct in CmuxNextAppTests is main-actor by default.
  • The fleet compile passed at 87d6fb0. The new head adds only the items above.

Checks (run on this head over d208989)

  • defaults_guard.py check: exit 0.
  • arch-ratchet.py: exit 0, app_lines -86, no count rises.
  • prepush-gate.py: exit 0. With no arguments it judged nothing, because the head is already on the remote. With --base d2089899a6b --head HEAD: exit 0, no new violations.

PR body: it describes the App's one-line folderNeededCode, the internal appFolderNeeded, the new test and the shorter doc comment. It is current. The compile and suite results at this head are still pending in the body.

Nits (do not block)

  • The test's second #expect checks the English text "Choose folder" (REVIEW.md section 1, "Don't assert localized English"). Comparing with the folderNeeded case's RefusalStrings.format(...) result, or checking that the text is not the default "refused" message, would not depend on the locale.

@lawrencecchen

Copy link
Copy Markdown
Contributor Author

Fleet evidence at bc518d0: compile with tests job 17566911cb1bfb2458fb6ac2 (CmuxNext 236 s, app, cmux-tui passed). Suites step 1526e5dfee67930529706d0f: AgentTabRemoteStartTests 8/8, CloudStartSessionTests 25/25, CloudAppLinkOwnerAnswerTests 5/5, AgentRemoteStartRefusalCodeTests 1/1 passed. defaults_guard.py check: exit 0.

@lawrencecchen lawrencecchen added the ready-to-land Reviewed at this head; next-autoland merges it into feat-cmux-next when its only reds are base reds label Oct 31, 2026
lawrencecchen pushed a commit that referenced this pull request Oct 31, 2026
… older-daemon codes, moves to CmuxNextDaemonService (app_lines +51) (#29590)

merge-batch candidate: the squash merge of #29590 at bc518d0
@cmux-bot
cmux-bot Bot merged commit 4e2b02b into feat-cmux-next Oct 31, 2026
203 of 205 checks passed
@cmux-bot
cmux-bot Bot deleted the base-red/remote-start-refusal-move branch October 31, 2026 19:31

This branch was previously deployed

1 inactive deployment
ci-routing-agent — bc518d0a Deployed Oct 31, 2026 by lawrencecchen via namespace autofix #10091
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant