Agentic up: signup-aware single front door — auth on the fly, create-on-no-link, device-code fallback, structured JSON contract#938
Merged
Conversation
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>
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
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>
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.
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).
railway upas the single front door: unauthenticatedupsigns 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 --newforces a fresh project even when linked; new flags-y/--yes,--name,--no-wait(alias--detach),--workspace.open; CI (--cior any truthyCIenv) /--json/ no-human fail fast with structuredNOT_AUTHENTICATED.--detachreports "Build queued", a dropped status stream reportsDEPLOY_STATUS_UNKNOWNinstead of implying success.cliAuthEventTrackper auth attempt (transport + outcome incl. typedtimed_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).last_checkedstamps regardless of fetch outcome so GitHub rate-limits don't retry-storm.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)up --jsonunderCI=1/CI=yesemits a single parseableNOT_AUTHENTICATEDJSON line, exit 1🤖 Generated with Claude Code