Skip to content

feat: add keenable update self-update command - #68

Merged
IlyaGusev merged 4 commits into
mainfrom
feat/update-command
Aug 19, 2026
Merged

feat: add keenable update self-update command#68
IlyaGusev merged 4 commits into
mainfrom
feat/update-command

Conversation

@IlyaGusev

Copy link
Copy Markdown
Collaborator

What

Adds keenable update — downloads and installs the latest GitHub release in place.

How

  • Uses axoupdater, cargo-dist's self-update library. It reads the install receipt the shell/PowerShell installers write, fetches the latest release, and swaps the binary (handles the Windows in-use-executable case).
  • Explicitly checks the receipt belongs to the running executable — without this check axoupdater reports "no update needed" for a stray binary instead of an error.
  • Homebrew installs get an error with the brew upgrade hint; manual/cargo builds get the installer one-liner. Exit 1 in both cases.
  • The hourly update notice now suggests keenable update instead of the curl one-liner (Homebrew installs still get the brew command).
  • Kills the daemon after a successful update so the next command starts the new binary.

Output

👀  keenable update

   ✓  Checked for updates
   ✓  Updated keenable v0.2.1 → v0.2.2

Testing

  • cargo build, cargo clippy, cargo test pass.
  • Live end-to-end: a fake install root with a v0.1.0 receipt really downloaded v0.2.2 and replaced the binary; up-to-date and no-receipt paths verified too.
  • New e2e test covers the deterministic no-receipt path (AXOUPDATER_CONFIG_PATH pointed at an empty dir), so the suite never replaces the binary under test.

Notes

  • axoupdater adds a heavy dependency tree (aws-lc-sys needs cmake at build time). axoupdater itself ships for the same cargo-dist targets, so release CI should build — watch the first release.
  • Already-released binaries lack the command; they update once via the printed installer path and get self-update from then on.

🤖 Generated with Claude Code

Uses axoupdater (cargo-dist's self-update library) with the install
receipt the shell/powershell installers write. Homebrew and manual
installs get an error with the right fix hint instead. The hourly
update notice now suggests `keenable update` for installer installs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add receipt-aware keenable update self-updates

✨ Enhancement 🧪 Tests 📝 Documentation 🕐 20-40 Minutes

Grey Divider

AI Description

• Adds receipt-aware keenable update self-updates from the latest GitHub release.
• Guides Homebrew and unsupported installs to appropriate upgrade or reinstall commands.
• Stops the daemon after replacement and updates notices, documentation, and guard-rail coverage.
Diagram

graph TD
  U["keenable update"] --> H{"Homebrew install?"} -- Yes --> BH["Show brew hint"]
  H -- No --> R{"Valid receipt?"} -- No --> IH["Show reinstall hint"]
  R -- Yes --> A["axoupdater"] --> G["GitHub release"] --> B["Replace binary"] --> D["Stop daemon"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Re-run generated installer scripts
  • ➕ Avoids axoupdater's large native and networking dependency tree
  • ➕ Keeps replacement logic centralized in cargo-dist installer scripts
  • ➖ Provides a less seamless user experience
  • ➖ Requires shell or PowerShell execution and platform-specific orchestration
  • ➖ May offer weaker receipt-to-executable validation from the CLI
2. Build a custom GitHub release updater
  • ➕ Could minimize dependencies and tailor behavior to Keenable
  • ➕ Would provide full control over validation, download, and messaging
  • ➖ Duplicates security-sensitive download and replacement logic
  • ➖ Requires custom Windows in-use executable handling
  • ➖ Adds ongoing maintenance across all release targets

Recommendation: Keep the axoupdater approach because it aligns with cargo-dist receipts and already handles cross-platform, in-place replacement—including Windows executable constraints. The dependency and CMake build cost is significant, so release CI and artifact-size impact should be verified on every supported target before shipping.

Files changed (9) +749 / -8

Enhancement (4) +88 / -3
mod.rsExport the update command module +1/-0

Export the update command module

• Registers 'update_cmd' in the commands module so the CLI dispatcher can invoke it.

src/commands/mod.rs

update_cmd.rsImplement receipt-aware self-updating +65/-0

Implement receipt-aware self-updating

• Adds the self-update workflow using axoupdater, including Homebrew rejection, receipt ownership validation, current-version correction, progress reporting, and reinstall hints. Successful updates replace the executable and stop the daemon so subsequent commands run the new version.

src/commands/update_cmd.rs

main.rsExpose and dispatch the update subcommand +11/-2

Expose and dispatch the update subcommand

• Adds 'keenable update' with usage guidance and routes it to the asynchronous update workflow. It suppresses the redundant hourly update check during self-update and changes update notices to use installation-aware hints.

src/main.rs

update.rsProvide installation-aware update guidance +11/-1

Provide installation-aware update guidance

• Exposes Homebrew installation detection and adds 'update_hint', which recommends self-update for installer installs and Homebrew commands for brew-managed binaries. Existing installer hints remain available for recovery paths.

src/update.rs

Tests (1) +15 / -0
test_update.pyTest rejection when no installer receipt exists +15/-0

Test rejection when no installer receipt exists

• Adds deterministic end-to-end coverage that redirects axoupdater to an empty configuration directory. The test verifies a nonzero exit and a safe reinstall hint without risking replacement of the test binary.

tests/e2e/test_update.py

Documentation (2) +2 / -1
CLAUDE.mdDocument the self-update command module +1/-0

Document the self-update command module

• Adds 'update_cmd.rs' to the documented source tree and identifies its dependency on installer receipts.

CLAUDE.md

README.mdDocument manual updates through the CLI +1/-1

Document manual updates through the CLI

• Replaces the installer-script update instruction with 'keenable update' while retaining the Homebrew upgrade command.

README.md

Other (2) +644 / -4
Cargo.lockLock axoupdater and its transitive dependencies +643/-4

Lock axoupdater and its transitive dependencies

• Records axoupdater 0.10.2 and its networking, TLS, asset, process, native-build, and platform-specific dependency graph. The lockfile now includes a second reqwest version used transitively by axoupdater.

Cargo.lock

Cargo.tomlAdd axoupdater as a runtime dependency +1/-0

Add axoupdater as a runtime dependency

• Adds axoupdater 0.10.2 to provide cargo-dist-compatible receipt loading, release retrieval, and executable replacement.

Cargo.toml

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@qodo-code-review

qodo-code-review Bot commented Aug 19, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Update notice bypasses ui ✓ Resolved 📘 Rule violation ⚙ Maintainability
Description
The modified update notification remains inside a bespoke eprintln! with inline colors instead of
using shared ui helpers. This bypasses centralized formatting and wrapping for human-facing
output.
Code

src/main.rs[369]

+            update::update_hint().cyan()
Evidence
PR Compliance ID 2 requires human-facing output to use shared ui helpers. The changed
update::update_hint().cyan() value is rendered by the ad-hoc eprintln! in src/main.rs, while
src/ui.rs provides centralized warning and hint helpers for this output.

CLAUDE.md: Use shared ui module for all human-facing command output (no ad-hoc formatting)
src/main.rs[365-370]
src/ui.rs[152-174]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The update notification uses ad-hoc `eprintln!` formatting and inline colors instead of the shared terminal UI helpers.

## Issue Context
PR Compliance ID 2 requires all human-facing command output to use helpers from `src/ui.rs`. Refactor the notification while preserving the version and install-specific update hint.

## Fix Focus Areas
- src/main.rs[365-370]
- src/ui.rs[152-174]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. New daemon cleanup race ✗ Dismissed 🐞 Bug ☼ Reliability
Description
After a successful update, kill_daemon waits for the old daemon's reply but then unconditionally
deletes the socket and PID files. If another command starts a replacement daemon between the old
daemon's exit and this cleanup, the update removes the new daemon's files, orphaning it and allowing
duplicate daemons to start.
Code

src/commands/update_cmd.rs[44]

+            daemon::kill_daemon();
Evidence
The new update success path invokes kill_daemon after replacement. The daemon replies before
checking ownership, removing its files, and exiting, while kill_daemon ignores that reply and
subsequently removes the shared paths without checking which PID now owns them; daemon startup can
bind those same paths and write a new PID during this interval.

src/commands/update_cmd.rs[38-49]
src/daemon.rs[190-207]
src/daemon.rs[318-338]
src/daemon.rs[98-135]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The post-update daemon shutdown can delete socket and PID files belonging to a newly started replacement daemon. Make cleanup conditional on ownership and distinguish a valid shutdown acknowledgment from the legacy-daemon fallback.

## Issue Context
The daemon already checks PID ownership when cleaning up its own files. The client-side `kill_daemon` ignores the response and unconditionally removes both files, creating a race after the old daemon acknowledges and exits.

## Fix Focus Areas
- src/commands/update_cmd.rs[42-44]
- src/daemon.rs[318-338]
- src/daemon.rs[190-207]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
Review mode: ⚖️ Balanced: This adds behavior that downloads and replaces the running executable, with installer-receipt validation, platform-specific handling, daemon lifecycle effects, and CLI integration; it is high-risk but principally one self-update path that a careful single review can assess.

Grey Divider

Tip of the day
💡 Did you know, you can show, collapse, or hide each part of a finding: code, evidence, and all

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread src/main.rs Outdated
Comment thread src/commands/update_cmd.rs
IlyaGusev and others added 2 commits August 19, 2026 20:01
Addresses Qodo review: human-facing output must use src/ui.rs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- close the progress step once, before the match, not in every arm
- update_hint delegates to install_hint for the Homebrew branch
- expose update::current_version() and drop the raw env! copies
- derive the axoupdater app name from CARGO_PKG_NAME
- fold the duplicate error+hint+exit blocks into bail_reinstall
- note why Update skips the background update check

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@IlyaGusev
IlyaGusev merged commit 3f47e4b into main Aug 19, 2026
12 checks passed
@IlyaGusev
IlyaGusev deleted the feat/update-command branch August 19, 2026 20:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant