cloud-journey: pass the person's Claude login the Mac's way and read the reply back; hourly on production
The production Cloud journey now gets a chat reply back. Until now every chat on a fresh Cloud machine failed with "Not logged in · Please run /login", because the machine had no Claude sign-in.
How a person's Cloud chat gets signed in already exists. cloud.startChat calls passHarnessLogin, which runs CloudCredentialTransfer. The Mac writes the person's own claude setup-token login (cached in the Keychain, or after one confirm that runs setup-token in a local terminal) to their own machine over the machine routes. It uses exec as the work user and fs/write as root. The platform keeps none of it (cmux-platform#580 removed the server-side store, and #587 added the remote off switch). DEV and nightly builds skip the Release consent gate. The journey never ran that step, so it could not get a reply.
What changes
cloud-journey.ts, new step "pass Claude login": it runs the Mac's ownCloudCredentialInstallerpreflight and installer, read from the Swift source, so the two cannot drift. They go through the sameexecand/v1/cloud/machines/{id}/fs/writeroutes, and the step then checks that the image's launcher sees an own login. The token comes fromCLAUDE_CODE_OAUTH_TOKEN. With no token the step is skipped.--skip syncis new, since link sync needs--cmux;cloud-journey-ci.sh(backend.yml's staging and production journeys) now passes it.--probe-out FILEwrites the run as one cmuxterm-hqprobes.pyrow (cloud-chat-replies-production):okwith ms from send to first reply, orfailnaming the step.backend.ymljourney-production and the new workflow take the production agent account from environmentcmux-next-cloud-journey(CMUX_DOGFOOD_STACK_*there shadows the repository's pair). The repository's pair is the dev Stack account, and production rejects it: this PR's first run failed sign-in withEMAIL_PASSWORD_MISMATCH. That environment holds no secrets yet, so the production runs stay red at sign-in until it does. The real model also getsCLAUDE_CODE_OAUTH_TOKEN, the same repository secretmachine-vault-e2e.ymluses. journey-staging gets the token too.--model stub(new,cloud-journey-model-stub.py): a test with everything real except the model endpoint. The account, machine, acpmux, the Claude Code binary and the login copy are real. A stub Messages server on the machine's loopback answers with a nonce and asks for one Bash call. The journey passes a made-upsk-ant-oat01-login through the same login copy and points Claude Code at the stub. It then checks three things: the reply carries the nonce; every model request carried the passed login (the stub logs only its SHA-256), so the credential hop works and nothing reached Anthropic; and the Bash output came back. No Claude secret is needed.- New
cloud-journey-prod.yml: the stub model runs hourly and the real login (CLAUDE_CODE_OAUTH_TOKEN) runs every six hours as a canary. Both register from main and always run feat-cmux-next's scripts, never a pull request's head, because the job holds the production account and a Claude login (Codex review F1). The workflow also runs on dispatch (inputmodel). The artifact holds the step table andprobe.json(the stub run is probecloud-chat-replies-production). Each model has one issue: a red run opens it, a later red rewrites its body, and a green run closes it.
Observed / Not run
- Before, on production (local run): every step passed up to
reply, which failed withClaude said: Not logged in · Please run /login. - With this step and a fake
sk-ant-oat01-token, on production (local run):pass Claude loginpassed ("own has-own-login"). The reply then failed401 OAuth access token is invalid, so Claude used the installed login. The machine was deleted. - Stub model on production (local run): every step up to the reply passed. The reply was "Stub reply 4919bd9e1988.", from 4 model requests that all carried the passed login, and the Bash output came back. Timings: create machine 1.4 s, running 15.6 s, pass Claude login 3.2 s, send chat 0.76 s, reply 0.93 s (send to reply 1.7 s).
- Production finding, not from this PR:
connect(link_token) answersauth.forbidden"the original receiving VM has no live ready attempt". - The PR-triggered production run (https://github.com/manaflow-ai/cmux/actions/runs/41820782689, since removed) failed at sign-in on both models, because the repository's pair is the dev account. Once
cmux-next-cloud-journeyholds the production account, a dispatch from main covers both models. - Not run here:
bun test(agents do not run tests on this Mac; CI's tooling tests run it). actionlint is clean.
Changelog
none
Agent session: agent session
🤖 Generated with Claude Code