Skip to content

acpmux: run an agent CLI's own sign-in on a Cloud machine for the chat's sign-in card - #30188

Merged
teamleaderleo merged 6 commits into
feat-cmux-nextfrom
agent-login-rpc
Nov 1, 2026
Merged

teamleaderleo merged 6 commits into
feat-cmux-nextfrom
agent-login-rpc

Conversation

@teamleaderleo

Copy link
Copy Markdown
Collaborator

Leo, 2026-11-01, on building the one-click sign-in for Cloud machines now: "yes those are important".

In production cmux must not copy the Mac's Claude or Codex login to a Cloud machine (it may be against the agents' terms), and CodeRouter never serves production users. So the first Cloud chat on a machine with no login needs a sign-in that runs the agent's own login on that machine. This PR adds the acpmux side (Rust only); the transcript card is a separate webview PR built against this contract.

Contract (as built)

ACP extension methods on acpmux's JSON-RPC:

  1. _acpmux/agent_login/start {agent: "claude"|"codex", sessionId?} -> {loginId, url, userCode?, needsCode, expiresAtMs}
    • claude: runs claude auth login --claudeai, needsCode: true. codex: runs codex login --device-auth, needsCode: false, userCode set.
    • The link counts only when it is an https URL on an allowed host (claude.com, claude.ai, platform.claude.com, console.anthropic.com, auth.anthropic.com, auth.openai.com): no user part, no port, ASCII only, OSC-8 and ANSI stripped first. The read is bounded (20 s, 64 KiB kept; the rest is drained so the CLI never blocks).
    • One sign-in per agent: a new start stops the previous one (or answers busy while that one's finish is writing the login). The CLI runs in its own process group in a private 0700 temp folder, gets kill_on_drop, has PR_SET_PDEATHSIG on Linux, and is killed 15 minutes after it starts.
  2. _acpmux/agent_login/finish {loginId, code?, sessionId?} -> {signedIn, restarted}
    • claude: writes code + "\n" to stdin (one line of printable ASCII), then waits up to 120 s. codex: waits for the device flow until the sign-in expires.
    • Signed in means exit 0 and the login now in the agent's own store, read the way agent-auth.sh reads it, never by value: <CLAUDE_CONFIG_DIR or ~/.claude>/.credentials.json has claudeAiOauth; <CODEX_HOME or ~/.codex>/auth.json has tokens or a key. On macOS, Claude's exit 0 counts by itself, because Claude keeps its login in the keychain there and acpmux never reads it.
  3. _acpmux/agent_login/cancel {loginId} -> {} (does nothing when no such sign-in is running).
  4. _acpmux/agent_login/api_key {agent, key, sessionId?} -> {saved: true, restarted}. claude: sets env.ANTHROPIC_API_KEY in the Claude home's settings.json, keeping every other key; the write is 0600 and atomic, and a file that is not a JSON object is left as it is (failed). codex: runs codex login --with-api-key with the key on stdin.

After a sign-in or saved key:

  • On a cmux Cloud image (/etc/cmux/agent-auth.sh exists), ~/.config/cmux/agent-auth/<agent> is set to own\n (0600, folder 0700), so the image's launcher uses the person's login and drops CodeRouter.
  • With sessionId, that chat's agent restarts through Hub::restart_harnesses (the same path as _acpmux/sessions/restart_harness). If the chat is between turns it restarts now (restarted: true). If it is in a turn, it restarts when the turn ends (restarted: false); the turn is never cut.

Errors: JSON-RPC error with data.reason one of unsupported_agent, shared_machine, busy, cli_missing, no_url, bad_code (a code or key that is malformed or that the CLI refused), expired, failed, unknown_login. One addition to the brief: remote_refused, for a caller that may not run a sign-in (below).

  • Team machine: refused with shared_machine when /etc/cmux/vm-kind exists and is not owner, or cannot be read (fail closed, as cmux_agent_own_login_allowed).
  • Who may call: the unix socket, the local app, and the owner's own peer daemon (vm_boundary::owner). Never a Web page, a paired device, or a peer forwarding for one. Nothing the caller sends names a program, path or env.
  • Which CLI and env: the CLI is the one acpmux starts the agent with: the claude / codex harness profile (with Codex's -c/-p globals and CODEX_PATH), else the one on the login PATH. The env is acpmux's, minus ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN, ANTHROPIC_BASE_URL, CLAUDE_CODE_OAUTH_TOKEN, OPENAI_API_KEY, OPENAI_BASE_URL, CODEX_API_KEY, ANTHROPIC_CUSTOM_HEADERS, DISPLAY and WAYLAND_DISPLAY. It adds BROWSER=true and CMUX_CLAUDE_AUTH=own / CMUX_CODEX_AUTH=own, so the image's /usr/local/bin/claude / codex launchers add no CodeRouter config to the login run.
  • No secrets in output: nothing logs, traces, journals or answers the code, key or token. The code and key are held in Zeroizing buffers.
  • Advertised: agent_login in initialize's _meta.acpmux.features, and capability agent-login-v1 (in _meta.acpmux.capabilities and _acpmux/status).

Routing: the card names its chat on every call

sessionId is optional on every method. A request that names a chat living on a peer is forwarded whole to that peer by the existing peer_forward.rs path. So a pane connected to the Mac's acpmux reaches the Cloud machine's acpmux by passing the Cloud chat's sessionId to start, finish, cancel and api_key. Without it, the sign-in runs on the daemon the pane is connected to.

What the card PR must add (not in this PR)

The app pane's host allowlist is default-deny. The card PR must add the four methods to AcpmuxPaneMethods.requests, their params to knownParams (agent, sessionId, loginId, code, key), the methods to optionallySessionScoped, and a gesture rule for start / finish / api_key. It must make the same change in cmux-tui/crates/cmux-agent-pane-policy/policy.json. the_allowlist_is_what_the_page_sends requires that table to match what the page sends, so the allowlist ships with the card.

How a Cloud machine gets this acpmux

The VM's acpmux is its cmux-tui binary, /home/cmux/.cmux/bin/cmux-tui. Once this lands, the cmux-tui artifacts workflow publishes the tree build, including cmux-tui-x86_64-unknown-linux-musl. Every DEV, nightly or release app built from a tree that contains this commit bundles that daemon (scripts/cmux-next/pin-cmux-tui.sh tree mode).

The app's automatic machine update then installs it on the machine: CloudMachineUpdateTarget runs cmux link machine update --to-bundled --if-older. That writes the pinned Linux build beside ~/.cmux/bin/cmux-tui, swaps it in and restarts only the daemon, so chats and terminals keep running and a failed update rolls back. Machines too old for that get the same build through the Cloud API exec instead (CloudLegacyMachineUpdate). A local source build with no payload manifest cannot update machines.

Tests

  • Unit tests (agent_login/tests.rs):
    • link parsing from the exact claude auth login output, both plain pipes and under a terminal (OSC-8 and colours)
    • a link that is still arriving is not offered
    • Codex's link and one-time code from its ANSI output, and the code's shape
    • allowlist refusals (http, lookalike host, user part, port, backslash, whitespace, non-ASCII)
    • vm-kind shared-machine detection, and refusal before anything runs
    • cli_missing
    • the own-choice file: only on an image, own\n, 0600/0700
    • settings.json merge: keys kept, 0600, no temp file left, non-object refused and left untouched
    • store checks
    • one-line codes and keys
    • model-plane env removed from the command
  • Integration test (tests/agent_login.rs), with stand-in claude and codex scripts on the harness profiles. The scripts print what the real CLIs print, fail if any model-plane value or DISPLAY reaches them, and read the code or key from stdin. Over a real connection it covers:
    • initialize advertises the feature and capability
    • unsupported_agent
    • Claude start, then a wrong code (bad_code, after which the login is gone: unknown_login)
    • a new start replaces an older one
    • Claude finish with the right code signs in
    • cancel, including a second cancel
    • Codex device flow, plus the own-choice file on an owner Cloud image
    • Codex and Claude API keys, including a refused key
    • shared_machine on a team machine
    • a Web connection is refused
  • cmux-ci test: cargo test -p acpmux --lib -- agent_login capabilities protocol, cargo test -p acpmux --test agent_login, cargo clippy -p acpmux --all-targets -- -D warnings and cargo test -p cmux-agent-pane-policy.

Risk

The change touches credentials. Mitigations:

  • The sign-in runs only for the person's own clients, and is refused on team machines.
  • Only allowlisted https links are answered.
  • Codes and keys never reach logs or replies.
  • The CLI never sees model-plane values.

It adds no Swift and no new trust path. Peer forwarding is the existing session-forwarding path, and the receiving daemon applies its own origin check.

Agent session: agent session

🤖 Generated with Claude Code

teamleaderleo and others added 2 commits October 31, 2026 22:06
… sign-in card

_acpmux/agent_login/start|finish|cancel|api_key run `claude auth login
--claudeai` or `codex login --device-auth` (or save an API key) on the
machine acpmux runs on, so a Cloud machine signs in with the person's own
account instead of a copy of the Mac's login. Refused on a team machine;
model-plane env never reaches the CLI; codes and keys are never logged or
answered. Advertised as the `agent_login` feature and `agent-login-v1`.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-3bab30503f0b9a7059e5
@github-actions github-actions Bot added the risk:high Risk tier (docs/ci/risk-tiers.md) label Nov 1, 2026
teamleaderleo and others added 2 commits October 31, 2026 22:21
… signal; hold a finishing sign-in through a new start

Review follow-ups: PR_SET_PDEATHSIG follows the spawning thread, which a
block_in_place worker can lose mid-sign-in; a sessionId that resolves
nowhere would have signed this machine in instead; the start that replaces
a sign-in now holds its lock through the stop. The Bedrock/Vertex/Foundry
switches join the removed model-plane env.

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

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

9 issues found across 15 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="cmux-tui/crates/acpmux/src/server/agent_login.rs">

<violation number="1" location="cmux-tui/crates/acpmux/src/server/agent_login.rs:80">
P2: `cli_for` only accepts profiles keyed exactly `claude`/`codex`, and rejects Claude profiles with launch arguments. A custom-named family profile therefore falls back to `which(...)`, losing the chat’s configured executable and credential-store environment, so the card can authenticate a different CLI/store. Resolve the selected family profile with the same resolver used for session startup.</violation>

<violation number="2" location="cmux-tui/crates/acpmux/src/server/agent_login.rs:90">
P1: `cli_for` drops the agent family's/defaults environment, so a configured `CODEX_HOME` or `CLAUDE_CONFIG_DIR` is ignored and login writes credentials to the fallback store instead of the store used by the running harness. Merge `cfg.defaults_for(agent.name()).env` with profile env, preserving profile values as the higher-precedence layer.</violation>
</file>

<file name="cmux-tui/crates/acpmux/src/server/requests.rs">

<violation number="1" location="cmux-tui/crates/acpmux/src/server/requests.rs:908">
P2: `initialize` advertises `agent_login` in `features` but omits all four sign-in methods from `_meta.acpmux.extensions`. Clients that gate callable extensions on that list will not expose or invoke the sign-in card; add `MUX_AGENT_LOGIN_START`, `MUX_AGENT_LOGIN_FINISH`, `MUX_AGENT_LOGIN_CANCEL`, and `MUX_AGENT_LOGIN_API_KEY` to the advertised extensions.</violation>
</file>

<file name="cmux-tui/crates/acpmux/src/agent_login.rs">

<violation number="1" location="cmux-tui/crates/acpmux/src/agent_login.rs:74">
P2: `MODEL_PLANE_ENV` omits provider credential variables such as AWS and Google/Vertex credentials. `Cli::command` reapplies profile env after the daemon scrub, so those values can reach `claude auth login`; remove the provider credential namespaces too.</violation>

<violation number="2" location="cmux-tui/crates/acpmux/src/agent_login.rs:85">
P1: The sign-in CLI still inherits Cloud's team-plane environment because this scrub list omits `CMUX_CLAUDE_TEAM_API_KEY` and the related `CMUX_CLAUDE_TEAM_BASE_URL`, `CMUX_CODEROUTER_URL`, and `CMUX_VM_ID` values. Add those model-plane variables before spawning; otherwise own sign-in violates the stated isolation and exposes the team credential to the CLI and its children.</violation>

<violation number="3" location="cmux-tui/crates/acpmux/src/agent_login.rs:222">
P1: `Machine::shared` treats an `owner` marker as private without checking `/srv/team`. `start` and `save_api_key` can therefore authorize own credentials on a team machine; reject a present or unreadable team marker too.</violation>

<violation number="4" location="cmux-tui/crates/acpmux/src/agent_login.rs:453">
P2: `kill_group` is a no-op on non-Unix targets. Cancel, expiry, and URL timeout then kill only the leader, allowing CLI descendants to outlive the sign-in; use a platform process-group or job equivalent.</violation>

<violation number="5" location="cmux-tui/crates/acpmux/src/agent_login.rs:583">
P2: Concurrent starts both spawn before either inserts its entry. The later insert kills the first process, but the first call still returns its invalid login ID; reserve or serialize the per-agent slot before spawning.</violation>
</file>

<file name="cmux-tui/crates/acpmux/src/agent_login/parse.rs">

<violation number="1" location="cmux-tui/crates/acpmux/src/agent_login/parse.rs:63">
P2: `strip_terminal` treats every `ESC` as an OSC terminator, even when it is not followed by `\\`. A pipe read can end on that byte, so `offer` may expose an unfinished OSC-8 target; set `closed` only for BEL or `ESC\\`.</violation>
</file>

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

};
let model = cfg.defaults_for(agent.name()).model.unwrap_or_default();
let expand = |v: &str| crate::hub::expand_env_value(v, &cwd, &home, &model);
let env: std::collections::BTreeMap<String, String> = profile

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: cli_for drops the agent family's/defaults environment, so a configured CODEX_HOME or CLAUDE_CONFIG_DIR is ignored and login writes credentials to the fallback store instead of the store used by the running harness. Merge cfg.defaults_for(agent.name()).env with profile env, preserving profile values as the higher-precedence layer.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At cmux-tui/crates/acpmux/src/server/agent_login.rs, line 90:

<comment>`cli_for` drops the agent family's/defaults environment, so a configured `CODEX_HOME` or `CLAUDE_CONFIG_DIR` is ignored and login writes credentials to the fallback store instead of the store used by the running harness. Merge `cfg.defaults_for(agent.name()).env` with profile env, preserving profile values as the higher-precedence layer.</comment>

<file context>
@@ -0,0 +1,203 @@
+    };
+    let model = cfg.defaults_for(agent.name()).model.unwrap_or_default();
+    let expand = |v: &str| crate::hub::expand_env_value(v, &cwd, &home, &model);
+    let env: std::collections::BTreeMap<String, String> = profile
+        .env
+        .iter()
</file context>

"ANTHROPIC_CUSTOM_HEADERS",
"CLAUDE_CODE_USE_BEDROCK",
"CLAUDE_CODE_USE_VERTEX",
"CLAUDE_CODE_USE_FOUNDRY",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: The sign-in CLI still inherits Cloud's team-plane environment because this scrub list omits CMUX_CLAUDE_TEAM_API_KEY and the related CMUX_CLAUDE_TEAM_BASE_URL, CMUX_CODEROUTER_URL, and CMUX_VM_ID values. Add those model-plane variables before spawning; otherwise own sign-in violates the stated isolation and exposes the team credential to the CLI and its children.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At cmux-tui/crates/acpmux/src/agent_login.rs, line 85:

<comment>The sign-in CLI still inherits Cloud's team-plane environment because this scrub list omits `CMUX_CLAUDE_TEAM_API_KEY` and the related `CMUX_CLAUDE_TEAM_BASE_URL`, `CMUX_CODEROUTER_URL`, and `CMUX_VM_ID` values. Add those model-plane variables before spawning; otherwise own sign-in violates the stated isolation and exposes the team credential to the CLI and its children.</comment>

<file context>
@@ -0,0 +1,743 @@
+    "ANTHROPIC_CUSTOM_HEADERS",
+    "CLAUDE_CODE_USE_BEDROCK",
+    "CLAUDE_CODE_USE_VERTEX",
+    "CLAUDE_CODE_USE_FOUNDRY",
+];
+
</file context>
Suggested change
"CLAUDE_CODE_USE_FOUNDRY",
"CLAUDE_CODE_USE_FOUNDRY",
"CMUX_CLAUDE_TEAM_BASE_URL",
"CMUX_CLAUDE_TEAM_API_KEY",
"CMUX_CODEROUTER_URL",
"CMUX_VM_ID",

/// On a team machine (module docs): `etc/cmux/vm-kind` exists and is not
/// `owner`, or cannot be read.
pub fn shared(&self) -> bool {
let kind = self.root.join("etc/cmux/vm-kind");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Machine::shared treats an owner marker as private without checking /srv/team. start and save_api_key can therefore authorize own credentials on a team machine; reject a present or unreadable team marker too.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At cmux-tui/crates/acpmux/src/agent_login.rs, line 222:

<comment>`Machine::shared` treats an `owner` marker as private without checking `/srv/team`. `start` and `save_api_key` can therefore authorize own credentials on a team machine; reject a present or unreadable team marker too.</comment>

<file context>
@@ -0,0 +1,743 @@
+    /// On a team machine (module docs): `etc/cmux/vm-kind` exists and is not
+    /// `owner`, or cannot be read.
+    pub fn shared(&self) -> bool {
+        let kind = self.root.join("etc/cmux/vm-kind");
+        match std::fs::symlink_metadata(&kind) {
+            Err(e) if e.kind() == std::io::ErrorKind::NotFound => false,
</file context>
Suggested change
let kind = self.root.join("etc/cmux/vm-kind");
let team = self.root.join("srv/team");
match std::fs::symlink_metadata(&team) {
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
_ => return true,
}
let kind = self.root.join("etc/cmux/vm-kind");

"personInstance": hub.person.instance(),
"extensions": extensions, "operations": crate::hub::HANDOFF_OPERATIONS.iter().chain(crate::hub::PERMISSION_GROUP_OPERATIONS.iter()).chain(super::fork_through::FORK_OPERATIONS.iter()).collect::<Vec<_>>(), "handoff": {"maxCapsuleBytes": crate::hub::MAX_CAPSULE_BYTES},
"features": ["promptAccepted", "turnIds", "eventPaging", "eventKinds", "eventStream", "cancelRequest", "messageSuperseded", "turnErrorText", "permissionGroups", "trustGate", "chiefBuiltinPresets", "personChallenge", "queueEdit", "steerInterrupt", "personHolds", "sessionTransfer", "approverDecisions", "toolInventory", "promptWithdraw", "processes", "worktreeCare", "harnessInventory", super::inventory_watch::FEATURE, crate::harness_health::INVENTORY_REFRESH_FEATURE, crate::harness_health::UPDATE_PLAN_FEATURE, crate::harness_health::UPDATE_PREPARE_FEATURE, "surfaces", crate::hub::FILE_PUT_FEATURE, crate::hub::file_uploads::CHUNKS_FEATURE, crate::hub::MEDIA_READ_FEATURE, crate::hub::SUBAGENT_TRANSCRIPT_FEATURE, crate::hub::NATIVE_HISTORY_FEATURE, crate::attach::FEATURE, crate::prompt_uploads::FEATURE, crate::hub::file_uploads::RESUME_FEATURE, super::upload_frames::FEATURE, crate::peer_states::STATE_EVENTS_FEATURE, crate::hub::session_auth::FEATURE, crate::hub::cancel::PERSON_STOP_FEATURE, crate::hub::stop_if_idle::FEATURE, SESSION_RESUME_FEATURE, crate::watch_delta::FEATURE], "trustGate": true}}
"features": ["promptAccepted", "turnIds", "eventPaging", "eventKinds", "eventStream", "cancelRequest", "messageSuperseded", "turnErrorText", "permissionGroups", "trustGate", "chiefBuiltinPresets", "personChallenge", "queueEdit", "steerInterrupt", "personHolds", "sessionTransfer", "approverDecisions", "toolInventory", "promptWithdraw", "processes", "worktreeCare", "harnessInventory", super::inventory_watch::FEATURE, crate::harness_health::INVENTORY_REFRESH_FEATURE, crate::harness_health::UPDATE_PLAN_FEATURE, crate::harness_health::UPDATE_PREPARE_FEATURE, "surfaces", crate::hub::FILE_PUT_FEATURE, crate::hub::file_uploads::CHUNKS_FEATURE, crate::hub::MEDIA_READ_FEATURE, crate::hub::SUBAGENT_TRANSCRIPT_FEATURE, crate::hub::NATIVE_HISTORY_FEATURE, crate::attach::FEATURE, crate::prompt_uploads::FEATURE, crate::hub::file_uploads::RESUME_FEATURE, super::upload_frames::FEATURE, crate::peer_states::STATE_EVENTS_FEATURE, crate::hub::session_auth::FEATURE, crate::agent_login::FEATURE, crate::hub::cancel::PERSON_STOP_FEATURE, crate::hub::stop_if_idle::FEATURE, SESSION_RESUME_FEATURE, crate::watch_delta::FEATURE], "trustGate": true}}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: initialize advertises agent_login in features but omits all four sign-in methods from _meta.acpmux.extensions. Clients that gate callable extensions on that list will not expose or invoke the sign-in card; add MUX_AGENT_LOGIN_START, MUX_AGENT_LOGIN_FINISH, MUX_AGENT_LOGIN_CANCEL, and MUX_AGENT_LOGIN_API_KEY to the advertised extensions.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At cmux-tui/crates/acpmux/src/server/requests.rs, line 908:

<comment>`initialize` advertises `agent_login` in `features` but omits all four sign-in methods from `_meta.acpmux.extensions`. Clients that gate callable extensions on that list will not expose or invoke the sign-in card; add `MUX_AGENT_LOGIN_START`, `MUX_AGENT_LOGIN_FINISH`, `MUX_AGENT_LOGIN_CANCEL`, and `MUX_AGENT_LOGIN_API_KEY` to the advertised extensions.</comment>

<file context>
@@ -905,7 +905,7 @@ async fn dispatch_request(
                 "personInstance": hub.person.instance(),
                 "extensions": extensions, "operations": crate::hub::HANDOFF_OPERATIONS.iter().chain(crate::hub::PERMISSION_GROUP_OPERATIONS.iter()).chain(super::fork_through::FORK_OPERATIONS.iter()).collect::<Vec<_>>(), "handoff": {"maxCapsuleBytes": crate::hub::MAX_CAPSULE_BYTES},
-                "features": ["promptAccepted", "turnIds", "eventPaging", "eventKinds", "eventStream", "cancelRequest", "messageSuperseded", "turnErrorText", "permissionGroups", "trustGate", "chiefBuiltinPresets", "personChallenge", "queueEdit", "steerInterrupt", "personHolds", "sessionTransfer", "approverDecisions", "toolInventory", "promptWithdraw", "processes", "worktreeCare", "harnessInventory", super::inventory_watch::FEATURE, crate::harness_health::INVENTORY_REFRESH_FEATURE, crate::harness_health::UPDATE_PLAN_FEATURE, crate::harness_health::UPDATE_PREPARE_FEATURE, "surfaces", crate::hub::FILE_PUT_FEATURE, crate::hub::file_uploads::CHUNKS_FEATURE, crate::hub::MEDIA_READ_FEATURE, crate::hub::SUBAGENT_TRANSCRIPT_FEATURE, crate::hub::NATIVE_HISTORY_FEATURE, crate::attach::FEATURE, crate::prompt_uploads::FEATURE, crate::hub::file_uploads::RESUME_FEATURE, super::upload_frames::FEATURE, crate::peer_states::STATE_EVENTS_FEATURE, crate::hub::session_auth::FEATURE, crate::hub::cancel::PERSON_STOP_FEATURE, crate::hub::stop_if_idle::FEATURE, SESSION_RESUME_FEATURE, crate::watch_delta::FEATURE], "trustGate": true}}
+                "features": ["promptAccepted", "turnIds", "eventPaging", "eventKinds", "eventStream", "cancelRequest", "messageSuperseded", "turnErrorText", "permissionGroups", "trustGate", "chiefBuiltinPresets", "personChallenge", "queueEdit", "steerInterrupt", "personHolds", "sessionTransfer", "approverDecisions", "toolInventory", "promptWithdraw", "processes", "worktreeCare", "harnessInventory", super::inventory_watch::FEATURE, crate::harness_health::INVENTORY_REFRESH_FEATURE, crate::harness_health::UPDATE_PLAN_FEATURE, crate::harness_health::UPDATE_PREPARE_FEATURE, "surfaces", crate::hub::FILE_PUT_FEATURE, crate::hub::file_uploads::CHUNKS_FEATURE, crate::hub::MEDIA_READ_FEATURE, crate::hub::SUBAGENT_TRANSCRIPT_FEATURE, crate::hub::NATIVE_HISTORY_FEATURE, crate::attach::FEATURE, crate::prompt_uploads::FEATURE, crate::hub::file_uploads::RESUME_FEATURE, super::upload_frames::FEATURE, crate::peer_states::STATE_EVENTS_FEATURE, crate::hub::session_auth::FEATURE, crate::agent_login::FEATURE, crate::hub::cancel::PERSON_STOP_FEATURE, crate::hub::stop_if_idle::FEATURE, SESSION_RESUME_FEATURE, crate::watch_delta::FEATURE], "trustGate": true}}
             });
             // Large messages go compressed from here on (`frame_compress.rs`).
</file context>

break;
}
if o == '\u{1b}' {
if chars.peek() == Some(&'\\') {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: strip_terminal treats every ESC as an OSC terminator, even when it is not followed by \\. A pipe read can end on that byte, so offer may expose an unfinished OSC-8 target; set closed only for BEL or ESC\\.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At cmux-tui/crates/acpmux/src/agent_login/parse.rs, line 63:

<comment>`strip_terminal` treats every `ESC` as an OSC terminator, even when it is not followed by `\\`. A pipe read can end on that byte, so `offer` may expose an unfinished OSC-8 target; set `closed` only for BEL or `ESC\\`.</comment>

<file context>
@@ -0,0 +1,154 @@
+                        break;
+                    }
+                    if o == '\u{1b}' {
+                        if chars.peek() == Some(&'\\') {
+                            chars.next();
+                        }
</file context>

pub(crate) fn cli_for(cfg: &Config, agent: Agent) -> Option<Cli> {
let cwd = std::env::temp_dir();
let home = dirs::home_dir().unwrap_or_default();
let profile = cfg.harnesses.get(agent.name()).filter(|p| match agent {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: cli_for only accepts profiles keyed exactly claude/codex, and rejects Claude profiles with launch arguments. A custom-named family profile therefore falls back to which(...), losing the chat’s configured executable and credential-store environment, so the card can authenticate a different CLI/store. Resolve the selected family profile with the same resolver used for session startup.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At cmux-tui/crates/acpmux/src/server/agent_login.rs, line 80:

<comment>`cli_for` only accepts profiles keyed exactly `claude`/`codex`, and rejects Claude profiles with launch arguments. A custom-named family profile therefore falls back to `which(...)`, losing the chat’s configured executable and credential-store environment, so the card can authenticate a different CLI/store. Resolve the selected family profile with the same resolver used for session startup.</comment>

<file context>
@@ -0,0 +1,203 @@
+pub(crate) fn cli_for(cfg: &Config, agent: Agent) -> Option<Cli> {
+    let cwd = std::env::temp_dir();
+    let home = dirs::home_dir().unwrap_or_default();
+    let profile = cfg.harnesses.get(agent.name()).filter(|p| match agent {
+        Agent::Claude => p.kind == HarnessKind::ClaudeStdio && p.argv.len() == 1,
+        Agent::Codex => crate::codex_app_server::is_profile_launch(&p.argv),
</file context>

store,
running: Arc::new(tokio::sync::Mutex::new(Running { child, stdin, _folder: folder })),
};
if let Some(old) = lock_table().insert(agent, entry) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Concurrent starts both spawn before either inserts its entry. The later insert kills the first process, but the first call still returns its invalid login ID; reserve or serialize the per-agent slot before spawning.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At cmux-tui/crates/acpmux/src/agent_login.rs, line 583:

<comment>Concurrent starts both spawn before either inserts its entry. The later insert kills the first process, but the first call still returns its invalid login ID; reserve or serialize the per-agent slot before spawning.</comment>

<file context>
@@ -0,0 +1,743 @@
+        store,
+        running: Arc::new(tokio::sync::Mutex::new(Running { child, stdin, _folder: folder })),
+    };
+    if let Some(old) = lock_table().insert(agent, entry) {
+        // A start that raced this one: the later one wins.
+        kill_group(old.group);
</file context>

}

#[cfg(not(unix))]
fn kill_group(_group: Option<u32>) {}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: kill_group is a no-op on non-Unix targets. Cancel, expiry, and URL timeout then kill only the leader, allowing CLI descendants to outlive the sign-in; use a platform process-group or job equivalent.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At cmux-tui/crates/acpmux/src/agent_login.rs, line 453:

<comment>`kill_group` is a no-op on non-Unix targets. Cancel, expiry, and URL timeout then kill only the leader, allowing CLI descendants to outlive the sign-in; use a platform process-group or job equivalent.</comment>

<file context>
@@ -0,0 +1,743 @@
+}
+
+#[cfg(not(unix))]
+fn kill_group(_group: Option<u32>) {}
+
+/// Takes the sign-in `id` (any agent) out of the table and stops its CLI.
</file context>


/// The model-plane values the sign-in never sees: they would receive the
/// person's sign-in or outrank it.
pub const MODEL_PLANE_ENV: &[&str] = &[

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: MODEL_PLANE_ENV omits provider credential variables such as AWS and Google/Vertex credentials. Cli::command reapplies profile env after the daemon scrub, so those values can reach claude auth login; remove the provider credential namespaces too.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At cmux-tui/crates/acpmux/src/agent_login.rs, line 74:

<comment>`MODEL_PLANE_ENV` omits provider credential variables such as AWS and Google/Vertex credentials. `Cli::command` reapplies profile env after the daemon scrub, so those values can reach `claude auth login`; remove the provider credential namespaces too.</comment>

<file context>
@@ -0,0 +1,743 @@
+
+/// The model-plane values the sign-in never sees: they would receive the
+/// person's sign-in or outrank it.
+pub const MODEL_PLANE_ENV: &[&str] = &[
+    "ANTHROPIC_API_KEY",
+    "ANTHROPIC_AUTH_TOKEN",
</file context>

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (subagent opus-5.5 medium): SHIP at 87f6af8

The independent review found no blocking finding. The fixes for its follow-ups landed at this head and were checked again:

  • no parent-death signal on the sign-in's child;
  • a sessionId that names no chat here is refused with unknown_session instead of signing this machine in;
  • cancel is exempt from that refusal;
  • a new start holds the old sign-in's lock while it stops it;
  • the Bedrock, Vertex and Foundry switches are removed from the sign-in's env.

Follow-ups: #30225 (a saved Claude key after a switch to team mode; killpg after the child is reaped; settings.json symlinks and concurrent writes; a forwarded Codex finish when the peer link drops).

cmux-ci passed at this head on the Linux pool: cargo test -p acpmux (the agent_login unit tests and the integration test) and cargo clippy -p acpmux --all-targets -D warnings.

@github-actions

github-actions Bot commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

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

Review 6255a96: 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=pr30188 -f ref=agent-login-rpc -f platform=macos -f backend_mode=local

Gallery before/after for 6255a96: 16 states rendered, 0 images differ, 2730 more touched states not rendered. Images are in the run's artifact, not on this public PR; the independent review of this head reads them on its own, so the PR needs no hand-made screenshots of these states.

gh run download 41280295774 -R manaflow-ai/cmux -n gallery-advisory-30188-6255a96d868aded852219f3f2be2d4efdb7ba392 (then read gallery-review.json)

States
State Theme Status Diff Picked by
agent-pane.live-chat-watch/choice-alone Apple System Colors Light unchanged 0% imports methods.ts (1 hops)
agent-pane.live-chat-watch/choice-alone Apple System Colors unchanged 0% imports methods.ts (1 hops)
agent-pane.live-chat-watch/view-only Apple System Colors Light unchanged 0% imports methods.ts (1 hops)
agent-pane.live-chat-watch/view-only Apple System Colors unchanged 0% imports methods.ts (1 hops)
agent-pane.approval-edits/single Apple System Colors Light unchanged 0% imports methods.ts (2 hops)
agent-pane.approval-edits/single Apple System Colors unchanged 0% imports methods.ts (2 hops)
agent-pane.approval-edits/grouped Apple System Colors Light unchanged 0% imports methods.ts (2 hops)
agent-pane.approval-edits/grouped Apple System Colors unchanged 0% imports methods.ts (2 hops)
agent-pane.approval-edits/single-multiedit Apple System Colors Light unchanged 0% imports methods.ts (2 hops)
agent-pane.approval-edits/single-multiedit Apple System Colors unchanged 0% imports methods.ts (2 hops)
agent-pane.approval-edits/grouped-multiedit Apple System Colors Light unchanged 0% imports methods.ts (2 hops)
agent-pane.approval-edits/grouped-multiedit Apple System Colors unchanged 0% imports methods.ts (2 hops)
agent-pane.approval-edits/single-long Apple System Colors Light unchanged 0% imports methods.ts (2 hops)
agent-pane.approval-edits/single-long Apple System Colors unchanged 0% imports methods.ts (2 hops)
agent-pane.approval-edits/grouped-long Apple System Colors Light unchanged 0% imports methods.ts (2 hops)
agent-pane.approval-edits/grouped-long Apple System Colors unchanged 0% imports methods.ts (2 hops)
agent-pane.auth-health/signed-in Apple System Colors Light unchanged 0% imports methods.ts (2 hops)
agent-pane.auth-health/signed-in Apple System Colors unchanged 0% imports methods.ts (2 hops)
agent-pane.auth-health/signed-out Apple System Colors Light unchanged 0% imports methods.ts (2 hops)
agent-pane.auth-health/signed-out Apple System Colors unchanged 0% imports methods.ts (2 hops)
agent-pane.auth-health/expired Apple System Colors Light unchanged 0% imports methods.ts (2 hops)
agent-pane.auth-health/expired Apple System Colors unchanged 0% imports methods.ts (2 hops)
agent-pane.auth-health/unknown Apple System Colors Light unchanged 0% imports methods.ts (2 hops)
agent-pane.auth-health/unknown Apple System Colors unchanged 0% imports methods.ts (2 hops)
agent-pane.chat-width/standard Apple System Colors Light unchanged 0% imports methods.ts (2 hops)
agent-pane.chat-width/standard Apple System Colors unchanged 0% imports methods.ts (2 hops)
agent-pane.chat-width/wide Apple System Colors Light unchanged 0% imports methods.ts (2 hops)
agent-pane.chat-width/wide Apple System Colors unchanged 0% imports methods.ts (2 hops)
agent-pane.chat-width/wide-blocks Apple System Colors Light unchanged 0% imports methods.ts (2 hops)
agent-pane.chat-width/wide-blocks Apple System Colors unchanged 0% imports methods.ts (2 hops)
agent-pane.composer-edge/short-edge Apple System Colors Light unchanged 0% imports methods.ts (2 hops)
agent-pane.composer-edge/short-edge Apple System Colors unchanged 0% imports methods.ts (2 hops)

Pick other states with a Gallery: entry.id, entry.id/variant line in the PR body.

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

Shipped in:

@blacksmith-sh

blacksmith-sh Bot commented Nov 1, 2026

Copy link
Copy Markdown

Found 9 test failures on Blacksmith runners:

Failures

Test View Logs
/tmp/tmpbqclx28q/summary-missing.json (live):/summary-missing.json (live): View Logs
/tmp/tmpzfiwgg9z/summary-fail.json (live):/summary-fail.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
convention violations found (ERROR lines above)./
convention violations found (ERROR lines above).
View Logs
test_cmux_next_follows_the_committed_target_graph (main.FocusedTestMap.test_cmux_ne
xt_follows_the_committed_target_graph)/
test_cmux_next_follows_the_committed_target_graph (main.FocusedTestMap.test_cmux_ne
xt_follows_the_committed_target_graph)
View Logs
test_every_part_of_bun_run_check_runs_in_the_warn_branch (main.WebviewsWarnSplitCov
ersCheck.test_every_part_of_bun_run_check_runs_in_the_warn_branch)/
test_every_part_of_bun_run_check_runs_in_the_warn_branch (main.WebviewsWarnSplitCov
ersCheck.test_every_part_of_bun_run_check_runs_in_the_warn_branch)
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

Review (subagent opus-5.5 medium): SHIP at 6255a96

The independent reviewer re-reviewed the two changes since the last receipt, both made for the Rust panic ratchet; each behaves exactly like the code it replaces. No new follow-ups; the earlier ones are in #30225.

@teamleaderleo
teamleaderleo merged commit 2b0a685 into feat-cmux-next Nov 1, 2026
236 of 239 checks passed
@teamleaderleo
teamleaderleo deleted the agent-login-rpc branch November 1, 2026 09:10
teamleaderleo added a commit that referenced this pull request Nov 1, 2026
…session

acpmux's agent_login (#30188) forwards a call that names a chat to the
machine the chat lives on, and refuses a caller that is not the owner's
(remote_refused) or a chat it does not have (unknown_session). The card
names the chat on start and cancel too, words both reasons, and the host
allowlist takes the four methods as optionally session scoped with
sessionId on each. The daemon relay names the tab's session on every step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-3bab30503f0b9a7059e5
teamleaderleo added a commit that referenced this pull request Nov 1, 2026
…he user's own machine (#30214)

* cmux NEXT Cloud: a failed sign-in card signs the agent in on its own machine

A Cloud chat whose Claude or Codex has no login on its machine offers
Sign in to Claude / Codex (the agent's own login, run there through
acpmux _acpmux/agent_login/*) and Use an API key, inline in the failed
turn's card. Never copies this Mac's login. Once signed in, the failed
prompt goes again on the person's click (a session/prompt gesture
ticket); nothing is sent without one. The pane's host allowlist and the
shared policy gain the four methods; the wire log keeps no code or key.

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

* Cloud sign-in card: a failed API key asks for the key again on Retry

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

* Cloud sign-in card: design-token spacing, test types

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

* Cloud sign-in card: format

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

* Cloud sign-in: only the person's click starts a login; the page opens on its own click

Review (router-review, BLOCK at b02e46f): start took no gesture, so a pane
script could start a login and finish it into the chat. The host now gates
_acpmux/agent_login/start like api_key. The Sign in click's gesture goes to
the start, so Open sign-in page leads the card and opens the page on its
own click. Also: a card that goes away ends every step in flight, and a
start reply with a page this Mac will not open cancels that login.

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

* Cloud sign-in card: format

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

* Cloud sign-in reaches Cloud chats: the daemon relays agent_login

Review (router-review, BLOCK at 61faf41): Cloud chats talk to their
machine through the session daemon (RemoteAcpmuxWire), which neither
advertised agent_login nor relayed its methods. cmux-tui-core gains
agent-session-login (agent-session-login-v1): each step goes to acpmux's
_acpmux/agent_login/<op> on the tab's own session, owner only (never a
teammate, guest or observation), acpmux's refusal back as
{refused:{reason}}, finish waiting up to 15 minutes for Codex's approval.
The Swift attach client and the remote wire relay the four frames, and
the wire's handshake names the agent_login feature where the daemon
serves it.

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

* agent-session-login: fix a closure lifetime and an async autoclosure

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

* Cloud sign-in: every step names its chat; remote_refused and unknown_session

acpmux's agent_login (#30188) forwards a call that names a chat to the
machine the chat lives on, and refuses a caller that is not the owner's
(remote_refused) or a chat it does not have (unknown_session). The card
names the chat on start and cancel too, words both reasons, and the host
allowlist takes the four methods as optionally session scoped with
sessionId on each. The daemon relay names the tab's session on every step.

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

* Remote wire: a sign-in step must name this chat

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

* agent-session-login: rustfmt

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

* Cloud sign-in card: text field focus shows by its border, floating promises voided, fmt

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

* Cloud sign-in: Retry after a wrong code starts a new login, card cleanup stops a pending resend, step deadlines match acpmux's

acpmux ends a login on any refused step, a wrong Claude code too, so Retry now starts a fresh
one. A resend checks that its card is still up after the prompt's attachments load. Start and
API-key steps wait past acpmux's own waits (20 s and 60 s) in the Swift client and the daemon
relay. The handshake test reads an absent feature list as false.

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

* daemon capabilities export: agent-session-login-v1

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

* AgentSessionAttachClient: call takes a timeout again (the base revert #30791 dropped it; login steps use it)

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: cmux-bot[bot] <348531757+cmux-bot[bot]@users.noreply.github.com>
teamleaderleo added a commit that referenced this pull request Nov 2, 2026
…r Codex is not signed in (#31517)

Leo, 2026-10-26: "if user picks a model they don't have api key, we offer them to add credits or add their own api key"

#27058 shipped the Add Credits half (the router's plan and credit refusals). The key half waited
on key plumbing (manaflow-ai/cmuxterm-hq#5411); #30188 has since given acpmux
`_acpmux/agent_login/api_key`, which saves a key where the agent's own CLI reads it (Claude's
settings.json env.ANTHROPIC_API_KEY, `codex login --with-api-key`) and restarts the chat on it.
Only Cloud chats offered it. A chat on this Mac whose built-in `claude` or `codex` fails a turn
for want of a sign-in now shows Use an API key after Sign in again; Save stores the key and Send
again resends the failed prompt on the person's click. Routes (claude-cr, claude-sr) set their
own key and endpoint, so they get no key field.

Tests: bun test test/cloud-signin-card.test.tsx test/access-offer.test.tsx test/signed-out-rail.test.tsx -> 30 pass.


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

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

This branch was previously deployed

3 inactive deployments
artifacts — 6255a96d Deployed Nov 1, 2026 by teamleaderleo via add the Windows daemon to the tree #94178
sentry-dsyms — 6255a96d Deployed Nov 1, 2026 by teamleaderleo via upload cmux-tui dSYMs to Sentry #94178
ci-routing-agent — 6255a96d Deployed Nov 1, 2026 by teamleaderleo via cmux-tui tree preflight #94178
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant