Skip to content

Agentic up: signup-aware single front door — auth on the fly, create-on-no-link, device-code fallback, structured JSON contract#938

Merged
codyde merged 32 commits into
masterfrom
fix/non-interactive-browser-login
Jun 5, 2026
Merged

Agentic up: signup-aware single front door — auth on the fly, create-on-no-link, device-code fallback, structured JSON contract#938
codyde merged 32 commits into
masterfrom
fix/non-interactive-browser-login

Conversation

@codyde
Copy link
Copy Markdown
Collaborator

@codyde codyde commented Jun 4, 2026

Summary

CLI half of the agentic signup flow (Agentic Up RFC, PR split plan). Merge after mono #30709 + #30710 are deployed (telemetry/attribution land best-effort, so mis-ordering loses funnel data, not correctness).

Branch name undersells the scope — it grew from a non-interactive login fix into the full agentic up surface.

  • railway up as the single front door: unauthenticated up signs in / signs up on the fly (unified OAuth — new accounts created server-side), then creates a project + service from the cwd, links, and deploys. Creation is auto-authorized for a just-completed signup, explicit -y, or a detected agent harness; an interactive human gets create / link-existing / cancel; non-interactive non-agent runs error — never a silent create from a script.
  • up --new forces a fresh project even when linked; new flags -y/--yes, --name, --no-wait (alias --detach), --workspace.
  • ExecutionContext: truth-tabled auth routing (output mode, TTYs, agent harness, browser reachability). Browser when reachable, device-code on SSH/no-DISPLAY/failed open; CI (--ci or any truthy CI env) / --json / no-human fail fast with structured NOT_AUTHENTICATED.
  • Structured output layer: stdout carries one JSON result or error object (NDJSON build logs in attached mode); error taxonomy with codes + hints; exit code derives from the deployment's terminal state — a FAILED deploy can't exit 0, --detach reports "Build queued", a dropped status stream reports DEPLOY_STATUS_UNKNOWN instead of implying success.
  • Resilience: project links to the cwd before upload so a failed upload retries into the same project instead of minting duplicates.
  • Telemetry: cliAuthEventTrack per auth attempt (transport + outcome incl. typed timed_out); deploy attribution headers (x-railway-caller / x-railway-agent-session) on the tarball upload, sent only when an agent harness drives the CLI (supersedes closed feat(telemetry): propagate caller/agent-session as HTTP headers on the up upload #899 at the deployment-event grain — see its close note for the session-grain analysis).
  • Skill auto-update: 12h-gated staleness check + safe apply (never clobbers user-modified files); last_checked stamps regardless of fetch outcome so GitHub rate-limits don't retry-storm.
  • Factory Droid harness detection via process tree + env.

Review note

Two file-disjoint passes verified feasible: auth/flow (up.rs, login.rs, exec_context.rs, errors.rs, reporter.rs, telemetry.rs, upload.rs) vs updater (skills.rs, setup.rs, consts.rs).

Test plan

  • cargo test: 239/239 (incl. the harness-detection test under a live agent harness)
  • Smoke-tested: unauthed up --json under CI=1/CI=yes emits a single parseable NOT_AUTHENTICATED JSON line, exit 1
  • E2E signup → create → deploy validated against railway-develop

🤖 Generated with Claude Code

codyde and others added 30 commits May 21, 2026 10:01
Today `railway login` exits immediately with "Cannot login in
non-interactive mode" when stdout isn't a TTY — blocking agents
(Claude Code, Cursor, etc.) that invoke the command via a captured
shell. The gate was there to protect the interactive "Open the
browser?" prompt, but the browser-launch path itself doesn't need
stdin or a TTY: it opens a browser via `open` and waits on a
localhost TCP listener for the OAuth callback. If opening the
browser fails, `browser_login` already falls back to device flow.

Drop the unconditional bail. When stdout isn't a TTY (and
`--browserless` wasn't requested), skip the prompt and go straight
to `browser_login`. Keep the bail for `--browserless` + non-TTY:
that flow prints a user code the human types into another browser,
which has nowhere visible to land without a terminal.
RAILWAY_API_TOKEN / RAILWAY_TOKEN remain the env-var path for true
headless automation.

Smoke-tested from a non-TTY shell: now reaches the auth flow
instead of exiting immediately.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ss auto-detect

- Print the auth URL to the terminal alongside opening the browser
  so the user has a copy-paste fallback when the wrong
  browser/profile/tab opens, or for debugging.
- Detect likely-headless environments ($CI, $SSH_CONNECTION, or
  Linux without $DISPLAY) and skip the doomed `open` attempt by
  routing straight to device-code flow.
- Restructure device-code output to mirror browser-flow ("Sign in
  at:" / "Enter this code:") so both paths look consistent.
- Tighten progress messaging:
  - Spinner reads "Waiting for sign-in..." (was "Waiting for
    authentication...").
  - Successful login is "✓ Signed in as ..." with a green check,
    matching the visual style used elsewhere in the CLI.

Smoke-tested:
- CI=1 (headless) → device-flow URL + code, no doomed browser open.
- Non-TTY (agent shell), no headless env → browser_login URL
  printed + browser open attempt (unchanged from Phase 1.0).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three composable pieces of the agentic signup flow:

- New top-level `create` command with `account` subcommand:
  `railway create account` is a thin wrapper around `login --signup`
  with brand-new-user copy.

- `login --signup` flag plumbs through to browser_login and adds
  `&intent=signup` to the OAuth authorization URL so the
  frontend/backend can route signup-bound CLI sessions to a
  signup-friendly landing page (when that lands).

- `railway up` detects unauthed state on entry and:
  - In TTY: shows a clack-style picker ("Create a new account and
    deploy" / "Log in and deploy"), then chains into login::command
    with the appropriate signup flag. Continues with `up` after
    auth succeeds.
  - In non-TTY / --ci / CI env / --json: bails with a clear message
    pointing at `railway login` or `railway create account`,
    instead of the previous cryptic GQLClient-creation error.

Smoke-tested:
- `railway create --help` / `railway create account --help`
- `railway login --help` shows the new --signup flag
- `railway up` from a non-TTY shell without auth bails cleanly with
  the new error message

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`Configs::env_is_ci()` only treats `CI=true` as truthy and ignores
common values like `CI=1`. The unauthed `railway up` bail was using
the strict check, so `CI=1 railway up` (without auth) fell through
to the clack picker instead of printing the helpful instruction.

Promote `is_likely_headless()` in login.rs to pub and reuse it from
up.rs. It accepts any non-empty `$CI` value plus `$SSH_CONNECTION`
and an empty `$DISPLAY` on Linux — same heuristics that already
gate browser-open vs device-code routing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cherry-picks the authed-deploy bones from feat/railway-new-app's
new_app.rs onto the agentic-signup branch, adapted for master's
shape of ProjectCreate (no agentPrompt) and UpResponse (no
service_id).

- New `railway create app` subcommand: workspace pick → project
  create → tar upload → link project → wait-for-serve. Surfaces
  the GitHub remote when detected (informational; full GH App
  integration is a separate piece).
- `railway up` first-run path: when the user just authed through
  the clack prompt AND there's no project linked to the directory,
  fall through to `create app` so they land on a deployed app
  instead of bouncing off "no project specified". Existing authed
  users still see the standard error (no accidental auto-creation
  on every `up`).
- Cherry-picked util/git.rs (GitHub remote detection) and
  util/detect.rs (framework + data-store hints) from
  feat/railway-new-app. detect.rs isn't wired into create app yet —
  reserved for a follow-up that connects data-store hints to the
  provisioning workflow.
- Cherry-picked pick_workspace into workspace.rs (workspaces() was
  already on master).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`upload_deploy_tarball` takes a reqwest::Client that must carry the
Bearer token in default headers (backboard's
/project/:id/environment/:id/up returns 401 otherwise). The cherry-
picked code constructed a fresh reqwest::Client::new() — unauth —
so every create-app upload 401'd after the project was created.

Use the GQLClient::new_authorized reqwest client (already in scope
from the ProjectCreate mutation) for the upload too.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
backboard's /up endpoint creates a service implicitly during the
tarball upload but doesn't return its id in the response on master.
The previous `create app` linked the project but not the service,
which left subsequent `railway logs` / `railway service` etc. with
no service context — `logs` would just no-op.

Extract the service id from the logs_url (shape
`.../project/<pid>/service/<sid>?...`) and call link_service after
link_project so the directory is fully bound to the deployment.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When the user runs `railway login --signup` (or its wrapper
`railway create account`), they've already declared intent to open
the browser. Prompting "Open the browser?" right after is friction
on top of friction — the whole point of the command is the browser
flow.

Skip the prompt when --signup is set and route straight to
browser_login. Browser-launch doesn't need a TTY anyway (we already
do this for non-TTY shells), so this is a natural extension of
that behavior. `railway login` without --signup keeps the prompt
as today, since it might be invoked when the user wants the
device-code flow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
For `railway login --signup` (and the `railway create account`
wrapper), once the OAuth callback hits localhost and the CLI has
captured the token, the browser tab still has the user's attention.
The default "Authentication successful — close this window" page
is fine for an existing user but jarring for a brand-new account:
they finished signup and we drop them on a dead-end page.

When the login flow had signup intent, swap the served HTML:
 - Heading reads "Welcome to Railway!" instead of "Authentication
   successful!".
 - Body copy reads "Taking you to your dashboard…" instead of
   "You can close this window…".
 - Add `<meta http-equiv="refresh" content="2;url=https://{host}/
   dashboard">` so the browser navigates to the dashboard ~2s later.

The CLI's TCP listener doesn't need the browser to stay open past
this point — the auth code was already captured before send_response
fires. Plumbed via a new `signup` parameter on wait_for_callback +
a new `redirect_to_dashboard` parameter on send_response.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
OAuth handles both new-account creation and existing-user sign-in
identically — there's no useful distinction for the CLI to declare.
Backend detects brand-new accounts on its own (user.createdAt < 2
minutes) and tags the OAuth callback URL with `new_user=1`. The
CLI just reads that tag from the callback and adapts the served
landing page.

Changes:

- `railway up` (unauthed): drop the "Create new account / Log in"
  clack picker. We just say "Opening browser to sign in or sign
  up…" and route to login. Less friction, identical outcome.

- `railway login`: drop the "Open the browser?" confirm prompt
  altogether. It only ever existed to choose between browser and
  device-code flows, but --browserless and the headless env
  detection already cover the device-code case. The browser is
  the canonical default.

- `--signup` flag and `signup` plumbing removed entirely. The flag
  did three things — add intent=signup to the OAuth URL, skip the
  prompt, redirect to dashboard after callback. With backend-side
  detection none of those need to be CLI-declared: backend tells
  us. `railway create account` still exists as a discoverable
  command; it just dispatches through `login` with no extra args.

- wait_for_callback parses `new_user=1` from the OAuth callback URL
  (backend appends it for fresh accounts) and toggles the served
  landing page between "Welcome to Railway / taking you to the
  dashboard" and the standard "Authentication successful / close
  this tab".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per the spec for `railway up`:

- Unauthed users now get a confirm before the browser tab opens
  ("Sign in or sign up to continue?" — default Yes). Catches the
  case where a user accidentally typed `railway up` and doesn't
  actually want a tab spawned.

- `-y / --yes`: accept all defaults. Skips the auth confirm above
  and (in the chained `create app` step) skips the project-name
  prompt, using the current directory's basename as default. Build
  logs still stream so the user sees progress; `-y` is about
  bypassing prompts, not silencing output.

- `--no-wait`: alias for the existing `--detach`. After the deploy
  is queued, return immediately with deployment info instead of
  attaching to the log stream. Same semantics as `create app
  --no-wait`.

- `railway up -y --no-wait` is the fully-unattended combination:
  browser opens (still needed for OAuth), user signs in once, and
  the CLI returns deploy info without further prompts or log
  streaming.

`railway create app` gets a parallel `-y / --yes` flag and a new
interactive project-name prompt (inquire::Text with the directory
basename as default). The prompt is skipped when `-y` is set, when
`--name` is explicit, or when stdout isn't a TTY. When `up`
fallthrough-chains into `create app` after fresh auth, it
propagates both `--yes` and `--no-wait` (mapped from `--detach`).

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

Two follow-ups from real-world testing of the agentic signup flow:

- `railway login` (and create account) now always serve a 2-second
  dashboard redirect on the success page — not just new users.
  Brand-new users still see "Welcome to Railway!" framing; existing
  users see the standard "Authentication successful" but get sent
  to the dashboard after the confirmation moment instead of being
  left on a dead-end tab.

- `railway create app` switches from URL polling (wait_for_serving)
  to real-time build + deploy log streaming, matching `railway up`'s
  default. Uses stream_build_logs + stream_deploy_logs on the
  deployment_id returned from the upload. `--no-wait` still bails
  early with just the URL info — same as before.

Together these make the post-auth UX feel like one continuous
experience: signup → browser redirects you to the dashboard →
terminal streams the build → live URL prints when ready.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
create app was streaming build + deploy logs but had nothing
watching the deployment status, so a failed build kept the streams
running indefinitely and the user had to ctrl+c.

Mirror `railway up`'s pattern: spawn a third task that subscribes
to the deployment status and process::exit's on terminal states:

- SUCCESS → print "🚀 Live at <url>" (or "✓ Deploy complete" if
  the URL didn't come through), exit 0.
- FAILED → print "✗ Build failed" + the logs URL, exit 1.
- CRASHED → print "✗ Deploy crashed" + the logs URL, exit 1.

The status task is fire-and-forget — process::exit kills the
log-stream tasks too, so there's no orphaning.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… --json error

Collapse signup/login into one OAuth flow surfaced by `railway up`, `railway create account`, and `railway login`. Unauthed `railway up` prompts and chains into account creation + project + deploy; agent harnesses (Claude Code, Cursor, Codex, …) are detected so the confirm prompt is skipped while a human completes the browser sign-in. `railway up --json` now emits a structured NOT_AUTHENTICATED error on stdout instead of a plain-string bail, so an agent can detect the unauthed state and run `railway create account`, then retry. Removes the experimental --github/--google/--email provider-hint flags. Corrects the is_non_interactive_agent doc comment: it gates prompt-skipping only and does not shorten any OAuth timeout. Service detection is local-diagnostic only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a single output reporter (util/reporter.rs) that owns the process output mode and the stream contract: stdout carries result-or-error JSON, stderr carries progress and warnings. RailwayError gains a stable machine `code()` (exhaustive — new variants must declare one) and a selective `hint()`, plus a NotAuthenticated variant. `main` now renders fatal errors mode-aware (a JSON object on stdout, or the human message plus hint on stderr) instead of `{e:?}`. First consumer: `railway create app` now warns (SERVICE_LINK_UNRESOLVED) instead of silently skipping when it can't recover the new service id. `emit_json` is the sanctioned JSON primitive, introduced ahead of migrating the existing ad-hoc call sites.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the auth-routing booleans that each command re-assembled by hand with one typed ExecutionContext (exec_context.rs). detect() gathers the environment; auto_auth() / login_transport() / agent_implicit_consent() are pure functions of its fields, covered by a 10-case truth table that is algebraically equivalent to the old gates. Unauthenticated `railway up` now fails fast with a structured NOT_AUTHENTICATED error when no interactive human can complete a sign-in, instead of opening a browser nobody can see; `railway login` uses the shared transport decision. Removes the now-orphaned telemetry::is_non_interactive_agent (folded into the context).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Both arms of the redirect_to_dashboard check produced the identical "Taking you to your dashboard…" body, so the parameter was dead. Remove it, collapse body_copy to a borrowed str, and refresh the now-stale comment (the new-user framing it described lives only in the caller's success message).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The backend no longer tags the OAuth callback with new_user=1 (signup detection moved to durable server-side compliance state), so the CLI's new_user parsing and its "Welcome to Railway!" success branch could never fire. Remove them — the callback page now always shows the standard "Authentication successful!" confirmation — and refresh the stale doc comments.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a public send_auth_event (mirroring send_setup_agent) and fire it from login::command with the transport (browser/device_code) and outcome (succeeded/timed_out/failed). Sent via a public mutation so timeouts and failures — which occur before a token exists — are still captured, attributed by caller/agent_session. Fires for direct `login` and the chained `up` / `create account` paths, making the browser-handoff conversion measurable across all entry points.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Previously an unauthenticated `railway up` failed fast whenever no local browser was reachable (SSH, no DISPLAY), forcing a separate `railway login --browserless` first — inconsistent with `railway login`, which device-codes in the same context. auto_auth now fails fast only when there's genuinely no human (JSON/CI, or captured stdout with no agent harness); when a human is present it proceeds with a browser if reachable, otherwise a device code the human completes on another device — so `up` can sign in and deploy in one shot on a remote box. The watching-agent narration names the transport. Truth table extended with the SSH-with-human and SSH-no-human cells.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The RFC documents the surface as `railway create account` / `railway create application`, but only `create app` shipped. Add `application` as a visible alias so the documented command works; `create app` stays the canonical name.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Follow-ups from manually testing the unauthed `up`/signup flow on dev:

- CI=true now fails fast like --ci. ExecutionContext::detect folds the
  CI env var into the `ci` field, so an unauthed implicit sign-in bails
  with NOT_AUTHENTICATED instead of hanging on a prompt / device-code
  poll in CI runners that present a TTY. (login_transport ignores `ci`,
  so explicit `railway login` is unaffected.)
- Not-authenticated hints lead with `railway login` and offer
  `railway create account` second (errors.rs hint + `create app` bail).
- `railway create account` gains `--browserless` (was hardcoded false),
  so a device-code flow can be forced, matching `railway login`.
- Announce the browser open before stealing window focus, and print a
  clear fallback line when no browser can open.
- Correct stale "detected via user.createdAt / account age" comments to
  the actual durable-compliance-state mechanism.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The agentic-up / `create app` deploy ended at "Deploy complete" with
nothing to hand back. Now print a summary on success (and on --no-wait):
the running URL when a domain exists, a hint to run `railway domain`
when it doesn't, plus the project name and a dashboard link to the
service. We deliberately do NOT auto-generate a domain — exposing a
service publicly stays the user's call.

Note: the service is created server-side and only its id is recovered
(from the logs URL), so the summary shows the project name + a
dashboard link that targets the service rather than echoing a service
name. Pending verification on a healthy builder.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drop the `railway create` command (account + app) — they were thin
wrappers (create account ≈ login; create app ≈ init + up) that added
surface without capability. Fold the create-app flow (project create +
bundle/upload + project & service link + build stream + end-of-run
summary) into `railway up` behind `--new`:

- authed + `--new`: create a fresh project even if one is linked
- authed + `-y`, nothing linked: auto-create + link + deploy
- unauthed cold start: login/signup → auto-create → deploy
- authed, nothing linked, no -y/--new: unchanged "no project" error

`railway login` remains the single auth entry (signs up new accounts on
the fly), so dropping `create account` loses nothing. Scrub the command
from the NOT_AUTHENTICATED hint, reporter tests, and comments.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Self-update installed agent skills, mirroring the binary self-updater.
SkillsManifest records installed skills + content hashes; the periodic
update task checks the upstream SHA (railwayapp/railway-skills@main, 12h
interval) and applies clean upgrades when auto-update is on. classify_skill
distinguishes not-installed / up-to-date / clean-upgrade / user-modified /
unverifiable; apply_skill preserves foreign files, removes dropped ones,
and never clobbers user-edited skills. Gated by the _RAILWAY_UPDATE_SKILLS
env; install_skills gains a force/quiet arg. Unit-tested.
…or humans

An agent harness now authorizes create-on-no-link for bare `railway up`
(no `-y` needed) — the same implicit-consent signal that skips the auth
confirm — so an agent can `railway up` and have it sign in and deploy the
current directory. An interactive human with no linked project gets a
create / link-existing / cancel choice (Link runs the real `railway link`
picker, then deploys) instead of a silent create or a dead-end error.
Non-interactive non-agent runs still error rather than create.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
is_agent_harness() only checked Layer 1 env fingerprints, but Factory
Droid does not reliably export an identifying env var into every spawned
shell, so unauthed `railway up` bailed with 'Not signed in' instead of
opening the browser for implicit-consent sign-in.

Fall back to the process-tree walk (which already maps a `droid` ancestor
to factory_droid for telemetry), aligning is_agent_harness() with
detect_caller(). Also match FACTORY_DROID_BINARY / FACTORY_DROID* env vars
as a cheap fast path. Adds tests for both signals.
…ven exit, link before upload

Review fixes on the deploy_new_project path:

- Honor --json: gate every human line and spinner, stream build logs
  as NDJSON, and emit a single structured result object (status,
  project/service/deployment ids, logsUrl, dashboardUrl, url,
  detectedServices) via the reporter. Previously this path printed
  human noise to stdout and no parseable result.
- Exit code now comes from the deployment's terminal state: the status
  stream is awaited in the foreground instead of fire-and-forget, so a
  FAILED deploy can no longer exit 0 just because the log streams
  closed early. A dropped stream surfaces DEPLOY_STATUS_UNKNOWN
  instead of implying success.
- Link the project to the cwd before bundle/upload so a failed upload
  retries into the same project instead of minting a duplicate.
- Typed NotAuthenticated on the direct `up --new` unauthed bail
  (structured NOT_AUTHENTICATED in --json instead of generic ERROR).
- --detach summary says "Build queued" instead of claiming "Deploy
  complete"; summary glyphs match the clack style; transport-aware
  doc comments.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…stamping, droid-test env isolation

- env_is_ci() accepts any truthy CI value (CI=1, CI=yes, ...) instead
  of only CI=true, consistent with is_likely_headless — an unauthed
  `up` on a TTY-allocating runner with CI=1 now fail-fasts with
  NOT_AUTHENTICATED instead of hanging on a device code.
- Auth-funnel outcome matches the typed OAuthDeviceCodeExpired error,
  so device-code expiry reports timed_out instead of failed (the
  browser path's anyhow-context timeout string still matches).
- Skills staleness check stamps last_checked regardless of the fetch
  outcome, so a GitHub rate-limit/network failure waits the full 12h
  interval instead of re-firing on every invocation.
- detects_factory_droid_via_binary_env_var clears all ambient
  STRONG_AGENT_ENV vars (and AGENT) before asserting, so it passes
  when the suite itself runs under an agent harness.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
codyde and others added 2 commits June 4, 2026 12:54
Closes the last gap in the signup → deploy funnel (RFC: Agentic Up,
deploy attribution). The upload to backboard's /up endpoint now carries
x-railway-caller and x-railway-agent-session headers — but only when an
agent harness is driving the CLI, so header presence is itself the
agent signal and human deploys are untouched.

The agent-session resolution chain is extracted from the telemetry
event context into resolve_agent_session_id() and shared by both, so
the deploy event joins against the auth funnel on identical values.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-browser-login

* origin/master: (28 commits)
  chore: Release railwayapp version 5.0.0
  feat(sandbox): fork, templates, and `--variable`/`--env-file` on create (#933)
  chore: Release railwayapp version 4.68.0
  feat(volume): include modified time in files JSON (#931)
  chore: Release railwayapp version 4.67.0
  Add service source connection support (#934)
  chore: Release railwayapp version 4.66.2
  Make GraphQL HTTP timeout configurable via RAILWAY_HTTP_TIMEOUT (#932)
  chore: Release railwayapp version 4.66.1
  feat(volume): show status and scheduled deletion date in volume list (#928)
  SSH Command: Handle Identity Files (#926)
  chore: Release railwayapp version 4.66.0
  feat(sandbox): `railway sandbox` commands (create/list/ssh/exec/destroy) (#925)
  chore: Release railwayapp version 4.65.0
  SSH Agent Support, `russh` edition. (#915)
  chore: Release railwayapp version 4.64.0
  chore: Release railwayapp version 4.63.0
  Rephrase agent advisory and gate by CLI version (#919)
  Forward --remote to setup agent in cli.new installer (#918)
  chore: Release railwayapp version 4.62.0
  ...

# Conflicts:
#	src/consts.rs
#	src/util/mod.rs
@codyde codyde added the release/minor Author minor release label Jun 4, 2026
@codyde codyde merged commit 4a636b1 into master Jun 5, 2026
8 of 9 checks passed
@codyde codyde deleted the fix/non-interactive-browser-login branch June 5, 2026 16:21
codyde added a commit to railwayapp/docs that referenced this pull request Jun 5, 2026
…or (#1188)

railway up is now a complete on-ramp (CLI v5.x): unauthenticated runs
sign the user in (creating accounts on the fly), and with no linked
project it can create a project + service from the current directory.

- up: document -y/--yes, --new, --name, --workspace, --no-wait alias,
  and the previously undocumented -m/--message; add the "First run:
  sign up and deploy" section (auth-on-the-fly, create/link/cancel for
  humans, structured fail-fast for scripts/CI); detached mode now
  notes it returns at QUEUED with a deployment-list polling example;
  exit codes clarified (terminal-status driven).
- login: sign-in and sign-up are the same flow (accounts created on
  the fly, ToS/Fair Use accepted while authorizing); automatic
  device-code fallback on SSH/headless; works from non-interactive
  shells.

Pairs with railwayapp/cli#938 — merge after that release ships.

Co-authored-by: Claude Opus 4.8 (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/minor Author minor release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant