Cloud: keep acpmux's typed error up the relay and in cloud start-chat
Leo, 2026-11-05: "I thought we had better typed errors than this. I thought we had a better solution than whatever was going on here."
A Cloud chat that failed on its machine lost its type on the way back. Two places collapsed it:
-
The machine relay (
cmux-host/src/cloud/relay.rs) sent every acpmux failure up aschat.failedplus acpmux's last stderr line. That line is acpmux's whole--jsonenvelope, cut at 300 characters. A failed session create always reported exit 1, so ano_sessioncreate read as a generic failure. The production fresh-machine proof (cmuxterm-hq run 42112686310) shows the result:chat.failed: {"error":{"code":"runtime","detail":"daemon_closed",...cut mid-object.
Now the relay reads the envelope and sends updetail(daemon_closed,trust_required, ...),acpmux_code,retryableanderror_kind, with acpmux's own sentence asmessage. A session create keeps its real exit. Only short code-shaped words go up (64 chars max: lowercase letters, digits and_.-). A sentence, a path or a value is dropped, and stderr with no envelope stays the plain failure it was.codekeeps its values, so the platform and app consumers are unchanged. -
cmux cloud start-chat(cli/cloud_start_chat.rssigned_out) treated any message containing "not signed in" or "sign in" as the cmux account being signed out. So acpmux's "Claude is not signed in here ..." (and anyauth.*errorKind) becamecloud.signed_outwith "runcmux cloud sign-in": the wrong account and the wrong fix. Now a harness auth kind is never the account. The account is matched by its codes and by the app's own sign-in refusal text. -
acpmux's
daemon_closed(client.rsclosed_error,cli/errors.rs) said only in prose why the connection closed. A panicked request task and a restart read the same, since both leave the daemon running. The error now carriesdata.cause(panicked,dropped,crashed,socket_left,shut_down) anddata.panic, the newestpanics.jsonlline from the last 10 minutes with only its thread, location and time (never its message or backtrace). The relay sendscauseup. The prod proof's case issocket_left.
It also adds fixtures for the start's login decision (cmux-cloud-start-core harness_login): the Mac's cached Claude login present and absent, against each machine-login probe answer (none, rejected, failed exec, session, cached, working, unknown), for the composer and for a headless run. Further fixtures check that a cached login never moves a start past an earlier check and that the FFI wire holds no credential field.
Changelog
Fixed: Cloud chat failures keep their cause (a closed daemon, a refused permission, a Claude sign-in) instead of a generic failure or a wrong "sign in to cmux".
Tests
The regressions are in commits of their own, ahead of the fixes:
cargo test -p cmux-host --lib cloud::relay::tests: daemon_closed and retryable, permission, timeout, errorKind, a signal's exit taken from the envelope, the malformed and no-envelope fallbacks, field bounds, and a failed session create run throughrun_chatwith a fake acpmux (exit 4 keepschat.no_session).cargo test -p cmux-tui --lib cloud_start_chat: a harness sign-in failure is notcloud.signed_out, and the account's refusals still are.cargo test -p acpmux --lib -- client::closed_tests cli::errors: each cause, the panic line's fields only, old, test and broken lines ignored, and the CLI envelope'sdata.cause. These are new functions, so there is no behavioral red.cargo test -p cmux-cloud-start-core --lib: the cache matrix and the ordering above.
Observed / Not run
Red and green results from the PR runner are recorded in the comments below.
š¤ Generated with Claude Code
Agent session: agent session