* feat: add auto-update mechanism for CLI
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: prevent auto-update from blocking CLI exit and fix permission/lock issues
- Don't await download_and_stage on exit; spawn it as fire-and-forget so
commands like --help return instantly even when an update is available
- Add writability check before attempting self-update download, skipping
the download entirely for root-owned paths like /usr/local/bin
- Replace acquire-then-drop file lock with PID file guard for package
manager updates to prevent duplicate concurrent spawns
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: cap update task with timeout and add staleness to PID guard
- Move download_and_stage back into the awaited task so the tokio
runtime doesn't cancel it on exit, but cap handle_update_task with
a 5-second timeout so short commands like --help aren't blocked
- Write "PID TIMESTAMP" to the lock file and treat entries older than
10 minutes as stale, fixing the permanent block on Windows where
is_pid_alive always returned true
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: cache version check timestamp, separate lock files, and clean up control flow
- Write last_update_check even when CLI is up-to-date, preventing a
GitHub API call on every invocation
- Use separate file paths for self-update flock (update.lock) and
package-manager PID guard (package-update.pid)
- Return Ok(None) instead of bail! for "already checked today" since
it's normal control flow, not an error
- Make Preferences::write atomic via temp file + rename
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: improve robustness, Windows support, and rollback UX for auto-update
- Bypass same-day gate for known-pending versions so timed-out downloads
retry on the next invocation
- Skip spawning update task when running `autoupdate` subcommand to avoid
racing with preference changes
- Use nix crate for Unix PID liveness check; add Windows implementation
via winapi instead of conservative fallback
- Detach child update process from console Ctrl+C on Windows
- Add Windows CREATE_NEW_PROCESS_GROUP flag to package manager spawning
- Use scopeguard for write-probe cleanup in install method detection
- Warn when checksums.txt is missing rather than silently skipping
- Improve rollback: back up current binary first, support multi-candidate
selection via inquire prompt
- Remove freebsd target triple (no release asset published)
- Use nanosecond-precision tmp file names to reduce collision risk
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* Updates
* fix: prevent autoupdate disable from applying staged update, handle non-writable shell installs, and preserve retry signal
- Skip try_apply_staged() for both `upgrade` and `autoupdate` subcommands
so `railway autoupdate disable` doesn't swap the binary before disabling
- Add can_write_binary() check in `railway upgrade` for shell installs in
non-writable locations (e.g. /usr/local/bin with sudo), guiding users to
use sudo or reinstall instead of failing with a permission error
- Move clear_latest() from eager call in main to post-success in the
background task, so a timed-out download preserves the retry signal for
the next invocation instead of losing it behind the same-day gate
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: clear stale version cache for notification-only installs, lock download_and_stage, and add FreeBSD target
- For installs that can't self-update or auto-run a package manager
(Homebrew, Cargo, Unknown, non-writable Shell), clear latest_version
after the notification so the next day's check_update() can discover
newer releases instead of freezing on the first cached version
- Wrap download_and_stage() with an exclusive file lock (update.lock)
using double-checked locking to prevent concurrent CLI processes from
racing on the staged-update directory
- Add FreeBSD x86_64 to detect_target_triple() to match install.sh
support, preventing shell-installed FreeBSD users from hitting an
"Unsupported platform" error on upgrade
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: only clear version cache when update actually starts, Windows-safe rename, custom bin dir detection, and rollback target tracking
- download_and_stage returns Result<bool> so callers distinguish "lock
held" (no work done) from a real staging; failed package-manager
spawns no longer fall through to the notification-only branch that
clears the cache
- Add rename_replacing() helper that removes the destination on Windows
before renaming, fixing silent write failures for preferences.json
and update.json after the first successful write
- Shell install detection now falls back to any parent directory named
"bin", covering custom --bin-dir installs (~/bin, /opt/bin, etc.)
- Backup filenames include the target triple; rollback filters
candidates by current architecture to prevent cross-arch restoration
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: Windows version cache write, rollback guards, CI status reporting, preference persistence, and FreeBSD self-update
- Use rename_replacing() in UpdateCheck::write() so Windows cache clears work
- Add interact_or! and can_write_binary() guards to rollback path
- Report CI-disabled state in autoupdate status
- Create ~/.railway dir in Preferences::write() for clean HOME directories
- Remove FreeBSD from self-update targets until release pipeline publishes assets
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: tighten install detection, ensure ~/.railway exists, gate FreeBSD self-update, and preserve notifications when auto-updates disabled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: preserve update retry signals, propagate preference write errors, and use atomic Windows rename
- Notification-only installs (Homebrew, Cargo, Unknown) now preserve
latest_version in the cache so the "new version available" notice
actually shows on the next invocation instead of being cleared before
the user sees it.
- Detached package-manager updates no longer clear the retry signal on
spawn — the cached version persists until the user is actually on the
new version.
- Failed staged-update applies preserve the staged payload for retry
instead of deleting it; the 7-day staleness TTL handles permanent
failures.
- `railway upgrade` now clears the version cache after a successful
update so the next invocation doesn't redundantly re-download.
- Preferences::write() returns Result so `railway autoupdate disable`
and `railway telemetry disable` report failures instead of silently
succeeding.
- Standardize timestamp_nanos_opt() on unwrap_or_default() everywhere.
- Windows rename_replacing uses MoveFileExW(MOVEFILE_REPLACE_EXISTING)
for a single-syscall atomic replace instead of remove+rename.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: deduplicate update-check persistence with persist_latest helper
Consolidate repeated UpdateCheck write blocks in spawn_update_task into
a single persist_latest() method, and skip redundant writes when
check_update() already persisted the timestamp.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: sort backups by version instead of mtime to fix Windows CI
list_backups relied on filesystem modification times, which required
write access to set in tests and is fragile across platforms. Sort by
the semver version embedded in the backup filename instead.
Also removes low-value tests that only assert trivial string formatting
or compiler-guaranteed match exhaustiveness.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: detach shell downloads, expire stale versions, reduce exit delay, and clarify update message
- Spawn background downloads in a detached child process so they survive
beyond the parent's exit timeout instead of restarting from zero
- Add download_failures counter to version.json; after 3 consecutive
failures, clear the cached version to force a fresh API re-check
(fixes infinite retry loop on yanked/stale releases)
- Reduce exit timeout from 5s to 2s since it now only gates the fast
API version check, not the download
- Append "(active on next run)" to the auto-update message since the
current process still executes the old binary
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: extract env var constant, name failure threshold, skip redundant spawns
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: stale comment, probe cleanup, non-TTY gating, Windows .old.exe, rollback prune timing, and check_update timeout
- Correct "5 s" → "2 s" in download_and_stage comment to match handle_update_task
- Simplify can_write_binary probe to unconditional create-then-remove
- Skip spawn_update_task in non-TTY when auto-update is disabled
- Clean up leftover .old.exe on Windows at the start of try_apply_staged
- Defer backup pruning until after rollback succeeds so candidates aren't removed before the picker
- Add 30 s timeout to check_update reqwest client to prevent indefinite hangs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: send update notifications to stderr to avoid corrupting command output
println! in the auto-update and version-banner paths wrote to stdout,
which breaks JSON parsing when the CLI is invoked programmatically
(e.g. by Claude Code piping `railway status --json`).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: clear stale update cache when CLI catches up to cached version
Once the installed binary reaches or surpasses the cached latest_version,
clear the cache so spawn_update_task falls through to a fresh check_update().
This prevents repeated package-manager spawns on every invocation after an
update lands and allows discovery of newer releases on manual install paths.
Also null out stdin on the detached package-manager update process to match
spawn_background_download and fully detach from the terminal.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: always attempt fresh API check so cached version does not become permanent
Previously spawn_update_task skipped check_update() entirely when a
cached version existed, and re-persisted the timestamp on every
invocation. This prevented the same-day gate from ever expiring, so the
CLI would advertise a stale cached version forever and never discover
newer releases.
Now we always call check_update() first (gated to once per UTC day),
falling back to the cached version only within the same day for download
retries.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* style: apply cargo fmt and lint-fix
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: keep cached pending version until staged update is actually applied
background_stage_update() was clearing the cached latest_version
immediately after staging succeeded, but the binary isn't replaced until
try_apply_staged() runs on a later invocation. If that apply step fails,
the user loses both the upgrade banner and future download retries until
the 7-day stale TTL expires. The cache is already cleared on successful
apply in try_apply_staged(), so the staging path should leave it alone.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* style: apply cargo fmt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: hold update lock across apply in interactive upgrade and rollback
self_update_interactive() dropped the update lock after staging but
before applying, letting a concurrent try_apply_staged() race the binary
replacement. Now the lock is held across both staging and apply.
rollback() was mutating the binary and cleaning staged state without
acquiring update.lock at all, which could interleave with a background
auto-updater. Now it acquires the same lock before replacing the binary.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: correct autoupdate status for non-writable shell installs and close stage-apply race
update_strategy() now checks can_self_update() and can_write_binary()
so `autoupdate status` no longer claims "Background download + auto-swap"
when the binary directory is not writable or the platform is unsupported.
self_update_interactive() now holds a single lock across both
download_and_stage_inner() and apply_staged_update(), eliminating the
window where a concurrent try_apply_staged() could consume the staged
binary between staging and applying.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: drop lock file handle instead of deleting to prevent concurrent lock race
Removing the lock file while the handle is still held unlinks the inode
on Unix, allowing a concurrent process to create a new file at the same
path and acquire its own "exclusive" lock. Replacing remove_file with
drop releases the lock via the OS and leaves the file as an inert
sentinel.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: extract spawn_detached helper and simplify update task logic
- Extract shared spawn_detached() into util/mod.rs, deduplicating the
detached-process setup from spawn_background_download and
spawn_package_manager_update
- Remove redundant staged-update check in spawn_background_download
(child process already checks in download_and_stage)
- Consolidate scattered from_cache/persist_latest branches into a
single needs_persist flag
- Cache is_auto_update_disabled() result in main() instead of calling
it twice
- Use persist_latest(None) in check_update's up-to-date branch
- Trim narrating comments
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: skip rolled-back version in auto-update instead of pausing entirely
After rollback, record the rolled-back-from version as skipped in
version.json. Auto-update skips only that version and resumes normally
once a newer release is published. This avoids silent staleness from
a full pause while still respecting the user's rollback intent.
- Add skipped_version field to UpdateCheck (serde-default for compat)
- Record skip in rollback(), guard try_apply_staged() against the race
where a pre-rollback detached download stages the skipped version
- Clear skip on successful update (clear_after_update) and autoupdate enable
- Suppress "new version available" notification for skipped version
- Show skipped version in autoupdate status
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: close concurrency races in rollback, package-manager guard, and failure reset
- Acquire update.lock before backup/picker in rollback() so a concurrent
try_apply_staged() cannot swap the binary during interactive selection
- Serialize the PID-check-spawn-write sequence in spawn_package_manager_update()
with a file lock to prevent two CLI invocations from both launching updaters
- Clear last_update_check alongside latest_version after 3 download failures
so the same-day gate does not suppress the fresh API re-check
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add checksum generation, respect disable preference, and lock staged cleanup
- Add generate-checksums job to release workflow that produces checksums.txt
with SHA-256 hashes for all release assets, making checksum verification
functional instead of always falling back to the skip path
- Gate update polling and "New version available" banner on auto_update_enabled
so disabled users no longer hit the GitHub API or see nagging banners
- Acquire update.lock in autoupdate disable before cleaning staged updates so
an in-flight background download cannot re-stage after cleanup returns
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* revert: remove checksums.txt workflow change (pre-existing, out of scope)
The missing checksums.txt in the release pipeline predates this PR.
Reverting the workflow change to keep this PR focused on auto-update fixes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Revert "revert: remove checksums.txt workflow change (pre-existing, out of scope)"
This reverts commit
|
||
|---|---|---|
| .cargo | ||
| .github | ||
| bin | ||
| npm-install | ||
| src | ||
| .dockerignore | ||
| .gitattributes | ||
| .gitignore | ||
| build.rs | ||
| Cargo.lock | ||
| Cargo.toml | ||
| CLAUDE.md | ||
| CONTRIBUTING.md | ||
| Dockerfile | ||
| flake.lock | ||
| flake.nix | ||
| install.sh | ||
| LICENSE | ||
| package.json | ||
| pnpm-lock.yaml | ||
| README.md | ||
| release.toml | ||
| shell.nix | ||
| v2.sh | ||
Railway CLI
Overview
This is the command line interface for Railway. Use it to connect your code to Railway's infrastructure without needing to worry about environment variables or configuration.
The Railway command line interface (CLI) connects your code to your Railway project from the command line.
The Railway CLI allows you to:
- Create new Railway projects from the terminal
- Link to an existing Railway project
- Pull down environment variables for your project locally to run
- Create services and databases right from the comfort of your fingertips
And more.
Documentation
Quick start
Follow the CLI guide to install the CLI and run your first command.
Authentication
For non-interactive authentication details, see the CLI guide.
Installation
Package managers
Cargo
cargo install railwayapp --locked
Homebrew
brew install railway
NPM
npm install -g @railway/cli
Bash
# Install
bash <(curl -fsSL cli.new)
# Uninstall
bash <(curl -fsSL cli.new) -r
Scoop
scoop install railway
Arch Linux AUR
Install with Paru
paru -S railwayapp-cli
Install with Yay
yay -S railwayapp-cli
Docker
Install from the command line
docker pull ghcr.io/railwayapp/cli:latest
Use in GitHub Actions
For GitHub Actions setup, see the blog post at blog.railway.com/p/github-actions.
Use in GitLab CI/CD
For GitLab CI/CD setup, see the blog post at blog.railway.com/p/gitlab-ci-cd.
Contributing
See CONTRIBUTING.md for information on setting up this repository locally.
Feedback
We would love to hear your feedback or suggestions. The best way to reach us is on Central Station.
We also welcome pull requests into this repository. See CONTRIBUTING.md for information on setting up this repository locally.