Skip to content

feat: migrate self-update logic to kaishin v0.2.3#47

Merged
yukimemi merged 1 commit into
mainfrom
feat/migrate-to-kaishin
May 9, 2026
Merged

feat: migrate self-update logic to kaishin v0.2.3#47
yukimemi merged 1 commit into
mainfrom
feat/migrate-to-kaishin

Conversation

@yukimemi
Copy link
Copy Markdown
Owner

@yukimemi yukimemi commented May 6, 2026

This PR migrates the internal self-update logic to the universal library \kaishin.

Key changes:

  • Switched to \kaishin\ for GitHub release checking and updating.
  • Added a minimal \ okio\ runtime in \src/updater.rs\ to bridge \kaishin's async API with
    enri's blocking execution.
  • Simplified \main.rs\ update check handling using \updater::Checker.
  • Improved maintainability by removing duplicate logic.

Summary by CodeRabbit

  • Chores
    • Updated internal dependencies and refactored the update-check system to improve maintainability and stability.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 6, 2026

Warning

Rate limit exceeded

@yukimemi has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 42 minutes and 57 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 303068f1-70ee-443f-a4cf-89a6dbc13013

📥 Commits

Reviewing files that changed from the base of the PR and between 98d51f5 and 0f37856.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • .kata/applied.toml
  • Cargo.toml
  • src/main.rs
  • src/updater.rs
📝 Walkthrough

Walkthrough

This PR migrates the self-update mechanism from the self_update and reqwest crates to the kaishin library. The Checker struct replaces previous updater types, and the background update-check flow is refactored to use kaishin::LatestRelease instead of updater::LatestRelease.

Changes

Self-Update Library Migration

Layer / File(s) Summary
Dependency Changes
Cargo.toml
Replace humantime, reqwest, and self_update dependencies with kaishin and tokio for async runtime support.
Core Updater Refactoring
src/updater.rs
Introduce public Checker struct wrapping kaishin::Checker with methods for interval configuration, check scheduling, banner formatting, and cached release retrieval. Delegate default_interval() and run_self_update() to kaishin implementations.
Auto-Update Flow Integration
src/main.rs
Refactor AutoUpdateHandle enum: CachedAvailable and Pending variants now carry Checker and kaishin::LatestRelease types instead of updater::LatestRelease. Update maybe_spawn_auto_update_check() and finalize_auto_update_check() to construct and use the new types, replacing banner logic with checker.format_banner().
Metadata & Tests
.kata/applied.toml, src/updater.rs
Update applied timestamp and add basic test verifying Checker initialization and readiness check.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • yukimemi/renri#22: Modifies the same self-update functionality and refactors src/updater.rs and the background check flow in src/main.rs.

Poem

🐰 The updater hops with newfound grace,
Kaishin guides it through the race.
Old banners fall, new checkers arise,
Async toil beneath the skies!
A cleaner path to stay up-to-date.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly and accurately describes the main change: migrating self-update logic to kaishin v0.2.3, which is reflected in all the modified files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/migrate-to-kaishin

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request migrates the application's self-update functionality to the kaishin library, replacing custom implementation logic. The review identifies several areas for improvement, including refactoring the Checker struct to avoid holding a tokio::runtime::Runtime to reduce resource overhead, and addressing regressions in update state management and the handling of the _non_interactive flag.

Comment thread src/updater.rs
Comment on lines +27 to 45
pub struct Checker {
inner: kaishin::Checker,
rt: tokio::runtime::Runtime,
}

/// Saves the update check state to the persistent state file atomically.
pub fn save_check_state(state: &UpdateCheckState) -> Result<()> {
if let Some(p) = state_path() {
if let Some(parent) = p.parent() {
std::fs::create_dir_all(parent)?;
let json = serde_json::to_string(state)?;
let mut tmp = tempfile::NamedTempFile::new_in(parent)?;
use std::io::Write;
tmp.write_all(json.as_bytes())?;
tmp.persist(&p)?;
}
impl Checker {
pub fn new() -> Result<Self> {
let opts = kaishin::KaishinOptions::new(
"yukimemi",
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_VERSION"),
);
let inner = kaishin::Checker::new(env!("CARGO_PKG_NAME"), opts);
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()?;
Ok(Self { inner, rt })
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The Checker struct currently owns a tokio::runtime::Runtime. Since this runtime is only used for the blocking check_and_save call, keeping it alive in the struct (and thus in the AutoUpdateHandle in main.rs until the program exits) is unnecessary resource overhead. Additionally, because Runtime is not Clone, it forces redundant recreation of the checker in main.rs when spawning the background thread.

Consider refactoring Checker to not hold the Runtime, and instead create a temporary runtime within check_and_save and run_self_update. This makes the struct lightweight and infallible to initialize.

pub struct Checker {
    inner: kaishin::Checker,
}

impl Checker {
    pub fn new() -> Self {
        let opts = kaishin::KaishinOptions::new(
            "yukimemi",
            env!("CARGO_PKG_NAME"),
            env!("CARGO_PKG_NAME"),
            env!("CARGO_PKG_VERSION"),
        );
        Self {
            inner: kaishin::Checker::new(env!("CARGO_PKG_NAME"), opts),
        }
    }

Comment thread src/updater.rs
Comment on lines +56 to 59
pub fn check_and_save(&self) -> Result<kaishin::LatestRelease> {
self.rt
.block_on(async { self.inner.check_and_save().await })
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

If the Runtime is removed from the Checker struct, it should be created locally within this method. This ensures that the runtime is only active during the network call.

    pub fn check_and_save(&self) -> Result<kaishin::LatestRelease> {
        let rt = tokio::runtime::Builder::new_current_thread()
            .enable_all()
            .build()?;
        rt.block_on(self.inner.check_and_save())
    }

Comment thread src/main.rs
let state = updater::load_check_state();
let now = std::time::SystemTime::now();
let current = env!("CARGO_PKG_VERSION").to_string();
let checker = updater::Checker::new().ok()?;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

If updater::Checker::new() is refactored to be infallible (by moving the runtime creation), this line can be simplified.

Suggested change
let checker = updater::Checker::new().ok()?;
let checker = updater::Checker::new();

Comment thread src/main.rs
}

let (tx, rx) = std::sync::mpsc::channel();
let checker_clone = updater::Checker::new().ok()?.interval(interval);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

This line creates a second Checker instance just to move it into the background thread. If updater::Checker is refactored to be lightweight and implement Clone, you could simply clone the existing instance. Even without Clone, recreating it is much cheaper if it doesn't initialize a tokio runtime.

Suggested change
let checker_clone = updater::Checker::new().ok()?.interval(interval);
let checker_clone = updater::Checker::new().interval(interval);

Comment thread src/main.rs
Comment on lines 325 to 329
let res = rx.recv_timeout(std::time::Duration::from_secs(1));
let now_unix = std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);

let mut state = updater::load_check_state().unwrap_or(updater::UpdateCheckState {
last_checked_unix: 0,
last_known_latest: None,
last_known_url: None,
});

state.last_checked_unix = now_unix;

if let Ok(Ok(latest)) = res {
state.last_known_latest = Some(latest.tag_name.clone());
state.last_known_url = Some(latest.html_url.clone());
let _ = updater::save_check_state(&state);
if updater::is_update_available(&current, &latest.tag_name).unwrap_or(false) {
eprintln!("\n{}", updater::format_update_banner(&current, &latest));
}
} else {
// Even on timeout or error, update the last_checked_unix to avoid constant checking.
let _ = updater::save_check_state(&state);
eprintln!("\n{}", checker.format_banner(&latest));
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

There is a regression in the update check state management. The previous implementation (lines 339-362 in the old version) ensured that the "last checked" timestamp was updated and saved even if the background check timed out or failed. This prevented the tool from attempting a check on every subsequent run in slow network conditions. The new implementation removes this logic, which may lead to repeated check attempts if the 1-second timeout is frequently hit.

Comment thread src/updater.rs Outdated
Comment on lines 12 to 24
pub fn run_self_update(yes: bool, check_only: bool, _non_interactive: bool) -> Result<()> {
let opts = kaishin::KaishinOptions::new(
"yukimemi",
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_VERSION"),
);

/// Fetches the latest release information from the GitHub API.
pub fn check_latest_release() -> Result<LatestRelease> {
let url = "https://api.github.com/repos/yukimemi/renri/releases/latest";
let client = reqwest::blocking::Client::builder()
.user_agent(format!("renri/{}", env!("CARGO_PKG_VERSION")))
.timeout(Duration::from_secs(5))
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()?;
let res = client.get(url).send()?;
if !res.status().is_success() {
return Err(anyhow!("GitHub releases API returned {}", res.status()));
}
let release: LatestRelease = res.json()?;
Ok(release)
}

/// Returns the path to the update check state file in the user's data directory.
fn state_path() -> Option<PathBuf> {
dirs::data_dir().map(|d| d.join("renri").join("last_update_check.json"))
rt.block_on(async { kaishin::run_self_update(&opts, yes, check_only).await })
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The _non_interactive parameter is currently unused. The previous implementation used this flag to bail early if an update was available but the environment was non-interactive and --yes was not provided. Ignoring this flag may lead to the tool attempting to prompt for confirmation in environments where no terminal is available, potentially causing hangs or failures.

pub fn run_self_update(yes: bool, check_only: bool, non_interactive: bool) -> Result<()> {
    if !yes && non_interactive {
        anyhow::bail!("non-interactive mode: use `--yes` to proceed with update");
    }
    let opts = kaishin::KaishinOptions::new(
        "yukimemi",
        env!("CARGO_PKG_NAME"),
        env!("CARGO_PKG_NAME"),
        env!("CARGO_PKG_VERSION"),
    );

    let rt = tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()?;
    rt.block_on(kaishin::run_self_update(&opts, yes, check_only))
}

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main.rs`:
- Around line 301-315: When scheduling a background refresh you currently drop
any known cached update; instead capture checker.cached_update() before spawning
the thread and carry that value through the Pending path (e.g., extend
AutoUpdateHandle::Pending to include a cached_latest: Option<_>). Create the
channel and spawn the thread as you do with checker_clone.check_and_save(), but
return Some(AutoUpdateHandle::Pending { checker, rx, cached_latest }) so callers
can fall back to cached_latest when rx times out or returns an error; update the
consumer logic that resolves Pending to use cached_latest on timeout/failure.
Ensure identifiers referenced: checker.should_check(), checker.cached_update(),
AutoUpdateHandle::Pending, checker_clone, check_and_save(), tx/rx, and interval.

In `@src/updater.rs`:
- Around line 75-79: The test test_checker_init is brittle because
Checker::should_check() depends on persisted updater state; change the test to
only assert that Checker::new() returns Ok (i.e., construction succeeds) OR
ensure the test isolates state by creating a temporary/unique state directory
(or clearing kaishin's state) before creating the Checker, then call
checker.should_check() deterministically; update test code to use only
unwrap()/is_ok() on Checker::new() if you don’t want to manipulate state, or add
setup/teardown that points Checker to a temp dir so should_check() yields a
stable value.
- Around line 12-23: The run_self_update function currently ignores the
_non_interactive argument causing `renri --non-interactive self-update` to still
prompt; update run_self_update to propagate the non-interactive flag to kaishin
by passing the actual boolean (rename _non_interactive to non_interactive) into
kaishin::run_self_update, or explicitly detect non_interactive and return an Err
with a clear message if kaishin::run_self_update does not support
non-interactive mode; adjust the call site in run_self_update so the third
parameter is used instead of being discarded.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f5f93693-5789-4c36-8bb6-00d9c4885c4c

📥 Commits

Reviewing files that changed from the base of the PR and between c2ff292 and 98d51f5.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • .kata/applied.toml
  • Cargo.toml
  • src/main.rs
  • src/updater.rs

Comment thread src/main.rs
Comment thread src/updater.rs Outdated
Comment thread src/updater.rs Outdated
Why:
- To consolidate self-update logic into a universal library \kaishin\.
- To improve code maintainability and standardize update banners.

Changes (addressing reviews):
- Refactored \updater::Checker\ to avoid persistent \	okio::runtime::Runtime\ to reduce resource overhead.
- Propagated \
on_interactive\ flag to \kaishin::run_self_update\ to honor \
enri --non-interactive\.
- Improved \AutoUpdateHandle\ to carry \cached_latest\ so banners can fallback to cached state on timeout.
- Fixed brittle tests by removing dependency on persistent state.
@yukimemi yukimemi force-pushed the feat/migrate-to-kaishin branch from 98d51f5 to 0f37856 Compare May 9, 2026 11:40
@yukimemi
Copy link
Copy Markdown
Owner Author

yukimemi commented May 9, 2026

Thank you @gemini-code-assist and @coderabbitai for the thorough reviews!

I have addressed all the identified issues in this push:

  • Runtime overhead: Refactored updater::Checker to avoid holding a persistent tokio::runtime::Runtime. It now creates a runtime only when performing sync blocking operations.
  • non_interactive flag: Now correctly propagating the non_interactive argument to kaishin::run_self_update.
  • Cache fallback: Enhanced AutoUpdateHandle::Pending to carry cached_latest. In finalize_auto_update_check, if the background fetch times out, it now falls back to displaying the cached update banner.
  • Brittle tests: Updated test_checker_init to only verify successful initialization without depending on persistent state.
  • Library update: Bumped kaishin to v0.2.4 to support the new UpdateOptions API.

@gemini-code-assist
Copy link
Copy Markdown

Thank you for the update, @yukimemi. The refactoring to avoid persistent runtime overhead and the improvements to the AutoUpdateHandle logic for cache fallback look excellent. These changes address the concerns raised during the review and significantly improve the robustness of the self-update mechanism. The updated test case also correctly focuses on initialization without unnecessary state dependencies. Great work on the migration to kaishin v0.2.4.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 9, 2026

@yukimemi Thank you for the detailed update! All the addressed points look great:

  • Runtime overhead – Creating the tokio::runtime::Runtime on-demand rather than holding it persistently is the right approach for minimizing resource usage.
  • non_interactive flag – Good catch; correctly forwarding this to kaishin::run_self_update ensures the intended UX behavior.
  • Cache fallback – The graceful degradation to a cached banner on timeout is a solid UX improvement.
  • Brittle tests – Decoupling test_checker_init from persistent state makes it more reliable across environments.
  • Library update – Bumping to kaishin v0.2.4 for the UpdateOptions API support ties it all together nicely.

I'll trigger a fresh review to verify the updated changes.

(˶ᵔ ᵕ ᵔ˶) ♡

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@yukimemi yukimemi merged commit 3293beb into main May 9, 2026
10 checks passed
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