feat: daemon auto-update support with hourly check loop#777
Merged
svarlamov merged 4 commits intofeat/async-mode-mar-17from Mar 24, 2026
Merged
feat: daemon auto-update support with hourly check loop#777svarlamov merged 4 commits intofeat/async-mode-mar-17from
svarlamov merged 4 commits intofeat/async-mode-mar-17from
Conversation
Contributor
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
|
|
- Add DaemonUpdateCheckResult enum and two public functions to upgrade.rs: - check_for_update_available(): checks if newer version exists (no install) - check_and_install_update_if_available(): checks + downloads + runs installer Both use Config::fresh() to respect runtime config changes. - Add daemon_update_check_loop() in daemon.rs: background thread that wakes hourly (sleeping in 5s ticks for prompt shutdown response), calls check_for_update_available(), and requests graceful shutdown on UpdateReady. - Add daemon_run_pending_self_update() in daemon.rs: runs after clean shutdown to fetch and install the update. Unix: atomic mv replaces binary. Windows: detached PowerShell installer polls until exe is unlocked. - Spawn update check thread alongside control and trace listener threads in run_daemon(). - Add unit tests for DaemonUpdateCheckResult and update check building blocks. Co-Authored-By: Sasha Varlamov <sasha@sashavarlamov.com>
The post-shutdown install function was re-checking the 24h time guard via should_check_for_updates(), which would always return false because check_for_update_available() had just updated last_checked_at. Now it reads the cached update_available flag directly, bypassing the time guard. Also clears the cache after successful install to prevent redundant re-installs. Co-Authored-By: Sasha Varlamov <sasha@sashavarlamov.com>
Three integration tests verify the daemon auto-update thread behavior: 1. daemon_update_check_loop_detects_cached_update_and_shuts_down: Seeds a fake update cache with a pending update, starts the daemon with a short check interval (1s via GIT_AI_DAEMON_UPDATE_CHECK_INTERVAL env var), and verifies the daemon self-shuts-down after detecting the cached update. Asserts clean exit, lock release, and socket closure. 2. daemon_update_check_loop_respects_disabled_auto_updates: Same cached update but with disable_auto_updates=true via config patch. Verifies the daemon stays alive through multiple check cycles. 3. daemon_update_check_loop_no_update_stays_alive: Seeds cache with no pending update, verifies daemon remains running. Also adds GIT_AI_DAEMON_UPDATE_CHECK_INTERVAL env var override to daemon_update_check_interval() so integration tests can use a short interval without waiting the production 1-hour cycle. Co-Authored-By: Sasha Varlamov <sasha@sashavarlamov.com>
Prevents a CPU spin-loop when GIT_AI_DAEMON_UPDATE_CHECK_INTERVAL is set to 0. Without the clamp, both interval and tick become 0, causing the inner while-loop to skip immediately and the outer loop to call check_for_update_available() in a tight busy-loop. Co-Authored-By: Sasha Varlamov <sasha@sashavarlamov.com>
a66d7db to
2407968
Compare
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.
Summary
Adds auto-update support to daemon mode. A new background thread wakes hourly to check if a newer version is available. When one is found, the daemon gracefully shuts down, and the installer runs post-shutdown to replace the binary. The next git command auto-starts a fresh daemon from the updated binary (via existing
ensure_daemon_running()).Changes:
src/commands/upgrade.rs: AddedDaemonUpdateCheckResultenum and two new public functions:check_for_update_available()— queries the releases API + updates cache, no installcheck_and_install_update_if_available()— reads cached update state, re-fetches release, downloads + runs installer silently. Intentionally bypasses the 24h time guard (readsupdate_availablefrom cache instead) so the install actually runs after the hourly check already confirmed availability.Config::fresh()(not theOnceLocksingleton) so the long-running daemon respects runtime config changessrc/daemon.rs:daemon_update_check_loop()— background thread sleeping in 5s ticks (for prompt shutdown), checks hourlydaemon_update_check_interval()— readsGIT_AI_DAEMON_UPDATE_CHECK_INTERVALenv var override (clamped to min 1s to prevent spin-loops), defaults to 3600sdaemon_run_pending_self_update()— called after clean shutdown to run the installerrun_daemon()tests/daemon_mode.rs: Three integration tests exercising the full daemon update thread lifecycle:daemon_update_check_loop_detects_cached_update_and_shuts_down— seeds fake update cache, starts daemon with 1s interval, verifies self-shutdown with clean exit, lock release, and socket closuredaemon_update_check_loop_respects_disabled_auto_updates— same cache butauto_updates_disabled=true, verifies daemon stays alivedaemon_update_check_loop_no_update_stays_alive— no pending update in cache, verifies daemon remains runningReview & Testing Checklist for Human
check_for_update_availablecache-hit path: Whenshould_check_for_updatesreturns false (not yet 24h), the function still returnsUpdateReadyif a previous check cached a pending update. This means the daemon could shut down on a stale cache entry from a prior non-daemon check. Verify this is acceptable.check_and_install_update_if_availableconfirms the update via cache, then makes a network call to get the release tag for the installer. If the API is unreachable at shutdown time, the install silently fails (debug_log only) and the old binary continues. Verify this graceful degradation is acceptable.daemon_run_pending_self_updateruns on every shutdown, not just update-triggered ones. Manualgit-ai daemon shutdownwill also attempt an install if a cached update exists. This seems correct but should be verified as intended.check_and_install_update_if_available()call will fail silently (no real release server). The install path is only exercised by the unit tests on its building blocks. Consider whether a mock release server test is needed.GIT_AI_DAEMON_UPDATE_CHECK_INTERVALenv var is not gated behindcfg(test): It's available in production builds to allow external testing. Verify this is acceptable or whether it should be debug-only.Suggested manual test plan:
git-ai daemon run)update_available = trueand a future version to~/.git-ai/internal/update_checkGIT_AI_DAEMON_UPDATE_CHECK_INTERVAL=10for a short cycleNotes
feat/async-mode-mar-17).install.shorinstall.ps1— Unix atomicmvworks on running binaries, and Windowsinstall.ps1already hasWait-ForFileAvailablepolling.max(1)to prevent a CPU spin-loop if the env var is set to0.Link to Devin session: https://app.devin.ai/sessions/f511157ae89e4449a7edb023a9c55cf7
Requested by: @svarlamov