Move the telemetry opt-out notice from a runtime banner (shown on first
CLI command) to the install script so users see it exactly once at
install time. Remove the Notices JSON persistence that tracked whether
the banner had been shown.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add `environment list` subcommand
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* style: improve environment list output spacing and separators
Add blank lines and a horizontal rule separator between environment
sections for better visual readability.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Switch from immediate `projectDelete` to `projectScheduleDelete` which
provides a 48-hour grace period before the project is permanently removed.
Made-with: Cursor
* fix(config): make RAILWAY_ENVIRONMENT_ID optional for project-level commands
Commands like `env new`, `env delete`, and `env link` only need a
project ID, not an environment. The previous validation required both
RAILWAY_PROJECT_ID and RAILWAY_ENVIRONMENT_ID to be set together,
which made these commands unusable with just RAILWAY_PROJECT_ID.
Make `LinkedProject.environment` an `Option<String>` and remove the
blanket XOR validation. Commands that need an environment now error
at the point of use with a clear message, while project-level
commands work with just RAILWAY_PROJECT_ID set.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: context-aware error messages for missing environment
When RAILWAY_PROJECT_ID is set, tell the user to set
RAILWAY_ENVIRONMENT_ID (since `railway environment` writes to local
config which is ignored when env vars take priority). When using local
config, suggest `railway environment` as before.
Also consolidates duplicated error messages across callers into the
central `environment_id()` helper.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: simplify error message for missing environment
The environment is only None when using env-var targeting (local config
always has an environment set via link_project), so drop the
unreachable branch.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: reject env-only override and defer environment preflight
P1: Reject RAILWAY_ENVIRONMENT_ID set without RAILWAY_PROJECT_ID
instead of silently falling back to local config.
P2: Make ensure_project_and_environment_exist() skip the environment
check when no environment is linked. Callers that accept --environment
resolve and validate it themselves, so the preflight no longer blocks
them.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add tests for env var project targeting validation
Extract resolve_env_var_project() from get_linked_project() so the
env var validation logic can be tested without Configs/auth/API deps.
Two tests:
- PROJECT_ID alone → succeeds with environment None
- ENVIRONMENT_ID alone → rejected
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: improve error messages and graceful status for missing environment
Provide actionable guidance (set env var, use --environment, or run
railway environment) and show "None" in status instead of crashing
when no environment is linked.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* style: fix rustfmt formatting in status.rs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: merge locally linked environment in project-only env-var mode
When RAILWAY_PROJECT_ID is set without RAILWAY_ENVIRONMENT_ID, fall
back to the environment from the local config so that `railway
environment` remains a valid remediation path.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: only merge local environment when project IDs match
Prevents silently using project A's environment with project B when
RAILWAY_PROJECT_ID overrides the locally linked project.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: resolve stale env name, nested dir lookup, and down panic in env-var mode
- Don't carry local environment_name when RAILWAY_ENVIRONMENT_ID override
is set, preventing preflight from validating against the wrong environment
- Use ancestor-walking lookup for local project so nested directories still
find the linked config in env-var mode
- Replace expect() with fallback in `down` command to avoid panic when
project/environment names are absent
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(config): allow project targeting via env vars without railway link
Add support for RAILWAY_PROJECT_ID, RAILWAY_ENVIRONMENT_ID, and
RAILWAY_SERVICE_ID env vars to configure the target project/environment
without requiring `railway link`. Useful for CI/CD and scripting.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* run cargo fmt
* fix(config): update environment variable checks for project targeting
Modify the `has_env_var_project_config` function to return true if either `RAILWAY_PROJECT_ID` or `RAILWAY_ENVIRONMENT_ID` is set, allowing for more flexible project targeting. Additionally, enforce that both environment variables must be set together in the relevant section of the code, providing clearer error messaging when one is missing.
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the GraphQL-based LoginSession flow and hyper HTTP server with
standard OAuth 2.0: Authorization Code + PKCE for browser login, and
Device Authorization Grant (RFC 8628) for browserless. Adds automatic
token refresh before commands, CSRF state verification, and proper
error variants. Removes hyper, hyper-util, http-body-util, and hostname
dependencies in favor of raw TCP + sha2 for PKCE.
Browser login hardened with OS-assigned port, looped callback handler
for preconnect resilience, 5-minute timeout, and prompt=consent to
ensure refresh tokens are always issued.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(ssh): add native SSH using serviceInstanceId
Adds support for native SSH connections using the new flow:
`ssh <serviceInstanceId>@ssh.railway.com`
Changes:
- Add native SSH module that uses serviceInstanceId for routing
- Add SSH key management (auto-detect local keys, register with Railway)
- Add GraphQL queries for SSH keys and service instances
- Native SSH is used by default when local SSH keys exist
- Add --relay flag to force WebSocket fallback mode
The native SSH flow:
1. Checks for local SSH keys (~/.ssh/id_*.pub)
2. Ensures key is registered with Railway (prompts or auto-registers)
3. Gets serviceInstanceId via GraphQL
4. Runs ssh <serviceInstanceId>@ssh.railway.com
* fix(ssh): use relay mode for command execution
Railway's SSH proxy doesn't forward exec commands through the QUIC
tunnel, so command execution requires relay mode. Native SSH is now
only used for interactive shells where it works correctly.
- Commands use relay mode (railway ssh <command>)
- Interactive shells use native SSH (railway ssh)
- Tmux sessions continue using relay mode
* feat(ssh): show which SSH key is being used
Display the SSH key path when connecting to help users understand
which key is being used for authentication.
* refactor(ssh): clean up unused code and fix clippy warnings
- Remove unused run_native_ssh_with_tmux (exec commands not supported)
- Remove unused find_registered_local_key and ensure_ssh_key_registered
- Fix &PathBuf -> &Path clippy warnings
- Keep tmux sessions using WebSocket relay since SSH exec isn't supported
* refactor(ssh): scan ~/.ssh/ for all .pub files instead of hardcoding
- Dynamically scans ~/.ssh/ directory for all .pub files
- Filters to supported key types (ed25519, ecdsa, rsa, dss)
- Sorts by key type preference (ed25519 first)
* feat(ssh): add key management commands
- Add `railway ssh keys` to list registered SSH keys
- Add `railway ssh keys add` to register a local key
- Add `railway ssh keys remove` to delete a registered key
- Shows which local keys match registered keys
- Supports 2FA for key deletion
* feat(ssh): add SSH key management commands
Add `railway ssh keys` command with subcommands:
- `list` (default): Show registered, GitHub, and local SSH keys
- `add`: Register a local SSH key with Railway
- `remove`: Delete a registered SSH key
- `github`: Import SSH keys from GitHub account
Also removes unused LogFormat::Simple variant.
* fix(ssh): format command hints on separate lines
* refactor(ssh): use direct serviceInstance query instead of listing all
* refactor(ssh): make native SSH opt-in via --native flag
WebSocket relay is now the default SSH method. Users can opt into
native SSH with --native flag when they want direct SSH connections.
* feat(ssh): add command execution support to native SSH mode
Pass command arguments to the ssh binary when using --native flag,
enabling commands like `railway ssh --native echo hello`.
* fix(ssh): improve 2FA handling and key registration UX
- Use validateTwoFactor mutation before delete instead of passing code to sshPublicKeyDelete
- Update GraphQL mutation to remove unused code parameter
- Fix 2FA error detection to be case-insensitive and match "two factor" string
- Replace silent non-TTY key registration with explicit error directing user to register manually
- Simplify run_native_ssh to always inherit stdio regardless of command mode
* fix(ssh): require user to select which SSH key to register
* style: run cargo fmt
* Add GraphQL queries and mutations for MCP server
Add Metrics and Templates queries for service observability and template
search. Add ServiceInstanceUpdate mutation for updating service settings
like build command, replicas, and health check path.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Extract upload logic from up command into controller
Move tarball creation and upload logic into src/controllers/upload.rs so
it can be reused by both the up CLI command and the MCP deploy tool.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Change log callbacks from Fn to FnMut
The MCP log handler collects logs into a Vec, which requires mutating
captured state. Fn closures don't allow this — FnMut does. Existing
callers are unaffected since every Fn is a valid FnMut.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add MCP server for AI-agent access to Railway
Expose Railway operations (deploy, logs, variables, domains, templates,
metrics, etc.) as MCP tools so AI agents can manage infrastructure
programmatically. Uses the rmcp crate for the MCP protocol.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add more fields to update_service MCP tool
Expose cron_schedule, dockerfile_path, healthcheck_timeout,
restart_policy_type, restart_policy_max_retries, pre_deploy_command,
region, railway_config_file, and watch_patterns.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix MCP server: deploy resolution, linked-context precedence, and workspace auto-detect
- Return clear error with available services when deploy gets an unknown service name
- Prefer locally-linked project over token-derived project in MCP context resolution
- Auto-detect team workspace when create_project is called without workspace_id
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix docs_search and split into search + fetch tools
- Replace GitHub API (401 auth failures) with public sitemap for page listing
- Fix content path from src/docs/ to content/ to match actual repo structure
- Use plain HTTP client for external fetches (Railway auth headers caused GitHub 401s)
- Split into docs_search (returns top 5 URLs) and docs_fetch (reads full page)
- Improve scoring: exact path segment matches rank higher than substrings
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Hide confirm field from MCP schema to prevent AI agents from auto-setting it
AI agents were reading "Requires confirm: true" in tool descriptions and
auto-populating confirm on the first call, bypassing the destructive action
safety guard. Fix by adding #[schemars(skip)] to the confirm field on
RemoveServiceParams, RemoveBucketParams, and RemoveVolumeParams so it's
hidden from the JSON schema (serde still deserializes it, defaulting to
false). Updated descriptions to say "Returns a preview first" instead.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Reject create_service when both source_repo and source_image are provided
The backend silently picks repo over image when both are set (via an
if/else chain in the serviceCreate resolver), dropping the image with
no error or warning. This is confusing for MCP agents that may not
realize one source was ignored. Validate early in the MCP layer to
give a clear, actionable error instead of silent precedence.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add help text for mcp command
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Drop vulnerable time crate by disabling unused serde_with features
serde_with was pulling in the time crate (RUSTSEC-2026-0009, DoS via
stack exhaustion in RFC 2822 parsing) through default features. We only
use skip_serializing_none from the macros feature, so disable defaults
and enable only macros. This removes time and several other unused
transitive deps (hex, indexmap v1, deranged, powerfmt, num-conv).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add HTTP log mode to logs command
* Add coverage for HTTP log examples
* Fix HTTP log streaming subscription
* Add full HTTP log field parity and discoverable filter flags
- Fix HTTP log fetch to use anchor-based pagination (beforeLimit/anchorDate)
instead of limit/startDate/endDate which the backend ignores
- Add all 20 HttpLog fields to GraphQL fragments for full JSON output
parity with the dashboard
- Add typed filter flags: --method (enum-validated), --status (supports
200, >=400, 500..599), --path, --request-id (all require --http)
- Expand --filter help to list all filterable fields and operators
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Detect deployment removal during HTTP log streaming
When a deployment is removed while streaming HTTP logs, the CLI now
exits cleanly with a message instead of silently retrying forever.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Mahmoud Abdelwahab <m@mahmoudw.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When trying to use the Railway CLI behind an HTTP proxy that requires
a custom certificate ( such as some VPNs ), the CLI would previously
fail with a certificate error. This adds the `rustls-tls-native-roots`
feature reqwest to trust the native platform's system certificate store to
fix that.
Railway commands that require a websocket connection were also
failing to go through the proxy, because while `reqwest` automatically
sends requests through the proxy `async-tungstenite` would not. This
removes `async-tungstenite` in favor of `reqwest-websocket` which is
simpler and uses `reqwest` to properly send the initial HTTP request
through the proxy before the websocket upgrade.
* feat: add `railway bucket` command for managing Railway buckets
Supports create, delete, list, info, credentials, and rename subcommands with --json output, interactive/non-interactive modes, and automatic commit vs stage detection based on pending environment changes.
Adds a user-friendly CLI command to manage telemetry preferences,
stored persistently in ~/.railway/preferences.json. Previously the
only way to opt out was via environment variables.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add per-command telemetry via cliEventTrack mutation
Sends command name, subcommand, duration, success/error, OS, arch,
CLI version, and CI flag to the Railway API after each invocation.
Silently skipped when unauthenticated, on network failure, or when
the user opts out via DO_NOT_TRACK=1.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Migrate telemetry to typed graphql_client mutation
Pull updated schema with cliEventTrack mutation and replace the raw
JSON POST with typed codegen via graphql_client, matching the rest
of the codebase. Also adds DNS_RECORD_TYPE_TXT from the schema update.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add one-time telemetry notice on first run
Shows a notice to stderr informing users about CLI usage data
collection, with DO_NOT_TRACK=1 opt-out. Persisted to
~/.railway/notices.json so it only displays once. Suppressed
in CI/non-TTY and when telemetry is already disabled.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Support RAILWAY_NO_TELEMETRY env var for opting out of telemetry
Adds RAILWAY_NO_TELEMETRY=1 as a Railway-specific alternative to
DO_NOT_TRACK=1 for disabling CLI telemetry. Updates the notice
text to mention both variables.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix telemetry docs link to /cli/telemetry
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix cargo fmt --all formatting in telemetry notice
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Build logs were using simple output mode which only displayed the raw
message without any formatting. This meant error-level logs from the
build process (like Docker build failures) didn't show the [ERRO]
indicator that helps users quickly identify problems.
Changes:
- Add LogFormat enum with Simple, LevelOnly, and Full modes
- Build logs now use LevelOnly mode to show level indicators without
the noisy buildkit metadata (digest, source, inputs, etc.)
- Deploy logs continue to use Full mode with all attributes
This brings CLI build log output closer to what the dashboard shows.
Co-authored-by: Warp <agent@warp.dev>
* Add `environment config` subcommand
Shows environment configuration with service/volume/variable summary.
Supports --json for machine-readable output and -e to specify environment.
* fmt
* Show service names instead of IDs in human output
* Improve service display with multi-line format
* Show more service config: root, builder, regions, domains
* Remove duplicate environment display
* Remove unused environment_name
* Allow null variables for deletion
Change BTreeMap<String, Variable> to BTreeMap<String, Option<Variable>>
for shared_variables and variables fields, matching existing pattern
for domains/tcp_proxies.
* Add --2fa-code flag for non-interactive 2FA verification
When 2FA is enabled, destructive operations (project delete, environment
delete, volume delete) now support a --2fa-code flag to provide the
verification code non-interactively.
Commands updated:
- `railway delete --2fa-code <CODE>`
- `railway environment delete --2fa-code <CODE>`
- `railway volume delete --2fa-code <CODE>`
This enables fully automated workflows even when 2FA is enabled on the account.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix rustfmt formatting
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>