Skip to content

Add railway changes commands and route bucket/TCP proxy edits through staged changes#1000

Open
m-abdelwahab wants to merge 7 commits into
masterfrom
staged-changes
Open

Add railway changes commands and route bucket/TCP proxy edits through staged changes#1000
m-abdelwahab wants to merge 7 commits into
masterfrom
staged-changes

Conversation

@m-abdelwahab

@m-abdelwahab m-abdelwahab commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

This PR gives the CLI first-class support for Railway's staged-changes workflow: a new railway changes command family to review, deploy, and discard the environment's staged patch, and parity with how the dashboard handles the operations that stage into it.

Command surface

railway changes                                  # help (aliases: railway staged-changes, railway change)

railway changes status                           # show staged changes
    [--path <dot-path>]...                       #   filter: prefix matches subtree, * matches one segment, \. escapes dots
    [--show-values]                              #   show variable values in plaintext instead of masked
    [--detailed-exit-code]                       #   exit 2 when changes are pending, 0 when none
    [--json]

railway changes deploy                           # deploy all staged changes (alias: apply)
    [-y | --yes]                                 #   skip confirmation
    [-m | --message <message>]                   #   commit message
    [--skip-deploys]                             #   commit without triggering deploys
    [--2fa-code <code>]                          #   for deletions / volume region moves when 2FA is enabled
    [--json]

railway changes discard                          # discard staged changes (interactive multi-select in a terminal)
    [--all]                                      #   discard everything
    [--path <dot-path>]...                       #   discard selected paths (subtree prefixes work)
    [-y | --yes] [--json]

Global on all subcommands: -p/--project, -e/--environment (default to the linked project/environment)

What the experience looks like

Changes staged from anywhere — the dashboard, environment edit --stage, bucket/TCP-proxy commands, MCP agents — accumulate in one patch per environment. Every staging command tells you where to go next:

$ railway environment edit --stage --service-config web deploy.startCommand "npm run serve"
Changes staged for production
Staged 1 change — review: railway changes status · deploy: railway changes deploy

$ railway bucket create uploads --region ams
Created bucket uploads (ams (EU West, Amsterdam)) and staged it for production
Staged 1 change — review: railway changes status · deploy: railway changes deploy

$ railway tcp-proxy create --port 5432 --service postgres
Staged TCP proxy for service postgres on application port 5432 in production
Staged 1 change — review: railway changes status · deploy: railway changes deploy

Review renders the patch the way the dashboard's changes panel does, grouped by resource:

$ railway changes status
4 changes to apply

Environment production
Patch f10a2cf3-eef8-487b-ab35-36c2d6db30f6 (STAGED)

web will be updated
1 Setting
  Change           Current Value  New Value
  + Start Command                 npm run serve

postgres will be updated
1 Setting
  Change                          Current Value  New Value
  + Application Port (TCP Proxy)                 5432

uploads will be updated
2 Settings
  Change                         Current Value  New Value
  + Is Created (Bucket Created)                 CREATED
  + Region                                       ams

Deploy is all-or-nothing, so discard is the pruning tool — drop what you don't want, then ship the rest as one commit:

$ railway changes discard --path 'services.web' --yes
Discarded 1 staged change for production.

$ railway changes deploy --yes --message "Add uploads bucket and postgres proxy"
Deployed staged changes for production.

Destructive deploys warn first (on stderr), and 2FA is enforced when enabled:

$ railway bucket delete --bucket uploads --yes
Staged deletion of bucket uploads for production
Staged 1 change — review: railway changes status · deploy: railway changes deploy

$ railway changes deploy --yes
Warning: This deploy deletes a resource:
  - uploads (bucket)
Deployed staged changes for production.

For automation, everything speaks JSON and exit codes:

$ railway changes status --detailed-exit-code > /dev/null; echo $?
2   # staged changes pending (0 = none)

$ railway changes status --json | jq '.changes[].path'
"services.06270578-7274-4e98-8094-1a8974483914.deploy.startCommand"
"buckets.1e19c892-34d2-4423-a130-79111b3c5ba1.isCreated"

New command: railway changes (aliases: staged-changes, change)

  • status — staged changes with dashboard-style labels, raw dot paths, current/new values. Supports --path filtering (prefix matches a subtree, * matches one segment, literal dots escape as \.), --show-values (variables are masked by default), --json, and --detailed-exit-code (exit 2 when changes are pending).
  • deploy (alias apply) — commits the staged patch and polls apply progress. --yes, --message, --skip-deploys, --2fa-code. Warns on stderr before deploys that delete resources; 2FA is enforced for deletions and volume region moves. An accepted-but-still-applying commit reports as pending, not as a failure.
  • discard--all, repeated --path (subtree prefixes work), or an interactive multi-select. Partial discard re-stages the remaining patch as raw JSON so fields staged by other clients are never silently dropped.

All subcommands are automation-safe: --json everywhere, and prompts are refused in non-interactive contexts with instructions instead.

MCP tools

staged_changes_status, staged_changes_deploy, staged_changes_discard mirror the CLI. Variable values are masked by default in MCP output (opt in via show_values); CLI --json returns full values.

Bucket and TCP proxy edits now go through staged changes ⚠️ behavior change

The dashboard routes bucket create/delete and TCP proxy creation through the staged-changes workflow; the CLI committed them immediately. The CLI's existing "stage only when the environment has pending changes" heuristic keyed off unmergedChangesCount, which is no longer populated — so the staging branch was unreachable and the CLI always committed. Rather than patch the heuristic, these operations now match the dashboard:

  • bucket create — still creates the bucket immediately at the project level, but its appearance in the environment is now always staged.
  • bucket delete — always stages the deletion.
  • tcp-proxy create — always stages the networking change. Deleting a live proxy still takes effect immediately, same as the dashboard.
  • scale stays commit-immediately, as its help documents.

Migration: scripts that ran railway bucket create/delete or railway tcp-proxy create and expected immediate effect must now follow with railway changes deploy --yes. Every staging command prints a notice saying exactly that.

Staged-resource deletion

tcp-proxy delete <port> and bucket delete detect targets that exist only as staged (not yet deployed) creations and remove the pending change instead — same outcome as discarding the change in the dashboard. The bucket itself remains at the project level, consistent with dashboard behavior. No 2FA for these, since nothing deployed is destroyed.

environment edit fixes (staging entry path)

  • CLI flags are now checked before piped stdin; previously a non-TTY stdin silently discarded --service-config flags and exited 0 doing nothing. "No changes to apply" also prints non-interactively now.
  • Stdin JSON keyed by service name is resolved to the service ID before staging (previously the patch was keyed by name, producing duplicate groups in review and a patch other clients don't recognize). Unknown services error with the available list; configuring one service by both name and ID is rejected.

Supporting changes

  • GraphQL: EnvironmentPatchCommitStaged and raw-config EnvironmentStageChangesRaw mutations; the EnvironmentStagedChanges query gains createdAt/updatedAt/lastAppliedError and decryptVariables.
  • errors::ExitCode sentinel so --detailed-exit-code can exit non-zero without printing an error while still running end-of-process work.
  • Staging notices across bucket/tcp-proxy/outbound-ipv6/environment-edit/config flows now point to railway changes instead of "use railway environment edit to commit".

Testing

  • Unit: CLI parsing for all subcommands/conflicts, path matching/escaping, sealed-value masking, 2FA detection, discard subtree-removal safety (417 tests pass).
  • Live E2E against production Railway: staged → reviewed → filtered → partially discarded → deployed (variables verified applied); bucket + TCP proxy staged and provisioned via changes deploy; staged deletions warn and delete; staged-resource removal nets the patch to empty; all three MCP tools exercised over stdio.

🤖 Generated with Claude Code

New command family (aliases: staged-changes, change) to review, deploy,
and discard an environment's staged patch:

- status: dashboard-style labels, --path filtering (subtree prefixes,
  * wildcards, escaped dots), --show-values, --json, --detailed-exit-code
- deploy (alias apply): commits via environmentPatchCommitStaged, polls
  apply progress, warns on resource deletions, enforces 2FA for
  deletions and volume region moves
- discard: --all, repeated --path, or interactive multi-select; partial
  discard re-stages the remaining patch as raw JSON so fields staged by
  other clients are preserved

Adds an ExitCode sentinel error so --detailed-exit-code can exit 2
without printing an error while still running end-of-process work.
The EnvironmentStagedChanges query gains createdAt/updatedAt/
lastAppliedError and decryptVariables.
staged_changes_status, staged_changes_deploy, and staged_changes_discard
mirror the CLI commands. Variable values are masked by default in tool
output (opt in via show_values); deletions requiring two-factor
verification are refused over token auth.

Also updates the create_bucket, remove_bucket, and create_tcp_proxy tool
descriptions ahead of those operations moving to the staged workflow.
bucket create/delete and tcp-proxy create previously decided between
committing immediately and staging based on unmergedChangesCount, which
is no longer populated — so they always committed. They now always stage
into the environment patch, matching the dashboard, and take effect on
railway changes deploy. Deleting a live TCP proxy still applies
immediately, also matching the dashboard.

bucket delete and tcp-proxy delete additionally detect targets that only
exist as staged creations and remove the pending change instead: proxies
by staging null over the port entry, buckets by dropping the pending
buckets.<id> subtree from the patch (the bucket itself remains at the
project level).

Applies to the equivalent MCP tools; scale keeps committing immediately
as its help documents. Spinners in tcp-proxy create/delete are now only
created when stdout is a terminal so success messages print in scripts.
Two fixes on the staging entry path:

- CLI flags (--service-config) are now checked before piped stdin.
  Previously a non-terminal stdin was read first, so scripts passing
  flags silently staged nothing and exited 0. The 'No changes to apply'
  message also prints in non-interactive mode now.
- Stdin JSON keyed by service name is resolved to the service ID before
  staging. Previously the patch was staged under the name key, which
  rendered as a duplicate resource group and produced a patch other
  clients don't recognize. Unknown services error with the available
  list; configuring one service by both name and ID is rejected.

Staging output now points at railway changes for review and deploy.
Flows that stage changes (outbound IPv6, config apply) printed
'commit with railway environment edit' style hints. They now emit the
shared staged-changes notice pointing at railway changes status/deploy.
@m-abdelwahab m-abdelwahab added the release/minor Author minor release label Jul 7, 2026
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