feat: railway code — launch Codex or Claude Code in a Railway sandbox with your own plan#1013
Draft
codyde wants to merge 12 commits into
Draft
feat: railway code — launch Codex or Claude Code in a Railway sandbox with your own plan#1013codyde wants to merge 12 commits into
railway code — launch Codex or Claude Code in a Railway sandbox with your own plan#1013codyde wants to merge 12 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>
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.
What
railway codelaunches a coding agent in a Railway sandbox on the user's own plan — authenticated, interactive, ~6 seconds end-to-end:Requires the
PROJECT_SANDBOXESfeature.The two agent flows
--codexUses the flow OpenAI documents for remote machines/containers: the local
~/.codex/auth.jsonsign-in is read client-side and rides ssh stdin into a 0600 file in the sandbox — never argv, never Railway variables, images, or server-side config. The copy is announced to the user (no data leaves the machine silently).--claudeDeliberately not a copy of the local sign-in:
~/.claude/.credentials.jsoncarries the OAuth refresh token, and two machines racing one rotating refresh token can sign the laptop out. Instead the CLI mints a purpose-made long-lived token — the same shape as mono's agent-vm Connect tab flow (CLAUDE_CODE_OAUTH_TOKEN, ANTHROPIC_API_KEY accepted as alternative):CLAUDE_CODE_OAUTH_TOKEN/ANTHROPIC_API_KEYin the local env → used silently.claude setup-tokenruns invisibly underscript(1)'s pty (claude's TUI needs a tty on both ends). The browser round-trip stays interactive — an existing session with prior consent completes hands-free — while the terminal shows only a spinner. Nothing renders on screen, least of all the minted token; it's harvested from a 0600 recording that's deleted immediately, with ANSI-stripping + soft-wrap reassembly. A 120s timeout catches the one path that can't work hidden (paste-a-code-into-the-terminal) and falls back to a masked paste prompt.KEY=VALUE, so both var names work), sourced at launch and on every reconnect.Two sandbox-image gotchas handled: the image ships a
~/.claude.jsonwithouthasCompletedOnboarding(the build stamps it), so the flag is merged in via jq (node fallback) rather than written-when-absent — otherwise claude ignores the token and shows the login picker. And the user's local~/.claude/settings.json(permissions mode, model, plugins) is mirrored into the sandbox when present.Keeping agents current
Fresh sandboxes launch the image's baked agent version, so a background update always loses the race against the launch (codex then greets the user with its update-available banner). Updates therefore run synchronously during provisioning, behind the spinner, kept cheap: codex compares installed vs registry (~2s when current) and npm-installs only on a real version gap;
claude updateno-ops when current (the image setsautoUpdates: false, so this is the only updater). Both aretimeout-bounded so a wedged registry can't hang provisioning.DX details
~/.railway/known_hosts_relay, accept-new, self-healing) — the user's~/.ssh/known_hostsis never touched. Works around the relay fleet presenting per-instance host keys; commented for revert to strict checking once the relay ships a shared key/CA.9;5:3u. A reset sequence now runs remotely after the agent exits and locally after the session ends — no-ops on a clean terminal.~/.profileseed reads~/.railway-code-agent, so any interactiverailway sandbox sshdrops back into whichever agent was last provisioned; quitting the agent lands in a shell.--newforces fresh; disconnect prints reconnect breadcrumbs.--variable/--env-filehave fullsandbox createparity;--ghprovisions a 0600 token file +GH_TOKENexport + git credential helper.Validation
claude -presponded using the injected token; update snippet verified to no-op at current version.Notes for reviewers
railway code --codex— launch OpenAI Codex in a sandbox with your own sign-in #1006 (branch renamedfeat/code-codex→feat/code-agentsnow that it's two agents).railway code --codex— launch OpenAI Codex in a sandbox with your own sign-in #1006; the Claude flow uses Anthropic's documented remote-machine mechanism (claude setup-token).🤖 Generated with Claude Code