feat(code): railway code — launch a coding agent on a Railway cloud agent VM - #1013
Draft
codyde wants to merge 13 commits into
Draft
feat(code): railway code — launch a coding agent on a Railway cloud agent VM#1013codyde wants to merge 13 commits into
railway code — launch a coding agent on a Railway cloud agent VM#1013codyde wants to merge 13 commits into
Conversation
…th your local sign-in New top-level `railway code` agent launcher. `--codex` copies the user's existing ~/.codex/auth.json (the flow OpenAI documents for remote machines and containers) into a fresh or reused sandbox and drops into an interactive codex session over the relay. - consent-gated, client-side read of the credential; it rides ssh stdin into a 0600 file in the sandbox — never argv, Railway variables, or images - seeds COLORTERM + codex folder-trust config (only when absent) - installs @openai/codex in the sandbox when missing (marker-based errors distinguish no-npm / install-failed / relay failure) - reuses the active RUNNING sandbox; --new forces a fresh one; 30m idle cap - ports run_native_ssh_captured (stdin-payload ssh) into ssh::native - create_and_store now returns the sandbox id for reuse by launchers Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… real errors Plumbing ssh now captures stderr and retries up to 3x with backoff for transient relay/boot failures. Host-key verification failures fail fast with remediation instead of being retried — that's a security signal. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…dbox Provisioning seeds a guarded ~/.profile block: plain connects (railway sandbox ssh) run bash as a login shell (verified against a live sandbox), so interactive sessions drop straight into codex. Not exec'd — quitting codex lands in a shell. [ -t 1 ] keeps command/scp sessions out; env guard prevents re-entry; grep guard keeps the seed idempotent. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ING boxes - print how to get back in on disconnect (sandbox ssh / code --codex) and the exact env-scoped 'sandbox list' invocation — 'railway sandbox ls' from a differently-linked dir silently shows a different environment - reuse now also matches CREATING sandboxes so a re-run seconds after a launch doesn't mint duplicates - --idle-timeout <minutes> (default 30) controls how long the box survives after disconnect Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…n script The relay fleet answers with per-instance host keys, so every fresh TCP connection is a new host-key lottery. railway code was making four (seed, inject, install, launch) versus sandbox ssh's one — which is why it failed host-key verification far more often than sandbox ssh against the same sandbox. - provisioning is now ONE script over ONE connection (credential still arrives via stdin, markers still distinguish failure modes) - every ssh in a run shares an OpenSSH ControlMaster (ControlPersist=90s): the provisioning connection is verified once and the interactive launch multiplexes over it — verified the Go relay accepts muxed sessions - run_native_ssh_with_opts / extra_opts on the captured variant let the launcher opt in without changing the shared ssh paths Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rministic Relay connections verify against the CLI's own file (~/.railway/known_hosts_relay, accept-new) and heal it on mismatch — never the user's ~/.ssh/known_hosts. With the relay fleet presenting 7+ per-instance host keys behind one hostname, single-key pinning was both futile (most connections mismatch) and security theater (TOFU re-accept is indistinguishable from MITM). Documented for revert to strict checking once the relay ships a shared host key / SSH CA. Validated: 10/10 consecutive fresh create→provision→launch→destroy cycles, ~6s each (image ships codex preinstalled; install step short-circuits). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…h GitHub auth --variable / --env-file ride the same server-side resolution as sandbox create (reference forms like postgres.DATABASE_URL work); a note warns when they're ignored because an existing sandbox is being reused. --gh reads the host token via `gh auth token` (works regardless of where gh stores it — macOS keychain included), fails fast before a sandbox is spent, and provisions the sandbox with a 0600 token file, a GH_TOKEN profile export, and a git credential helper for github.com/gist — no gh install required in the box; gh honors GH_TOKEN natively if present. Token rides ssh stdin over the same multiplexed connection; the consent prompt names it when --gh is set. Verified e2e: fresh sandbox, variable visible in env, token file 0600, api.github.com authenticates, credential helper set. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… the session The interactive launch exec'd codex, so quitting it tore down the whole ssh session. Now (matching the ~/.profile autostart behavior) codex runs un-exec'd and hands off to a login shell on exit; the exported RAILWAY_CODE_AUTOSTARTED guard keeps that shell's profile from relaunching codex on top of the user. Scripted runs (-- args) keep the exec-and-exit behavior so pipelines don't hang on a trailing shell. Verified live: Ctrl-C out of codex → shell responds → exit closes the session; `-- --version` still exits cleanly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ning The sandbox image doesn't ship distro bubblewrap yet, so codex warns at startup and falls back to its bundled copy (cosmetic — sandboxing works either way). Provisioning now apt-installs it when absent (~8s on fresh boxes); the command -v guard makes this a free no-op once the image ships bwrap. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…us updates
- railway code --claude: mints a long-lived token by running `claude
setup-token` invisibly under script(1)'s pty (the browser round-trip
stays interactive; nothing — least of all the token — renders in the
terminal), with CLAUDE_CODE_OAUTH_TOKEN / ANTHROPIC_API_KEY env
passthrough and a masked paste prompt as fallbacks. The token rides
ssh stdin into a 0600 env file sourced at launch and on reconnect
- onboarding disable MERGES into the image's pre-baked ~/.claude.json
via jq (node fallback) — the write-when-absent seed never fired
because the image build stamps that file; local ~/.claude/settings.json
is mirrored into the sandbox when present
- credential copy is announced instead of consent-prompted (drops --yes)
- quiet launch output: one-line sandbox create (CreateReport::Quiet),
ensure_ssh_key_quiet drops the key announce for plumbing connections
- terminal-state reset after the agent TUI exits (remote and local)
kills the kitty keyboard protocol leak ("9;5:3u") on unclean exits
- agents refresh synchronously during provisioning: codex npm-installs
only on a real registry version gap (~2s check when current), claude
update no-ops when current — a background update always lost the race
against launch on fresh sandboxes, greeting users with codex's
update-available banner
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
codyde
force-pushed
the
feat/code-agents
branch
from
August 3, 2026 22:57
99bbcfd to
90e731b
Compare
railway code — launch Codex or Claude Code in a Railway sandbox with your own planrailway code — launch a coding agent on a Railway cloud agent VM
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
railway code --codex/--claude/--groklaunches a coding agent on a Railway cloud agent VM using your own credentials.Requires railwayapp/mono#34349 and railwayapp/mono#34350 deployed — the cloud agent API is Internal-only until then.
Why the VM does most of it
cloud-agent-basebakes every harness, andexpress-agent serve --agentsreconciles their config on every boot (MCP servers, hooks, onboarding/trust flags, autonomy posture). So this installs nothing and configures nothing — doing either would fight the reconciler for the same files.Deleted as dead code: the npm and x.ai installers,
AGENT-NO-NPMhandling, the codex/claude update snippets, codextrust_level, grok[ui] yolo, the~/.claude.jsononboarding merge, and the bubblewrap install. What's left is credential delivery and the launch.Credentials
Read client-side, announced, and delivered over ssh stdin into a 0600 file — deliberately not create-time variables, so they never reach the VM spec, an image, an argv, or server-side config. A reused agent is refreshed the same way a fresh one is.
auth.jsonclaude setup-tokengrant (orANTHROPIC_API_KEY), never the local sign-in's rotating refresh tokenLifecycle
Agents are durable and have no idle timeout, so disconnecting sleeps the agent and the next run wakes it, with the disk intact.
--keep-awakeleaves it running;--rmdestroys it.Reuse is keyed per environment, not a single global slot. When the local pointer is missing (second machine, wiped config) it adopts your sole existing agent rather than silently minting a duplicate that bills forever — scoped with
mine: true, without which it could adopt a teammate's box.Two fixes worth review
~/.claude/settings.jsonis merged, not overwritten. On an agent VM that file is co-owned — express-agent writes the railway and playwright MCP servers and its hook entries there — so the previous truncating write would have stripped the harness's Railway tools until the next boot. Merged withhooks/mcpServersleft to their owner.cd. The machine spec setsworkDirfor the workload and every in-VM session (/app); forcing$HOMEoverrode a platform default and landed outside the dirs express-agent reconciles trust into.Verification
cargo check/fmtclean, 550/550 tests.schema.jsonregenerated from the mono branch.