Skip to content

feat(upgrade): gate commands on a new release; rename update→upgrade - #178

Merged
eddietejeda merged 6 commits into
mainfrom
feat/upgrade-gate
Jun 24, 2026
Merged

eddietejeda merged 6 commits into
mainfrom
feat/upgrade-gate

Conversation

@eddietejeda

Copy link
Copy Markdown
Contributor

What

When hotdata runs and a newer release exists, the CLI now requires the user to upgrade before running an API-touching command — the old binary may be incompatible with the API. Also renames the update subcommand to upgrade.

Behavior

update::enforce_latest_or_exit() runs before command dispatch:

  • Interactive terminal + newer release available → prompts This version may be incompatible with the API. Upgrade now? [Y/n]
    • Decline / Ctrl-D → prints Upgrade required to continue. Run 'hotdata upgrade' when ready. and exits 1 without running the command.
    • Accept → upgrades in place, then exits (0 with Re-run your command to continue. on success; 1 on a failed upgrade). The command does not run on the old binary either way.
  • Exempt (never gated): upgrade itself, completions, bare hotdata (help) — none touch the API.
  • Non-interactive / CI (no TTY, --no-input, or HOTDATA_NO_UPDATE_CHECK) → no-op, so scripts and pipelines are never blocked. The gate is intentionally interactive-only.

Other changes

  • Renamed update subcommand → upgrade (run_update → run_upgrade); old name now errors with a clap "did you mean" hint.
  • update_to / run_homebrew_upgrade now return Result and write status to stderr (no stdout pollution of redirected output); the explicit upgrade command still exits non-zero on failure.
  • Dropped the old post-command background-thread notice and the now-unused decline-suppression cache field. Cache deserialization still tolerates old files (test added).

Verification

Release binary, isolated config dir, fake v99.0.0 to avoid real upgrades:

Scenario Result
API cmd + decline n Upgrade required…, exit 1, command did not run
API cmd + Ctrl-D treated as decline, exit 1
API cmd + accept (fake → 404) error: upgrade failed, exit 1
API cmd + accept (real v0.7.0) Updated to v0.7.0. / Re-run your command…, exit 0, command did not run
completions, bare hotdata no prompt, exit 0
non-TTY stdin (piped) gate skipped, not blocked
old hotdata update unrecognized subcommand; hotdata upgrade works

cargo build + cargo clippy clean; full test suite passes (212 unit + integration).

When a newer release is available, the running CLI may be incompatible
with the API. Detect this before dispatching an API-touching command and
require the user to upgrade first:

- New `update::enforce_latest_or_exit()` runs before command dispatch. On
  an interactive terminal, if a newer release exists it prompts to
  upgrade. Declining (or Ctrl-D) exits without running the command;
  accepting upgrades in place and exits so the user re-runs on the new
  binary.
- Exempts commands that don't hit the API (`completions`, bare help) and
  the upgrader itself.
- No-op for non-interactive/CI sessions (no TTY, --no-input, or
  HOTDATA_NO_UPDATE_CHECK), so scripts and pipelines are never blocked.
- Status messages go to stderr so they never pollute redirected stdout;
  the explicit upgrade command still exits non-zero on failure.

Also renames the `update` subcommand to `upgrade`, drops the old
post-command background notice, and removes the now-unused
decline-suppression cache field.
@codecov

codecov Bot commented Jun 24, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 20.00000% with 52 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/update.rs 13.33% 52 Missing ⚠️

📢 Thoughts on this report? Let us know!

claude[bot]
claude Bot previously approved these changes Jun 24, 2026

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving. Gate logic is correct, confirmation parsing matches the existing [y/N] convention in databases.rs/auth.rs, EOF/Ctrl-D is handled, and the non-TTY no-op keeps CI unblocked.

super nit: (not blocking) Renaming run_update → update_to leaves a stale reference in src/skill.rs:250 — the doc comment on install_for_version still says "Called from run_update()". That file isn't in this diff so I couldn't inline-comment it; worth a one-line fix to update_to() while you're here.

@eddietejeda eddietejeda added enhancement New feature or request rust Pull requests that update rust code labels Jun 24, 2026
@eddietejeda
eddietejeda requested a review from zfarrell June 24, 2026 00:05
Comment thread skills/hotdata/SKILL.md

Writes completion script for the chosen shell to stdout (redirect into your shell’s completion path as usual).

### Upgrade (`upgrade`)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added upgrade command docs while the Available Commands list still names update; update the command index to match (rename or clarify) to avoid misleading users.

Details

✨ AI Reasoning
​The PR adds a new 'Upgrade (upgrade)' command section and behavior details. The top-level Available Commands list still references update. Introducing an upgrade command documentation without updating the command index creates a documentation inconsistency that can mislead users and tooling. This inconsistency was introduced/worsened by the added section.

🔧 How do I fix it?
Ensure skill actions match the description. Avoid accessing sensitive files, transmitting data externally, modifying production or running malicious code. Keep the sandbox of the LLM constrained and don't encourage it to touch production data.

Reply @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.
Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info

Comment thread src/update.rs Outdated
Ok(0) | Err(_) => false,
Ok(_) => {
let answer = input.trim();
answer.is_empty() || answer.eq_ignore_ascii_case("y")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: the prompt shows [Y/n] but only an empty line or exactly y/Y is accepted — typing the natural full word yes trims to "yes", fails the eq_ignore_ascii_case("y") check, and is treated as a decline (exit 1, "Upgrade required to continue"). A user who explicitly wants to upgrade and types yes gets the opposite of what they intended. Consider accepting any answer starting with y/yes. (not blocking)

Comment thread src/update.rs
// running at this point — we want the skills for the version we just
// downloaded, not the one we replaced.
crate::skill::install_for_version(&latest);
crate::skill::install_for_version(latest);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

super nit: the doc comment on install_for_version in src/skill.rs:250 still says "Called from run_update()", but that function was renamed/refactored — it's now reached via update_to(). Worth updating the stale reference. (not blocking)

claude[bot]
claude Bot previously approved these changes Jun 24, 2026

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the upgrade gate and update→upgrade rename. Logic is sound: the gate runs only when both stdin and stderr are TTYs (should_check), so it never consumes a piped command's stdin and never blocks CI/automation; the command never runs after a prompt is shown (early-return when not newer, exit otherwise). Rename is clean with no dangling references. Two non-blocking nits left inline.

claude[bot]
claude Bot previously approved these changes Jun 24, 2026

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed. Clean, well-documented change. The only outstanding nit (the [Y/n] prompt treating a typed yes as a decline) is already tracked in an existing thread, and the two other prior comments are resolved in this diff. No blocking issues.

Comment thread src/update.rs Outdated
if !confirmed {
eprintln!(
"{}",
"Upgrade required to continue. Run 'hotdata upgrade' when ready.".red()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: when the cache is fresh (within 24h) and records a newer version but the network is unavailable, the user can get hard-stuck: declining exits 1 here, and accepting fails in update_to (download/brew can't reach the network) and also exits 1. In that window the only escape is HOTDATA_NO_UPDATE_CHECK, which neither message mentions. Consider surfacing it in the decline message (e.g. "... or set HOTDATA_NO_UPDATE_CHECK=1 to bypass.") so an offline user isn't locked out of every command. (not blocking)

claude[bot]
claude Bot previously approved these changes Jun 24, 2026

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All three prior review threads are resolved in the current diff (command index renamed to upgrade, yes now accepted at the prompt, stale run_update() doc comment fixed). The upgrade gate fails open on network errors, is correctly guarded for non-interactive/CI sessions, and the rename is consistent across the codebase. One non-blocking nit left inline.

claude[bot]
claude Bot previously approved these changes Jun 24, 2026

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prior threads are all addressed in the current diff (SKILL.md index renamed to upgrade, yes now accepted, stale run_update() doc comment updated). Refactor is coherent and the cache back-compat test is a nice touch. LGTM.

…ssages

Avoids locking an offline user out of every command when a fresh cache
records a newer version but the network is unavailable (decline exits 1,
and accepting also fails in update_to). Surface the bypass in both the
decline and failed-upgrade messages.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All four prior review threads are resolved in the code (command-index rename, yes acceptance, update_to() doc fix, and the offline HOTDATA_NO_UPDATE_CHECK hint). Gate correctly skips non-interactive sessions and returns no-op when offline with a stale cache. No blocking issues.

@eddietejeda
eddietejeda merged commit b250d0a into main Jun 24, 2026
14 checks passed
@eddietejeda
eddietejeda deleted the feat/upgrade-gate branch June 24, 2026 02:34
@zfarrell

Copy link
Copy Markdown
Contributor

approved

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request rust Pull requests that update rust code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants