feat(cli): add flash update command with passive update check#237
feat(cli): add flash update command with passive update check#237
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a self-update flow to the Flash CLI along with a passive, cached update notification that runs in the background on most CLI invocations.
Changes:
- Introduces
flash updatecommand to install the latest (or a specified)runpod-flashversion from PyPI. - Adds a passive background update checker with a 24h on-disk cache and an
atexitnotice on stderr when a newer version is available. - Wires the background checker into the CLI entrypoint while excluding
flash runandflash update.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
src/runpod_flash/cli/commands/update.py |
New flash update command plus helper functions for PyPI lookup and pip install. |
src/runpod_flash/cli/update_checker.py |
New background update-checker module with disk cache + atexit notice. |
src/runpod_flash/cli/main.py |
Registers the new update command and triggers the passive update check for most subcommands. |
tests/unit/cli/commands/test_update.py |
Unit/integration-style tests for flash update behavior. |
tests/unit/cli/test_update_checker.py |
Unit tests for caching, background check behavior, and notice printing. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Re: no-subcommand / --help / --version skipping the check (comment on main.py:97) Intentional. The no-subcommand path only renders the welcome panel and exits — there's no meaningful work for the check to overlap with, and the notice would print immediately after the panel (odd UX). Same for Addressing the other 4 items in code:
|
deanq
left a comment
There was a problem hiding this comment.
All 5 review items addressed in 3e4af42:
-
main.py:97 (no-subcommand skip) — Intentional by design. No-args shows the welcome panel and exits immediately;
--versionraisestyper.Exit()before the check. Neither has meaningful work to overlap with. -
update.py:58 (JSON/key errors) — Fixed.
_fetch_pypi_metadatanow catchesJSONDecodeError,UnicodeDecodeError, andKeyError, re-raising asRuntimeErrorwith user-friendly messages. -
update_checker.py:100 (missing latest_version in fresh cache) — Fixed. Missing or empty
latest_versionin a fresh cache is now treated as stale, falling through to a PyPI fetch. -
update_checker.py:143 (non-TTY skip) — Fixed. Added
_is_interactive()helper; skips when neither stdout nor stderr is a TTY. -
update_checker.py:147 (idempotency) — Fixed. Added
_startedflag with_start_locksostart_background_check()only runs once per process.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
runpod-Henrik
left a comment
There was a problem hiding this comment.
Code Review — PR #237: flash update + background update checker
Clean implementation. Three issues found:
Bug 1 (HIGH): uv pip install fails without active venv
update.py — _build_install_command()
if shutil.which("uv"):
return ["uv", "pip", "install", package_spec, "--quiet"]uv pip install requires an active venv (VIRTUAL_ENV set or .venv discoverable). If flash is installed system-wide and uv is on PATH, this fails with exit code 2. The pip fallback works correctly because it uses sys.executable.
Fix: Pass --python sys.executable:
return ["uv", "pip", "install", "--python", sys.executable, package_spec, "--quiet"]Bug 2 (MEDIUM): _started = True set before env-var guards — permanently disables update checks
update_checker.py — start_background_check()
with _start_lock:
if _started:
return
_started = True # <-- set HERE
if os.getenv("FLASH_NO_UPDATE_CHECK"):
return # _started=True, no thread started, no atexitIf called with CI=1 or FLASH_NO_UPDATE_CHECK=1, _started becomes True but no thread/atexit is registered. If the env var is later cleared (test suites), subsequent calls are silently no-oped. Move _started = True to after the guards, just before thread.start().
Bug 3 (LOW): _parse_version crashes on PEP 440 pre-release versions
update.py — _parse_version()
return tuple(int(part) for part in version.split("."))PyPI info.version can be 1.5.0rc1 or 1.5.0a1. int("0rc1") raises ValueError. In update_command the except ValueError: pass handles it, but in update_checker._run_check() the bare except Exception: pass silently skips the entire comparison — no update notice is printed even if a newer stable version exists.
Fix: Use packaging.version.Version (already a transitive dep):
from packaging.version import Version
def _parse_version(version: str) -> Version:
return Version(version)- Add _compare_versions() with zero-padding for differing tuple lengths - Fix _started flag ordering in start_background_check (set after skip checks) - Replace Linux-specific /proc test path with monkeypatch - Fix missing newline in _print_update_notice output - Add TestCompareVersions with 8 test cases covering edge cases
runpod-Henrik
left a comment
There was a problem hiding this comment.
Follow-up review by Henrik's AI-Powered Bug Finder
Checked ed1c2e3 against our three original items:
Bug 2 (_started flag): FIXED
_started = True now set after all skip checks, inside the lock. Thread start + atexit.register also moved inside the lock — cleaner than before. Good.
Bug 3 (_parse_version): MITIGATED
_compare_versions() with zero-padding handles the tuple-length edge case ((2, 0) == (2, 0, 0)). 8 solid test cases. However, _parse_version("1.5.0rc1") still raises ValueError — the except Exception: pass in _run_check() swallows it, so users on a pre-release version get no update notice for the next stable release. Low priority, but worth a note.
Bug 1 (uv pip install without venv): STILL OPEN — follow-up item
_build_install_command() still runs bare uv pip install which requires an active venv. If flash is installed system-wide and uv is on PATH, this fails with exit code 2. Not blocking since the error message is clear and flash update is non-critical. Fix for follow-up: ["uv", "pip", "install", "--python", sys.executable, ...]
Bonus fixes
/proctest path → monkeypatch (cross-platform)- Missing newline in update notice output
- Both well done.
Approving — Bug 2 fix was the important one and it's solid. Bug 1 is a minor edge case suitable for a follow-up PR.
🤖 Reviewed by Henrik's AI-Powered Bug Finder
Adds `flash update` to upgrade to latest PyPI version and `flash update --version X.Y.Z` to pin a specific version. Uses stdlib only (urllib.request + subprocess) with no new deps.
Spawn a daemon thread on CLI invocation that checks PyPI (at most once per 24h, cached to ~/.config/runpod/update_check.json). An atexit handler prints a one-line notice to stderr if a newer version exists. The thread never blocks the command -- slow networks are silently skipped. Excluded from check: flash run (long-running), flash update (already managing versions), no-subcommand (help panel). Opt-out via FLASH_NO_UPDATE_CHECK or CI env vars.
_run_pip_install replaced with _run_install that prefers `uv pip install` when uv is on PATH, falling back to `python -m pip install` otherwise. Fixes "No module named pip" error in uv-managed environments.
- _fetch_pypi_metadata: catch JSONDecodeError, UnicodeDecodeError, and KeyError from malformed PyPI responses, re-raise as RuntimeError - _run_check: treat missing/empty latest_version in fresh cache as stale so a corrupted cache does not suppress checks for 24h - start_background_check: skip when neither stdout nor stderr is a TTY to avoid threads and notices in piped output - start_background_check: add idempotency guard to prevent duplicate threads and atexit handlers on repeated calls
- Add _compare_versions() with zero-padding for differing tuple lengths - Fix _started flag ordering in start_background_check (set after skip checks) - Replace Linux-specific /proc test path with monkeypatch - Fix missing newline in _print_update_notice output - Add TestCompareVersions with 8 test cases covering edge cases
ed1c2e3 to
a4ce7d1
Compare
Summary
flash updatecommand for self-updating runpod-flash to latest or a specific version via PyPIuv pip installwhen uv is on PATH, fall back topython -m pip installotherwiseflash run(long-running),flash update(already managing versions), and CI/headless environmentsTest plan
flash update-- verify it checks PyPI and reports current versionflash update --version 1.4.0-- verify it installs specific version (uses uv)flash build --help-- verify passive check triggers (inspect~/.config/runpod/update_check.json)flash run --help-- verify passive check does NOT triggerFLASH_NO_UPDATE_CHECK=1and run any command -- verify check is skipped