cloud-journey: pass the person's Claude login the Mac's way and read the reply back; hourly on production

Merged
TE
2d ago
+345-11

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 own CloudCredentialInstaller preflight and installer, read from the Swift source, so the two cannot drift. They go through the same exec and /v1/cloud/machines/{id}/fs/write routes, and the step then checks that the image's launcher sees an own login. The token comes from CLAUDE_CODE_OAUTH_TOKEN. With no token the step is skipped. --skip sync is new, since link sync needs --cmux; cloud-journey-ci.sh (backend.yml's staging and production journeys) now passes it. --probe-out FILE writes the run as one cmuxterm-hq probes.py row (cloud-chat-replies-production): ok with ms from send to first reply, or fail naming the step.
  • backend.yml journey-production and the new workflow take the production agent account from environment cmux-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 with EMAIL_PASSWORD_MISMATCH. That environment holds no secrets yet, so the production runs stay red at sign-in until it does. The real model also gets CLAUDE_CODE_OAUTH_TOKEN, the same repository secret machine-vault-e2e.yml uses. 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-up sk-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 (input model). The artifact holds the step table and probe.json (the stub run is probe cloud-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 with Claude said: Not logged in · Please run /login.
  • With this step and a fake sk-ant-oat01- token, on production (local run): pass Claude login passed ("own has-own-login"). The reply then failed 401 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) answers auth.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-journey holds 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