Skip to content

Agent-facing update detection: cached check + recommendation#2

Merged
ulmentflam merged 2 commits into
mainfrom
update-detection
May 27, 2026
Merged

Agent-facing update detection: cached check + recommendation#2
ulmentflam merged 2 commits into
mainfrom
update-detection

Conversation

@ulmentflam

@ulmentflam ulmentflam commented May 27, 2026

Copy link
Copy Markdown
Owner

Lets the /autosentry skill detect a newer release and recommend the upgrade command across Claude, Cursor, Codex, OpenCode, Zed, and Gemini — inspired by GSD's idempotent self-update, adapted to autosentry's skill model.

What

updater.py

  • check() caches the PyPI "latest version" on disk (~/.cache/autosentry, XDG-aware, 24h TTL). use_cache=False forces a live query. Cache is best-effort — corrupt/unwritable → cache miss, never an error.
  • Detects Homebrew installs (Cellar path) and runs brew upgrade autosentry for them (falls back to install.sh only for --pre/pinned --version, which brew can't honor against the tap).

autosentry update command

  • --check now exits 0 and prints a recommendation (→ update available — run autosentry update, or brew upgrade autosentry) so agent triage chains don't abort on a non-zero exit.
  • --json{"current","latest","is_outdated"}; --no-cache forces a live query.

SkillsAGENTS.md and the per-tool wrappers run autosentry update --check during triage and surface the recommendation, explicitly without upgrading unprompted (it can restart the CLI mid-session).

Tests

New coverage for caching (hit / miss / TTL expiry / allow_pre keying / corrupt cache), Homebrew detection, and the CLI --check/--json output. Full suite green (206 passed), ruff + pyrefly clean.

Heads-up (separate from this PR)

src/autosentry/cli.py is dead code — it's shadowed by the cli/ package that's actually imported, which is why it only had init/run/status/incidents/dispatcher while the real CLI has update, skills, doctor, etc. Left untouched here; worth deleting in a follow-up.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • autosentry update --check now caches PyPI lookups for a day (use --no-cache to bypass), always exits 0, and prints a recommendation if an update is available.
    • --json produces machine-readable output (current/latest/is_outdated).
    • Detects install method (including Homebrew) and recommends the appropriate upgrade command; Homebrew users get a brew upgrade suggestion.
  • Documentation

    • README and changelog updated with new options and behavior.
  • Tests

    • Added tests covering update checks, caching, install-method detection, and JSON output.

Review Change Stack

Lets the /autosentry skill (Claude, Cursor, Codex, OpenCode, Zed, Gemini)
detect a newer release on every invocation and recommend the upgrade
command, without hammering PyPI or upgrading unprompted.

updater.py:
- check() caches the PyPI "latest version" lookup on disk (XDG-aware,
  24h TTL); use_cache=False forces a live query. Best-effort — a corrupt
  or unwritable cache is just a miss.
- detect_install_method() recognizes Homebrew (Cellar) installs and
  perform_update() runs `brew upgrade autosentry` for them, falling back
  to install.sh only for --pre / pinned --version.

cli update command:
- --check now exits 0 and prints a recommendation ("→ update available —
  run autosentry update", or `brew upgrade autosentry` for Homebrew) so
  agent triage chains don't abort on a non-zero exit.
- --json emits {"current","latest","is_outdated"}; --no-cache forces live.

skills: AGENTS.md and the per-tool wrappers run `autosentry update --check`
during triage and surface the recommendation.

Tests: caching (hit/miss/ttl/allow_pre/corrupt), brew detection, and the
CLI --check/--json behavior. Full suite green (206).

Note: src/autosentry/cli.py is dead code — shadowed by the cli/ package —
and is untouched here; flagging separately.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 27, 2026

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f2c1836d-1ab0-42b3-a0c7-eb56684d4ebc

📥 Commits

Reviewing files that changed from the base of the PR and between a6e5386 and 3537baa.

📒 Files selected for processing (2)
  • src/autosentry/updater.py
  • tests/test_updater.py

📝 Walkthrough

Walkthrough

The PR adds disk-caching for PyPI update checks, Homebrew install detection and a brew upgrade path, extends the CLI with --json and --no-cache (check mode now exits 0 and can emit JSON), updates agent skill prompts to run autosentry update --check, and adds tests covering caching, Homebrew detection, and CLI output.

Changes

Agent-driven update detection with caching

Layer / File(s) Summary
Updater caching infrastructure
src/autosentry/updater.py, tests/test_updater.py
Adds XDG-aware disk cache, TTL-based freshness checks, cache-keying by allow_pre, and best-effort cache writes. check() gains use_cache and ttl parameters. Tests cover cache reuse, bypassing cache, TTL expiry, allow_pre separation, and corrupt/invalid cache handling.
Homebrew install method detection & upgrade
src/autosentry/updater.py, tests/test_updater.py
Detects Homebrew-managed installs via /Cellar/autosentry/ in the interpreter path and returns brew. perform_update() runs brew upgrade autosentry for standard upgrades, falling back to install.sh for pre-release or pinned-version cases. Tests verify Homebrew detection.
CLI command integration & output modes
src/autosentry/cli/commands/update.py, tests/test_cli.py
Adds --json (implies --check) and --no-cache options, and _recommended_command() to suggest an upgrade command by install method. --check no longer exits non-zero when outdated; it prints a recommendation or emits JSON with current, latest, is_outdated. Runtime check errors emit JSON when --json is set; otherwise styled error message and exit 1. Tests validate messaging and JSON output.
Agent skill prompt updates
src/autosentry/templates/skills/AGENTS.md, claude.md, codex.md, cursor.md, gemini.toml, opencode.md, zed.md
Adds a triage step to run autosentry update --check (cached daily), instructs agents to mention an available update once and recommend the shown upgrade command (autosentry update or brew upgrade autosentry), optionally use --json for parsing, and avoid unprompted upgrades.
Documentation & changelog
CHANGELOG.md, README.md
Documents the new agent-driven update detection, caching behavior with --no-cache, --json output and check semantics, Homebrew detection/upgrades, and expands README examples to include --check, --check --json, and --pre.

Sequence Diagram

sequenceDiagram
  participant Agent
  participant CLI as autosentry update --check
  participant Updater
  participant Cache as Disk Cache
  participant PyPI
  
  Agent->>CLI: invoke --check
  CLI->>Updater: check(use_cache=True, ttl=86400)
  Updater->>Cache: read cached result
  alt Cache hit & TTL valid
    Cache-->>Updater: return cached UpdateCheck
  else Cache miss or stale
    Updater->>PyPI: fetch_latest_version()
    PyPI-->>Updater: latest version
    Updater->>Cache: write result (best-effort)
    Cache-->>Updater: written
  end
  Updater-->>CLI: UpdateCheck { current, latest, is_outdated }
  alt --json output
    CLI-->>Agent: JSON { current, latest, is_outdated }
  else User-facing
    alt is_outdated
      CLI-->>Agent: "→ update available\nRun: autosentry update (or brew upgrade)"
    else up-to-date
      CLI-->>Agent: (silent)
    end
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Bunny hops through cache halls bright,
PyPI queries rest at night,
Homebrew paths now in our sight,
Agents nudge with update right,
Check it once, reuse with might!

🚥 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 directly summarizes the main feature: agent-facing update detection with caching and recommendation functionality, which is the primary change across all 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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch update-detection

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/autosentry/cli/commands/update.py (1)

22-27: ⚡ Quick win

Consider adding test coverage for the Homebrew recommendation path.

The _recommended_command() helper and its integration into the check-mode output work correctly, but the current tests don't verify the Homebrew-specific recommendation ("brew upgrade autosentry"). Consider adding a test that mocks detect_install_method to return "brew" and asserts the output contains "brew upgrade autosentry".

🧪 Example test to add

Add this to tests/test_cli.py:

def test_update_check_outdated_recommends_brew_when_homebrew_install(
    runner: CliRunner, monkeypatch
):
    from autosentry.updater import UpdateCheck

    def fake_check(**_kwargs):
        return UpdateCheck(current="0.1.0", latest="0.2.0", is_outdated=True)

    def fake_detect():
        return "brew"

    monkeypatch.setattr("autosentry.cli.commands.update.updater_check", fake_check)
    monkeypatch.setattr("autosentry.cli.commands.update.detect_install_method", fake_detect)

    result = runner.invoke(app, ["update", "--check"])
    assert result.exit_code == 0
    out = _plain(result.output)
    assert "brew upgrade autosentry" in out

Also applies to: 82-89

🤖 Prompt for 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.

In `@src/autosentry/cli/commands/update.py` around lines 22 - 27, Add a unit test
that asserts the Homebrew-specific recommendation by mocking
detect_install_method to return "brew" and the updater_check to report an
outdated version; specifically, add a test (e.g.,
test_update_check_outdated_recommends_brew_when_homebrew_install) in
tests/test_cli.py that monkeypatches
autosentry.cli.commands.update.detect_install_method to return "brew" and
autosentry.cli.commands.update.updater_check (or the updater_check import used
in that module) to return an UpdateCheck with is_outdated=True, then invoke the
CLI (runner.invoke(app, ["update","--check"])) and assert the output contains
"brew upgrade autosentry" to cover the _recommended_command() brew path.
tests/test_updater.py (1)

111-117: ⚡ Quick win

Add regression coverage for valid-JSON wrong-shape cache content.

Current corruption coverage misses cases like []/null/"str" that are valid JSON but invalid schema.

Suggested test addition
 def test_check_treats_corrupt_cache_as_miss(tmp_path, monkeypatch):
@@
     monkeypatch.setattr(updater, "fetch_latest_version", _counting_fetch([]))
     assert check().latest == "9.9.9"
+
+
+def test_check_treats_non_object_cache_as_miss(tmp_path, monkeypatch):
+    monkeypatch.setenv("XDG_CACHE_HOME", str(tmp_path))
+    cache = tmp_path / "autosentry" / "update-check.json"
+    cache.parent.mkdir(parents=True)
+    cache.write_text("[]", encoding="utf-8")
+    monkeypatch.setattr(updater, "fetch_latest_version", _counting_fetch([]))
+    assert check().latest == "9.9.9"
🤖 Prompt for 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.

In `@tests/test_updater.py` around lines 111 - 117, The existing test
test_check_treats_corrupt_cache_as_miss only covers syntactically invalid JSON;
extend it to also cover valid-JSON but wrong-shape cache contents (e.g. [],
null, "str", and an object missing expected fields) by parametrizing the test to
write each of those payloads into the same cache path, then call check() and
assert it falls back to the network result (with monkeypatched
updater.fetch_latest_version as in the test) so check().latest == "9.9.9"; keep
using the same helpers (tmp_path, monkeypatch) and the same cache path logic to
locate the code that reads and validates the cache in check().
🤖 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/autosentry/updater.py`:
- Around line 132-137: The cache loading code in function check() assumes the
JSON payload is a dict and will raise AttributeError for valid JSON types like
lists or strings; after json.loads(...) verify that the loaded value (data) is
an instance of dict and treat non-dict payloads as a cache miss by returning
None so subsequent calls to data.get don't fail; update the branch around
_cache_path(), json.loads, and the following use of data.get (referencing
_cache_path() and the local variable data and the check() function) to perform
an isinstance(data, dict) guard and return None when it is not a dict.

---

Nitpick comments:
In `@src/autosentry/cli/commands/update.py`:
- Around line 22-27: Add a unit test that asserts the Homebrew-specific
recommendation by mocking detect_install_method to return "brew" and the
updater_check to report an outdated version; specifically, add a test (e.g.,
test_update_check_outdated_recommends_brew_when_homebrew_install) in
tests/test_cli.py that monkeypatches
autosentry.cli.commands.update.detect_install_method to return "brew" and
autosentry.cli.commands.update.updater_check (or the updater_check import used
in that module) to return an UpdateCheck with is_outdated=True, then invoke the
CLI (runner.invoke(app, ["update","--check"])) and assert the output contains
"brew upgrade autosentry" to cover the _recommended_command() brew path.

In `@tests/test_updater.py`:
- Around line 111-117: The existing test test_check_treats_corrupt_cache_as_miss
only covers syntactically invalid JSON; extend it to also cover valid-JSON but
wrong-shape cache contents (e.g. [], null, "str", and an object missing expected
fields) by parametrizing the test to write each of those payloads into the same
cache path, then call check() and assert it falls back to the network result
(with monkeypatched updater.fetch_latest_version as in the test) so
check().latest == "9.9.9"; keep using the same helpers (tmp_path, monkeypatch)
and the same cache path logic to locate the code that reads and validates the
cache in check().
🪄 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 Plus

Run ID: ad52e412-bbea-4892-aaeb-6691ffc76417

📥 Commits

Reviewing files that changed from the base of the PR and between d5d4cf3 and a6e5386.

📒 Files selected for processing (13)
  • CHANGELOG.md
  • README.md
  • src/autosentry/cli/commands/update.py
  • src/autosentry/templates/skills/AGENTS.md
  • src/autosentry/templates/skills/claude.md
  • src/autosentry/templates/skills/codex.md
  • src/autosentry/templates/skills/cursor.md
  • src/autosentry/templates/skills/gemini.toml
  • src/autosentry/templates/skills/opencode.md
  • src/autosentry/templates/skills/zed.md
  • src/autosentry/updater.py
  • tests/test_cli.py
  • tests/test_updater.py

Comment thread src/autosentry/updater.py
_read_cache only guarded JSON parse errors and key mismatches; valid JSON
of the wrong shape (e.g. `[]`) would hit data.get() and raise AttributeError,
crashing check() instead of missing gracefully. Add an isinstance(data, dict)
guard and a regression test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ulmentflam ulmentflam merged commit d5f0fb4 into main May 27, 2026
11 of 12 checks passed
@ulmentflam ulmentflam deleted the update-detection branch May 27, 2026 23:06
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