Add inline auto-update prompt after version notification#997
Merged
gtrrz-victor merged 25 commits intomainfrom Apr 23, 2026
Merged
Add inline auto-update prompt after version notification#997gtrrz-victor merged 25 commits intomainfrom
gtrrz-victor merged 25 commits intomainfrom
Conversation
When the existing daily version check detects a newer release, the CLI prints the usual notification and then asks the user "Install the new version now? [Y/n]". Accepting runs whatever command versioncheck.updateCommand(current) resolves to, streaming stdio so the user sees the installer. Declining skips; the 24h version-check cache prevents re-prompts within a day. Guardrails: TTY + empty CI env + ENTIRE_NO_AUTO_UPDATE kill switch. No subcommands, no settings file, no silent auto-install. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Entire-Checkpoint: f880b34689b1
4 tasks
Contributor
There was a problem hiding this comment.
Pull request overview
Adds an interactive inline auto-update flow to the existing daily version check so that, when an outdated release is detected, the CLI can optionally run the appropriate installer command (brew/mise/scoop/curl) immediately.
Changes:
- Invoke a new
MaybeAutoUpdateprompt after printing the “newer version available” notification. - Add the auto-update implementation that prompts the user and shells out to the resolved installer command with stdio passthrough.
- Add unit tests and architecture documentation for the new behavior.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| docs/architecture/auto-update.md | Documents the new post-notification auto-update prompt and its guardrails. |
| cmd/entire/cli/versioncheck/versioncheck.go | Calls MaybeAutoUpdate after printing the outdated-version notification; minor comment tweaks. |
| cmd/entire/cli/versioncheck/autoupdate.go | Implements interactive confirm prompt + installer subprocess execution. |
| cmd/entire/cli/versioncheck/autoupdate_test.go | Adds unit tests covering guardrails, decline path, success, and installer failure messaging. |
| cmd/entire/cli/versioncheck/versioncheck_test.go | Minor refactor in TestUpdateCommand (path constant reuse). |
Pre-select the affirmative so Enter accepts, matching the (Y/n) UX described in docs/architecture/auto-update.md. Previously the zero value of the bound bool made "No" the default, contradicting the prompt and surprising users who expected the implied accept. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 7b8020d9f4cb
Replaces the ad-hoc stdoutIsTerminal seam with the shared
interactive.CanPromptInteractively() helper, which opens /dev/tty
directly. This handles three review items with a single change:
- Cobra redirects via cmd.SetOut(&bytes.Buffer{}) no longer matter;
the TTY check is independent of the writer fd.
- Test binaries running from a real terminal skip the prompt via
ENTIRE_TEST_TTY=0 set in setupCheckAndNotifyTest.
- Agent subprocesses that inherit the user's stdout but have no
controlling terminal for stdin hit the /dev/tty open failure and
are skipped; CI runners land on the same path.
Also drops the redundant os.Getenv("CI") check — the /dev/tty probe
already covers it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 4c7de4acdbd6
Contributor
Author
|
bugbot run |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 5872166. Configure here.
Self-hosted runners and some Docker configurations leave /dev/tty attached, so the prior /dev/tty-only probe would let huh forms run in CI and hang the pipeline. Gate on CI in addition to /dev/tty; ENTIRE_TEST_TTY still overrides both so existing tests that exercise the interactive path keep working. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 096757b44493
Remove the design-doc file from this PR as requested. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 23fdeb653d1e
CI guardrail in CanPromptInteractively short-circuits to false when CI=1, which broke TestResume_LocalLogNewerTimestamp_UserConfirmsOverwrite in GitHub Actions — the pty is real but the env check wins. Override with ENTIRE_TEST_TTY=1 in RunCommandInteractive so interactive prompts work. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Entire-Checkpoint: a1da08f67a46
Move hasTTY from strategy package to interactive package (its documented home) and replace all interactive.CanPromptInteractively callers with interactive.HasTTY. One check path for agent-subprocess and /dev/tty detection across cli, strategy, and versioncheck. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 1f83680e3b77
Keep the consolidated body (agent-subprocess + /dev/tty checks) under the original name so call sites and tests read the same as before. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Entire-Checkpoint: df1ddfe6b261
pfleidi
previously approved these changes
Apr 22, 2026
Self-hosted runners and some Docker CI configs expose /dev/tty, so the existing TTY probe alone would let the auto-update huh prompt fire and hang the pipeline. Add CI=<non-empty> as a central guardrail in CanPromptInteractively so every interactive caller (auto-update, setup, login, etc.) stays silent on CI. Entire-Checkpoint: e1608c493997
Previously the 24h cache was bumped before the installer ran, so if `brew upgrade` / `mise upgrade` / `scoop update` failed (network loss mid-install, auth, etc.), the user had to wait a full day for the auto-update prompt to re-arm. MaybeAutoUpdate now reports installer failure back to CheckAndNotify, which zeros LastCheckTime so the next CLI invocation re-fetches and re-prompts. Fetch-failure behavior (cache still bumped to avoid hammering GitHub during outages) is unchanged. Entire-Checkpoint: 47637d280ce6
# Conflicts: # cmd/entire/cli/integration_test/testenv.go # cmd/entire/cli/interactive/interactive.go # cmd/entire/cli/interactive/interactive_test.go # cmd/entire/cli/strategy/manual_commit_hooks.go # cmd/entire/cli/strategy/preparecommitmsg_bench_test.go # e2e/testutil/repo.go
Revert the cache-invalidation-on-installer-failure branch and keep the
24h window uniform for every terminal state:
- User declines → silent for 24h (unchanged).
- User accepts and installer succeeds → no re-prompt until the next
outdated release is detected 24h later (unchanged).
- User accepts and installer fails → print the failure plus a manual
retry hint ("Try again later with: <installer command>") and stay
silent for 24h. Avoids re-prompting every invocation while an upstream
issue (network, auth, repo outage) is still in place; the user has
the exact command to retry manually when ready.
Drops the now-unused installerFailed return value from MaybeAutoUpdate
and the corresponding cache-reset path in CheckAndNotify. Matches the
simpler "every failure path keeps the 24h cache fresh" invariant (only
a fetch failure leaves the cache fresh as before, same as accept/decline
paths).
Entire-Checkpoint: a3cbe08bf582
The new CI=* guardrail in CanPromptInteractively short-circuits the interactive gate when the runner sets CI=true, even for subprocesses that have a real pty attached. This broke resume_interactive_test on GitHub Actions: the spawned CLI inherited CI=true, returned false from CanPromptInteractively, and bailed with "cannot prompt to overwrite local session logs in non-interactive mode". Set ENTIRE_TEST_TTY=1 in the pty test helper so the subprocess stays interactive regardless of the runner's CI env. Matches the existing pattern established in 1d1e012. Entire-Checkpoint: bbb8fed6c7ce
Manual testing of the auto-update prompt requires hitting the real GitHub API, which rate-limits unauthenticated clients at 60 req/h per IP. Iterating on the prompt UX (scenarios × retries) blows through that quickly, especially when the 24h cache is cleared between attempts. Introduce two env-var overrides: - ENTIRE_VERSION_CHECK_URL → overrides the /releases/latest endpoint - ENTIRE_VERSION_CHECK_RELEASES_URL → overrides the /releases list endpoint Point them at any mock HTTP server that returns a GitHubRelease JSON payload. Tests continue to override the vars directly; production callers are unaffected (env unset → real GitHub URLs). Entire-Checkpoint: b05ec25035ac
Banner drops the "Run '<cmd>' to update." line — the interactive prompt that immediately follows already offers to run the installer, so the extra sentence is redundant noise. On non-interactive environments (CI, agent subprocess) the banner still shows what's new; the user can infer the upgrade command from their install manager. Installer-failure hint becomes "Try again later running:\n <cmd>" — the command now sits on its own indented line, which is easier to copy when the installer string is long (e.g. the curl fallback). Entire-Checkpoint: 4384cb5889d6
The URL override env vars were added to make local manual testing easier without hammering the GitHub API. They're scaffolding — not something end users should rely on — so keep them out of the shipped binary. Tests still override the package-level vars directly. Entire-Checkpoint: bf252e83f2e8
When ENTIRE_NO_AUTO_UPDATE is set or CanPromptInteractively returns
false (CI, agent subprocess, no /dev/tty), MaybeAutoUpdate used to
silently return after the banner — leaving the user with no indication
of how to upgrade manually. Now the resolved installer command is
printed:
To update entire run:
brew upgrade --cask entire
This restores the info that was removed from the banner itself (where
it was redundant, because the interactive prompt offers to run it).
Callers with the prompt available still see only the prompt; the hint
is only for paths that skip it.
Entire-Checkpoint: 99cd58ca1c60
main is currently broken — PR #999 (`Add entire activity`) merged with an unresolved reference to `isTerminalWriter` in activity_cmd.go, which doesn't exist in the cli package. The sibling call sites (status_style.go, search_cmd.go, explain.go) correctly use the exported interactive.IsTerminalWriter helper. Apply the same qualification here. Entire-Checkpoint: 4cb94a44f29b
"To update entire run: <cmd>" read ambiguously — "entire" could be
parsed as an adverb ("run the update entirely"). The new wording is
grammatically clean and parallels the installer-failure hint
("Try again later running: <cmd>"), so the two non-happy-path
messages now read as a pair.
Entire-Checkpoint: 29f6ae5b8238
Reviewer flag: `os.Getenv("CI") != ""` also blocks when a developer sets
CI=false to override an inherited value — a common sandbox/devcontainer
workaround. Match the widely used `is-ci` convention: any non-"false"
value blocks, "false" doesn't.
Inlined the two-line check (no helper) and added a targeted test pair:
CI=true blocks, CI=false doesn't. The CI=false assertion relies on
ENTIRE_TEST_TTY=1 to stand in for a real /dev/tty in the host.
Entire-Checkpoint: ae4dc0e79f92
Entire-Checkpoint: 81adb7a7c7f2
The fallback installer command is POSIX (`curl -fsSL ... | bash`), so auto-running it on Windows without a detected install manager would either fail (cmd.exe doesn't know curl-pipe-bash) or look confusing (partial execution, stray bash spawn via WSL, etc.). Introduce `canAutoInstall()` which returns false on Windows unless the binary is under a scoop/mise install prefix. When false, MaybeAutoUpdate skips the prompt and points the user at the releases page instead of printing the unusable POSIX command. A `goos` test seam lets non-Windows hosts exercise both the blocked and permitted Windows paths. New tests: - WindowsUnknownInstallerNoAutoRun: verifies no installer call, download hint shown, POSIX command not surfaced. - WindowsScoopStillAutoRuns: verifies scoop detection on Windows still takes the interactive path. Entire-Checkpoint: f6e622ab0da2
Soph
approved these changes
Apr 23, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

https://entire.io/gh/entireio/cli/trails/e09fcff9bd93
Example
On error:
Flow
flowchart TD start(["entire <command>"]) --> dev{"version == 'dev' or empty?"} dev -- yes --> skip(["silent exit<br/>(no cache touched)"]) dev -- no --> cache{"cache age < 24h?"} cache -- yes --> skip cache -- no --> fetch["fetch GitHub /releases/latest<br/>(2s timeout)"] fetch --> bump["cache.LastCheckTime := now()<br/>saveCache()"] bump --> fok{fetch succeeded?} fok -- no --> done24 fok -- yes --> outdated{isOutdated?} outdated -- no --> done24 outdated -- yes --> banner["print banner:<br/>'A newer version of Entire CLI is available: vX (current: vY)'"] banner --> autoOK{"canAutoInstall()<br/>(Windows + scoop/mise OK;<br/>Windows + unknown blocked)"} autoOK -- "false<br/>(Windows + unknown installer)" --> winHint["print download hint:<br/>'To update, download the latest release from:'<br/>' https://github.com/entireio/cli/releases'"] winHint --> done24 autoOK -- true --> kill{"ENTIRE_NO_AUTO_UPDATE set?"} kill -- yes --> hint["print manual hint:<br/>'To update, run:'<br/>' <installer cmd>'"] kill -- no --> tty{"CanPromptInteractively()<br/>&& isTerminalOut(stdout)"} tty -- "false<br/>(CI=true, no /dev/tty,<br/>agent subprocess, redirected stdout)" --> hint tty -- true --> prompt["huh prompt:<br/>'Install the new version now?'"] hint --> done24 prompt --> ans{user answer} ans -- No --> done24 ans -- Yes --> run["run installer command<br/>(brew / mise / scoop / curl)"] run --> res{installer exit code} res -- 0 --> ok["print:<br/>'Update complete. Re-run entire to use the new version.'"] res -- "non-zero" --> fail["print:<br/>'Update failed: <err>'<br/>'Try again later running:'<br/>' <installer cmd>'"] ok --> done24 fail --> done24 done24(["24h cache is fresh<br/>no re-prompt until it expires"])Invariants:
~/.config/entire/version_check.jsonor wait 24h.version == "dev") and invocations with a fresh cache short-circuit before ever touching the GitHub API.canAutoInstall()(blocks Windows when no known install manager is detected, since the POSIX curl-pipe-bash fallback would fail from cmd.exe), theENTIRE_NO_AUTO_UPDATEkill switch, and the combinedCanPromptInteractively() && isTerminalOut(stdout)gate. The kill switch and interactive gates both printTo update, run: <cmd>; the Windows-unknown path prints a download link instead (no POSIX command is surfaced on Windows).Summary
versioncheck.updateCommand(current)resolves to (brew upgrade --cask entire[@nightly],mise upgrade entire,scoop update entire/cli, or the curl-pipe-bash fallback). stdin/stdout/stderr stream through so installer prompts and progress reach the user.<installer command>") is printed so the user can run it manually once the upstream issue is fixed, without being nagged every invocation.CanPromptInteractively()andisTerminalOut(w)must be true, so redirected output skips the prompt),CImust be empty orfalse,ENTIRE_NO_AUTO_UPDATE=1is a kill switch, and on Windows the auto-run is blocked unless scoop or mise was detected (no POSIX curl-pipe-bash executed from cmd.exe).Test plan
mise run checkgreen locally (unit + integration + canary)CI=trueblocks,CI=falseoverrides), kill-switch, user-declines, happy path, installer failure with retry hint, installer-failure keeps 24h cache fresh, kill-switch / no-TTY / CI paths all print manual-update hint, non-terminal writer skips prompt, Windows + unknown installer shows download link without auto-running, Windows + scoop still auto-runsNon-goals
See
docs/architecture/auto-update.md.Replaces #981 (clean history — single commit on top of
mainwith only the auto-update files).🤖 Generated with Claude Code
Note
Medium Risk
Introduces a new interactive prompt and executes installer commands via
sh -c/cmd /C, which could affect UX or fail in edge environments; guardrails and tests reduce but don’t eliminate runtime risk.Overview
When the daily version check detects the CLI is outdated, it now follows the existing notification with an interactive Y/N prompt to install the new version, and on confirmation shells out to the resolved installer command (brew/mise/scoop/curl), streaming stdin/stdout/stderr so prompts and progress are visible.
The prompt is skipped silently when non-interactive (no TTY) or when
ENTIRE_NO_AUTO_UPDATEis set, and failures are non-blocking (debug-logged or printed as a simple update failure message). Adds unit tests for the auto-update flow and documents the behavior indocs/architecture/auto-update.md.Reviewed by Cursor Bugbot for commit 5872166. Configure here.
Update Since Original Description
entire status > out.txtnow skip the interactive confirm and print the manual update command instead of opening a prompt on/dev/tty.CheckAndNotifytest seam so terminal vs redirected-output behavior is exercised explicitly.Additional Verification
mise run lintgo test ./cmd/entire/cli/versioncheckRemaining Follow-Up
The Windows fallback installer path still resolves to the POSIXResolved in d20d773:curl -fsSL https://entire.io/install.sh | bashcommand for unknown install methods, so that path should not be auto-executed on Windows until it has a Windows-safe fallback.canAutoInstall()now returnsfalseon Windows when the install manager is unknown, skipping the prompt and printing a download-page link instead.