Skip to content

perf(ca): attach by probing the route instead of polling status to RUNNING - #1098

Merged
codyde merged 2 commits into
masterfrom
cody/ca-eager-attach
Aug 13, 2026
Merged

perf(ca): attach by probing the route instead of polling status to RUNNING#1098
codyde merged 2 commits into
masterfrom
cody/ca-eager-attach

Conversation

@codyde

@codyde codyde commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

What

railway code and railway ca ssh polled agent status until RUNNING before attaching. RUNNING lands ~2.5-3s after the shell is actually connectable: it waits out route publication the tunnel doesn't use, a ~0.7s status-projection hop, and a poll interval (code.rs still had its own 2s poll — the adaptive poll from #1095 never covered it).

Now: probe the route directly every 750ms and attach on the first success. Status is still fetched each round, but only to fail fast on crashed/failed/deleting. Applies to create, wake, and ca ssh paths.

The probe is output-verified, not exit-code-verified

Found during live testing: the relay answers a session against a target it can't route yet with a status JSON and a clean exit 0, so ssh <target> true reports success seconds before a command can run — an exit-code probe attaches prematurely and the session dies with "Provisioning produced no status marker". The probe asks the guest to echo a marker and succeeds only when the marker round-trips.

Server pairing

Pairs with mono #35462 (route agents at STARTING_WORKLOAD). Ships safely in either order: against today's servers the probe first succeeds at RUNNING — verified live with a dev build (prompt at 14.5s, same as release; the ~3s win arrives when the server side deploys).

Cost

2-5 probe connections per launch through the relay (each is one route auth + a no-op session), inside the per-IP budget.

Testing

  • 1104 tests pass, no new warnings
  • Live dev-build runs against prod: no premature attach, no behavior change pre-gate

🤖 Generated with Claude Code

…NNING

The launcher and `ca ssh` waited for status RUNNING, which arrives only
after route publication, a Kafka projection hop, and a poll interval —
none of which the shell needs. Probe the SSH route (asking the guest to
echo a marker) every 750ms and attach on first success; status is still
read each round to catch crashed/failed/deleting early.

Success is the marker round-tripping, never the exit code: the relay
answers a session against a target it cannot route yet with a status
JSON and a clean exit 0, so `ssh <target> true` reports success seconds
before a command can actually run (verified against prod).

Pairs with the server routing agents at STARTING_WORKLOAD (mono #35462);
without it the probe simply succeeds at RUNNING, which still beats the
old 2s poll that `railway code` was using. Removes the now-unused
ensure_running and code.rs's private wait_until_running.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@codyde
codyde force-pushed the cody/ca-eager-attach branch from daac0ac to e85aa7e Compare August 13, 2026 16:30
@codyde codyde added the release/patch Author patch release label Aug 13, 2026
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@codyde codyde added release/skip Author no release and removed release/patch Author patch release labels Aug 13, 2026
@codyde
codyde merged commit 34ce4a2 into master Aug 13, 2026
9 checks passed
@codyde
codyde deleted the cody/ca-eager-attach branch August 13, 2026 16:59
codyde added a commit that referenced this pull request Aug 15, 2026
* fix(ca): an agent with a pane open is not still "waking"

Create a new cloud agent, land in its session, and the tree still calls it
"waking" while you are typing into it. The row renders the platform's raw
status, and `sorted_agents` already documents the vocabulary: "running first,
waking next" — where the middle rank is `starting`.

That status lags routability by design. #1098 established the gap: the
platform routes a shell as soon as the container exists, while RUNNING
additionally waits on route publication, a projection hop, and a poll
interval. The launcher stopped gating attach on it and started probing the
route instead — but the tree kept rendering the value the CLI itself had
already decided not to trust.

An attached, unfinished pane is direct evidence the agent is up: there is a
shell running on it. So believe the pane over the projection.

Scoped to `starting` only. A pane can outlive what it is attached to, and
sleeping/crashed/failed/deleting alongside a live pane is news the user needs
rather than latency to paper over.

Three places, so display and state agree:

- the row's status and note read `running` once a pane is open;
- `sorted_agents` ranks on the displayed status, so the row does not sit in
  the waking band while reading `running`;
- `attach_session` clears a settled wake op and its watch. A pane opening
  proves the wake landed, and it stops the 1.5s watch tick polling that
  environment for up to WAKE_PATIENCE about an agent already taking
  keystrokes. Sleep and delete are untouched — neither is proven by an attach.

Known limitation, measured after the fact: a 10-agent benchmark put the
routine lag at a few seconds, closing before the session is usable (status
reached RUNNING before the harness answered in all 10 runs). The case that
prompted this was an agent that stayed `starting` and had to be deleted — and
this change would render that one `running` too, hiding the only signal
something was wrong. A follow-up should bound the override to a short grace
window after attach and surface a persistent `starting` explicitly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(ca): stop excusing a starting agent once the pane is old

The override shipped in the previous commit was unbounded: any open pane made
a `starting` agent read `running`, forever. That hides the case that prompted
the fix — an agent that stays `starting`, routes SSH fine, and has to be
deleted. The row would have painted it green and removed the only signal the
user had.

Bound it to the window the lag actually lives in. Measured over ten
`railway code --claude --new` runs, status reached RUNNING 5.3–14.2s after
create, before the harness answered every time. 45s from pane open is
generously past that, so a slow-but-healthy boot never flickers into looking
stuck, while an agent still `starting` past it goes back to reporting what the
platform says.

`agents_with_live_panes` now carries how long each pane has been open rather
than just which agents have one, via a new `Session::open_for`. The decision
stays a pure function of (status, pane age), so the boundary is unit-tested
directly instead of through a fabricated Session clock.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release/skip Author no release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant