Skip to content

fix(update): refresh brew tap before upgrade and verify result - #87

Merged
elkaix merged 2 commits into
mainfrom
fix/homebrew-updater-stale-tap
Jun 8, 2026
Merged

fix(update): refresh brew tap before upgrade and verify result#87
elkaix merged 2 commits into
mainfrom
fix/homebrew-updater-stale-tap

Conversation

@elkaix

@elkaix elkaix commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Problem

On a Homebrew install, pythinker update prints a contradictory result:

Running: brew upgrade pythinker-code
Warning: pythoughts-labs/pythinker/pythinker-code 0.37.0 already installed
Updated successfully!

The update both fails to install and falsely reports success.

Root cause

Two independent issues, confirmed empirically (local tap HEAD was one commit behind the upstream homebrew-pythinker tap; an explicit brew update flipped the local formula 0.37.0 → 0.38.0 and the upgrade then worked):

  1. Stale tap, no refresh. brew upgrade <formula> resolves against the locally-cloned tap formula. A Homebrew tap only advances on brew update; the updater never ran it, so a stale clone pinned 0.37.0 and the upgrade no-op'd. (brew upgrade's implicit auto-update refreshed only the core/cask JSON API, not the third-party git tap.)
  2. Exit-code-only success. do_update treated returncode == 0 as success. A no-op brew upgrade exits 0, so it printed "Updated successfully!" without ever checking that the installed version advanced.

It's compounded by the existing readiness gate reading the remote formula (sees 0.38.0 ✓) while brew upgrade reads the stale local tap — two different sources of truth.

Changes (src/pythinker_code/ui/shell/update.py, Homebrew-gated)

  • Refresh before upgrade. Run brew update --quiet before brew upgrade so a stale clone can't pin the old version. Best-effort — a failed refresh logs a warning and still attempts the upgrade (the version check below catches a no-op). --quiet avoids dumping the host's full outdated-formula list.
  • Verify the version advanced. New _installed_homebrew_version() re-resolves the installed version via brew list --versions after a 0 exit (the running process's own importlib.metadata can't observe an in-place upgrade). Only prints "Updated successfully!" when it reached the target; otherwise returns FAILED with a clear, actionable message.

Scope

Both paths are Homebrew-only. The uv/pip/pipx paths still trust exit code 0 — PyPI is authoritative with no stale local clone, so a false-positive is far less likely. Deliberate boundary, not an oversight.

Testing

  • 5 new tests in tests/ui_and_conv/test_shell_update.py (TDD, RED verified first): refresh-ordering, no-op→FAILED, refresh-failure-is-non-fatal, version parsing (max-of-multiple-kegs / non-zero exit → None).
  • Full update suite: 131 passed. ruff check, ruff format --check, and the type checker are clean.
  • The underlying brew mechanism was verified manually (brew updatebrew upgrade advanced 0.37.0 → 0.38.0). The patched do_update was not exercised against a live stale-tap scenario because the local tap is now current.

Summary by CodeRabbit

  • Bug Fixes
    • Homebrew updater now refreshes metadata before upgrading and verifies the installed version actually advanced, preventing false-success reports from stale taps.
  • Tests
    • Added tests covering the Homebrew update/upgrade flow and version-detection behavior.
  • Documentation
    • Unreleased changelog entry describing the Homebrew updater fix.

`pythinker update` on a Homebrew install ran `brew upgrade pythinker-code`
against the locally-cloned tap. A stale clone pins the old formula, so the
upgrade silently no-ops ("0.37.0 already installed") while the updater — which
only checked the subprocess exit code — still printed "Updated successfully!".

- Run `brew update --quiet` to refresh the tap before `brew upgrade`, so a
  stale clone can no longer pin the old version. Best-effort: a failed refresh
  does not block the upgrade attempt.
- After a brew upgrade exits 0, re-resolve the installed version via
  `brew list --versions` (the running process's own importlib.metadata cannot
  observe an in-place upgrade) and report a clear failure instead of success
  when the version did not advance to the target.

Both paths are Homebrew-gated; uv/pip/pipx upgrades are unchanged.
@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a3da6433-a59e-4575-a4f9-1520ee360382

📥 Commits

Reviewing files that changed from the base of the PR and between df8e239 and 1f2ae4d.

📒 Files selected for processing (1)
  • src/pythinker_code/ui/shell/update.py

📝 Walkthrough

Walkthrough

This PR adds Homebrew updater correctness checks. A new helper extracts the installed version from brew list --versions output. The Homebrew upgrade flow now runs brew update --quiet first, then validates post-upgrade that the installed version actually advanced, returning failure if the version is still stale—preventing no-op success reports caused by stale tap clones.

Changes

Homebrew Updater Correctness

Layer / File(s) Summary
Homebrew version checker helper and unit tests
src/pythinker_code/ui/shell/update.py, tests/ui_and_conv/test_shell_update.py
New _installed_homebrew_version() extracts the highest semantic version from brew list --versions output and returns None on parsing or process failure. Unit tests verify correct max-version extraction and None return on brew command failure.
Homebrew upgrade flow: metadata refresh and version validation
src/pythinker_code/ui/shell/update.py, tests/ui_and_conv/test_shell_update.py
Homebrew upgrade orchestration now runs brew update --quiet before brew upgrade, then validates via _installed_homebrew_version that the installed version actually advanced. Post-upgrade version mismatch returns UpdateResult.FAILED with stale-tap diagnostics. Unit tests cover successful upgrade-with-refresh, stale-version detection, and refresh failure resilience.
Changelog entry documenting the Homebrew fix
CHANGELOG.md
Unreleased section notes the Homebrew updater fix: brew update before upgrade and post-upgrade version re-check to prevent stale tap false-success reports.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Suggested labels

bug

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 43.75% which is insufficient. The required threshold is 70.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed Title follows conventional commits format (fix(update): ...) and clearly describes the main Homebrew updater fix.
Description check ✅ Passed Description comprehensively covers problem, root cause, solution, scope, and testing; includes all template sections.
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 fix/homebrew-updater-stale-tap

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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/pythinker_code/ui/shell/update.py`:
- Around line 661-669: The subprocess.run call that invokes ["brew", "list",
"--versions", "pythinker-code"] uses an excessive timeout and omits an explicit
check flag; change the call in update.py that assigns to result (the
subprocess.run(...) call using get_clean_env()) to use timeout=5 and add
check=False so non-zero returncodes are handled via result.returncode instead of
raising an exception.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 70f4c53f-06fe-49f7-a131-3bc9c7b5be5c

📥 Commits

Reviewing files that changed from the base of the PR and between 2308004 and df8e239.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • src/pythinker_code/ui/shell/update.py
  • tests/ui_and_conv/test_shell_update.py

Comment thread src/pythinker_code/ui/shell/update.py
@codecov

codecov Bot commented Jun 8, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 69.23077% with 8 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/pythinker_code/ui/shell/update.py 69.23% 6 Missing and 2 partials ⚠️

📢 Thoughts on this report? Let us know!

`_installed_homebrew_version` used a 60s timeout for the local
`brew list --versions` query; the file's other local package-state
queries (dpkg-query, rpm) use 5s. A local DB read never needs 60s, and
a shorter timeout fails fast if brew hangs. Addresses CodeRabbit review.
@elkaix
elkaix merged commit f811f87 into main Jun 8, 2026
32 checks passed
@elkaix
elkaix deleted the fix/homebrew-updater-stale-tap branch June 8, 2026 16:44
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