Cloud agent start: a new machine's first chat asks the owner answer again on the live link instead of failing closed

Merged
TE
6d ago
+135-18

Leo, on cmux NEXT nightly 4113890 (classic link, signed in, New Agent Chat on a new Cloud machine): "Couldn't start Codex: kind-aqua-sandpiper could not confirm what kind of Cloud machine it is, so no agent chat starts there." [Retry]. Retry cleared it. Claude Code showed the same card.

Cause

AgentSessionAttachClient.start (and trust) read the Cloud link's owner answer (CloudMachineLink.ownerLink: the link id while the running link's /api/vm attach answer said vmKind: "owner", else nil) before it connected, and refused with agent_session.host_unverified unless the same id came back once the connection was live (AgentSessionAttachClient.swift ownerAnswer, from #26330). Each start uses a fresh client, so its connection asks for the link's socket. On a new machine, or a machine whose link is down and comes back on use (#26786), the link is not up at that first read: the read is nil (or an id the restart then replaces), so the start failed closed even though the owner answer arrived during the connect. Retry finds the link up, so it passes.

The backend sends vmKind on every classic attach answer (web/services/vms/workflows.ts vmKindForCaller), so nothing is missing server-side for a machine on the caller's personal account. A machine on a Stack team (one member included) is a separate cause: its answer is always team and Retry never helps (#28479, open).

Fix

ownedConnection (start and folder trust) counts an owner answer only on a connection that connect() began after the first owner read, and only when the read once the connection is live gives the same non-nil id. Otherwise the link came up or came back while the call connected:

  • That connection is retired without telling any attachment.
  • Both reads are taken again around a new connection over the live link.
  • At most 3 tries, with no wait between.

Unchanged:

  • An owner answer never carries over to another link.
  • A link that never answers owner (a team machine, no link) is refused with the same code.
  • start_unsupported and model checks still come before the owner refusal.
  • A start whose connection ends after the check still fails rather than reconnecting (aStartNeverReconnectsAfterTheOwnerCheck).

One change for an attached client: it keeps its connection and is refused instead of retried. The app never starts a chat or asks trust on an attached client: runRemoteStart and the pane's trust ask each make a fresh one.

Tests

Each test is committed before its fix:

  • aStartWhoseLinkCameUpWhileConnectingAsksAgainOnTheLiveLink (eed66521f9d, red on the base):
    • Owner reads nil,3,3,3 / 3,4,4,4 / nil,nil,5,5 start with vm_kind: "owner".
    • The re-ask makes a second connection: two identify.
    • A link that flaps for 3 tries is refused.
    • Trust passes the same way.
  • anOwnerWordGoesOnlyOnAConnectionMadeInsideTheReads (3ba54477141, red on the first fix):
    • An earlier call's connection is remade inside the reads.
    • An attached client is refused, and its page gets no agent-session-closed.

Command: cmux-ci run --class light --script scripts/ci/package-test-lane.sh --ref <sha> --arg=suite --arg=Packages/macOS/CmuxNext --arg=AgentSessionStartOwnerLinkTests --label suites, at eed66521f9d (expected red) and at the head (expected green).

Observed / Not run

  • Observed: scripts/cmux-next/prepush-gate.py passes (no new violations).
  • Not run yet: the Swift suite above, red and green. This lane VM has no Swift toolchain and no fleet client. The results go in a comment before landing.
  • Read, not run: the live repro on a new Cloud machine.

No capture: no UI change; the error card and its text are unchanged.

Changelog

  • Fixed: a new chat on a new Cloud machine no longer fails with "could not confirm what kind of Cloud machine it is" while the machine's link is still coming up.

Agent session: agent session

šŸ¤– Generated with Claude Code