Skip to content

fix(upgrade): verify tool is brew-managed before routing to brew upgrade#904

Open
decode2 wants to merge 8 commits into
Gentleman-Programming:mainfrom
decode2:fix/brew-upgrade-non-brew-tools
Open

fix(upgrade): verify tool is brew-managed before routing to brew upgrade#904
decode2 wants to merge 8 commits into
Gentleman-Programming:mainfrom
decode2:fix/brew-upgrade-non-brew-tools

Conversation

@decode2

@decode2 decode2 commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes upgrade failures on Linux systems where Linuxbrew is installed for unrelated packages. Previously, effectiveMethod() would route ALL tools to brew upgrade when profile.PackageManager == "brew", even if the tool was installed via go install or binary download.

Root Cause

On Linux, platform detection sets PackageManager = "brew" when Linuxbrew is present on PATH — regardless of how individual tools were actually installed. The upgrade logic then blindly routed every tool to brew upgrade, which failed with "No available formula" for tools not managed by Homebrew.

Fix

  1. Added isBrewManaged(ctx, toolName) helper in strategy.go that probes brew list --versions <toolName> to verify the tool is actually installed via Homebrew.

  2. Updated effectiveMethod() to check isBrewManaged() before routing to InstallBrew. If the tool is not brew-managed, it falls through to the next priority (go-install or declared method).

  3. Added execCommandContext package var for testability (same pattern as existing execCommand).

Tests

Closes #186

Summary by CodeRabbit

  • Bug Fixes

    • Improved Homebrew upgrade routing to only use the Homebrew path when the tool is confirmed Homebrew-managed via deterministic formula/cask probing.
  • Improvements

    • Made install-method selection context-aware for better cancellation/timeout behavior during upgrades.
    • Added in-session Homebrew detection caching to reduce repeated Homebrew checks.
  • Tests

    • Expanded and standardized upgrade strategy and Homebrew detection tests with deterministic command mocking.
    • Updated backup-path upgrade tests to use platform-aware path helpers.

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

effectiveMethod now checks whether a tool is actually brew-managed before choosing InstallBrew, and upgrade execution now passes resolved methods through ExecuteWithOptions into runStrategy. Tests add context-aware brew probing, cache isolation, and platform-correct backup path handling.

Changes

Brew-managed detection for upgrade strategy

Layer / File(s) Summary
Test injection hooks and mocking helpers
internal/update/upgrade/executor.go, internal/update/upgrade/strategy.go, internal/update/upgrade/strategy_test.go
Adds test-swappable context-aware command execution, brew-managed cache state with reset support, an OS-specific exit-code helper, and a shared strategy test wrapper.
Brew-managed probe and cache
internal/update/upgrade/strategy.go
Implements isBrewManaged(ctx, toolName) with cached formula/cask probing via brew list --versions.
Context-aware method routing
internal/update/upgrade/strategy.go, internal/update/upgrade/executor.go
Changes strategy routing to use a precomputed install method and makes brew selection depend on the brew-managed probe.
Method resolution in executor flow
internal/update/upgrade/executor.go
Moves install-method resolution into ExecuteWithOptions, passes the resolved method into executeOne, and sets result methods directly for non-executable outcomes.
Platform-aware backup path test
internal/update/upgrade/executor_test.go
Updates the GGA backup-path test to use platform-correct helpers and create the expected files before asserting backup coverage.
Strategy and brew probe test coverage
internal/update/upgrade/strategy_test.go
Updates strategy tests for the context-aware signature, adds deterministic brew probe controls, and expands coverage for caching and probe short-circuiting.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Gentleman-Programming/gentle-ai#826: Both PRs touch the Homebrew upgrade path in internal/update/upgrade/strategy.go and related tests, so they overlap in the same routing and execution flow.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The backup-path test changes in executor_test are unrelated to the brew-managed upgrade fix and appear outside issue #186's scope. Move the backup-path test updates to a separate PR or explain how they are required for the brew-routing fix.
Docstring Coverage ⚠️ Warning Docstring coverage is 62.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly states the main change: verifying brew ownership before routing upgrades to brew.
Linked Issues check ✅ Passed The PR checks brew ownership per tool with brew list and falls back to the declared install method when not brew-managed, matching #186.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@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

🤖 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 `@internal/update/upgrade/strategy.go`:
- Around line 305-318: The isBrewManaged function uses brew list without
explicit filtering flags, which has unreliable exit code semantics across
Homebrew versions. Since the function's contract states it should detect tools
installed "either as a formula or a cask," modify the function to make two
separate execCommandContext calls to brew list, one with the --formula flag and
one with the --cask flag, and return true if either call succeeds (returns nil).
This explicit probing approach ensures correct detection across different
Homebrew versions.
🪄 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: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a180db3c-f0b9-47c9-bf78-815fba8e3bfa

📥 Commits

Reviewing files that changed from the base of the PR and between 8b2e2cf and 601dd6f.

📒 Files selected for processing (5)
  • internal/cli/run.go
  • internal/cli/run_component_paths_test.go
  • internal/update/upgrade/executor.go
  • internal/update/upgrade/strategy.go
  • internal/update/upgrade/strategy_test.go

Comment thread internal/update/upgrade/strategy.go
@decode2 decode2 force-pushed the fix/brew-upgrade-non-brew-tools branch from 601dd6f to 5c412c4 Compare June 16, 2026 05:41

@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: 2

🤖 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 `@internal/update/upgrade/executor.go`:
- Around line 500-501: The effectiveMethod function is being called multiple
times for the same tool (at the code around lines 500-501, 513-514, 521-525, and
within runStrategy at lines 571-577) instead of once per tool, causing repeated
external shell calls via isBrewManaged and creating TOCTOU risks. Compute
effectiveMethod(ctx, r.Tool, profile) once at the start of the tool processing
flow, store the result in a variable, and pass this precomputed method value
through all execution paths instead of recomputing it. Update all the affected
sites to use the stored method value: replace the effectiveMethod calls at lines
500-501, 513-514, 521-525 with references to the precomputed variable, and
modify runStrategy to accept the precomputed method as a parameter instead of
computing it internally at lines 571-577.

In `@internal/update/upgrade/strategy_test.go`:
- Around line 359-365: The test table in the struct definition is missing a test
case that exercises the mixed formula/cask outcome behavior. Currently all test
cases use the same brewListExit value for both probes, so the code path where
one probe fails (like --formula returning non-zero exit) while the other
succeeds (--cask returning zero) is never verified. Add at least one test case
to the tests slice where brewListExit differs from another field or add multiple
entries with different exit codes to cover the scenario where --formula fails
but --cask succeeds, ensuring the fallback/dual-probe logic is fully exercised
in the test coverage.
🪄 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: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4704b492-64b6-4a49-892b-81c614bf7826

📥 Commits

Reviewing files that changed from the base of the PR and between 601dd6f and 5c412c4.

📒 Files selected for processing (3)
  • internal/update/upgrade/executor.go
  • internal/update/upgrade/strategy.go
  • internal/update/upgrade/strategy_test.go

Comment thread internal/update/upgrade/executor.go Outdated
Comment thread internal/update/upgrade/strategy_test.go
@Alan-TheGentleman Alan-TheGentleman added the type:bug Bug fix label Jun 17, 2026

@Alan-TheGentleman Alan-TheGentleman 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.

This is the lower-risk direction because Homebrew ownership should come from Homebrew, not from path heuristics. Please cache the resolved method once per tool so we do not run repeated brew list probes during the same upgrade path. Also add one mixed probe test where --formula fails and --cask succeeds. After that, this is the PR I would keep for #186.

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/update/upgrade/strategy.go (1)

77-83: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Refresh the strategy-routing comment to match current behavior.

Line 77 still says brew profile always routes to brew, but routing is now conditional on isBrewManaged(ctx, toolName). Keeping this comment stale can mislead future changes.

Suggested doc-only fix
-//   - brew profile → brewUpgrade (regardless of tool's declared method)
+//   - brew profile + brew-managed tool → brewUpgrade
+//   - brew profile + non-brew-managed tool → fall through to go-install/declared method
🤖 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 `@internal/update/upgrade/strategy.go` around lines 77 - 83, The upgrade
strategy routing comment describes the brew profile routing as unconditional
with "regardless of tool's declared method", but the actual implementation now
conditionally routes based on the isBrewManaged function check. Update the
comment block describing the upgrade strategy routing to accurately reflect that
brew profile routing is conditional on isBrewManaged(ctx, toolName) rather than
being an unconditional path, ensuring the documentation matches the current
implementation behavior.
🤖 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.

Outside diff comments:
In `@internal/update/upgrade/strategy.go`:
- Around line 77-83: The upgrade strategy routing comment describes the brew
profile routing as unconditional with "regardless of tool's declared method",
but the actual implementation now conditionally routes based on the
isBrewManaged function check. Update the comment block describing the upgrade
strategy routing to accurately reflect that brew profile routing is conditional
on isBrewManaged(ctx, toolName) rather than being an unconditional path,
ensuring the documentation matches the current implementation behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 9d5ddb85-81bc-4c28-ad15-83ba7e09b260

📥 Commits

Reviewing files that changed from the base of the PR and between 5c412c4 and 5ea32e7.

📒 Files selected for processing (2)
  • internal/update/upgrade/strategy.go
  • internal/update/upgrade/strategy_test.go

@decode2

decode2 commented Jun 19, 2026

Copy link
Copy Markdown
Contributor Author

Changes requested have been applied:

✅ Cache �ffectiveMethod result per tool to avoid repeated �rew list probes
✅ Added mixed probe test (formula fails + cask succeeds)

The cache is implemented at the isBrewManaged() level with a package-level map protected by sync.RWMutex. This ensures we don't spawn multiple �rew list processes for the same tool during a single upgrade run.

Ready for re-review.

@decode2

decode2 commented Jun 19, 2026

Copy link
Copy Markdown
Contributor Author

@Alan-TheGentleman Changes requested have been applied and are ready for re-review.

@Alan-TheGentleman Alan-TheGentleman 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.

Approved. The routing change is conceptually correct: a brew profile should not force every tool through Homebrew unless the specific tool is actually brew-managed.

The cache addresses the repeated-probe concern well enough for this CLI process model. This does not solve the Windows PATH shadowing issue in #943, so that should stay separate.

auto-merge was automatically disabled June 23, 2026 04:24

Head branch was pushed to by a user without write access

@decode2

decode2 commented Jun 23, 2026

Copy link
Copy Markdown
Contributor Author

Hi @Alan-TheGentleman, I've updated this PR:

  • Refactored \�xecutor.go\ to compute \�ffectiveMethod\ once per tool and pass it down to \�xecuteOne\ and
    unStrategy\ to eliminate redundant Homebrew shell-out probes.
  • Added \TestIsBrewManaged_MixedProbe\ to cover the mixed probe scenario where --formula\ fails but --cask\ succeeds.

Ready for re-review!

@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

🤖 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 `@internal/update/upgrade/strategy_test.go`:
- Around line 1860-1862: The runStrategyTest helper function invokes
effectiveMethod which routes to execCommandContext for brew package managers,
but execCommandContext is not stubbed in tests, causing real subprocess calls to
the host's Homebrew and making tests non-deterministic. Additionally,
clearBrewManagedCache is never called, allowing cached results to leak between
tests. Stub execCommandContext (similar to how execCommand is stubbed elsewhere
in the test file) and call clearBrewManagedCache inside runStrategyTest to
ensure deterministic behavior and isolation for all tests that use this helper,
including TestRunStrategy_BrewUpgrade, TestRunStrategy_BrewUpgradeFailure,
TestRunStrategy_ExecErrorWrapped, and other brew-related test cases.
🪄 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: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: f0e7fd39-292b-467e-bf4d-f8570d7ccf04

📥 Commits

Reviewing files that changed from the base of the PR and between cad09f6 and 87eba17.

📒 Files selected for processing (4)
  • internal/update/upgrade/executor.go
  • internal/update/upgrade/executor_test.go
  • internal/update/upgrade/strategy.go
  • internal/update/upgrade/strategy_test.go

Comment thread internal/update/upgrade/strategy_test.go Outdated
@decode2

decode2 commented Jun 23, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

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.

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/update/upgrade/executor.go (1)

495-517: 🚀 Performance & Scalability | 🔵 Trivial

Avoid brew list subprocess probes when labeling skipped tools.

For dev-build and version-unknown tools (which are reported as UpgradeSkipped and never executed), effectiveMethod(ctx, ...) invokes isBrewManaged, which shells out to brew list --formula/--cask --versions solely to populate the informational Method field. On a brew profile this probes brew for each distinct skipped tool, adding latency to the upgrade command for tools that won't be upgraded. Consider computing the display label without the brew probe for these non-executed paths (e.g. fall back to the declared method, since a source/dev build is never brew-managed).

Also applies to line 513.

🤖 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 `@internal/update/upgrade/executor.go` around lines 495 - 517, The
effectiveMethod function is being called for devBuilds and versionUnknowns which
are both marked as UpgradeSkipped and never executed, causing unnecessary brew
subprocess probes on brew profiles. In both loops (the devBuilds loop starting
at line 495 and the versionUnknowns loop at line 513), replace the call to
effectiveMethod(ctx, r.Tool, profile) with the declared method from the tool
directly (such as r.Tool.Method) to avoid the expensive brew list subprocess
probe since source/dev builds are never brew-managed anyway and these tools
won't be upgraded regardless.
🤖 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.

Outside diff comments:
In `@internal/update/upgrade/executor.go`:
- Around line 495-517: The effectiveMethod function is being called for
devBuilds and versionUnknowns which are both marked as UpgradeSkipped and never
executed, causing unnecessary brew subprocess probes on brew profiles. In both
loops (the devBuilds loop starting at line 495 and the versionUnknowns loop at
line 513), replace the call to effectiveMethod(ctx, r.Tool, profile) with the
declared method from the tool directly (such as r.Tool.Method) to avoid the
expensive brew list subprocess probe since source/dev builds are never
brew-managed anyway and these tools won't be upgraded regardless.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 61b8e84e-335a-4371-8661-e1d9815cd879

📥 Commits

Reviewing files that changed from the base of the PR and between cad09f6 and efa9400.

📒 Files selected for processing (4)
  • internal/update/upgrade/executor.go
  • internal/update/upgrade/executor_test.go
  • internal/update/upgrade/strategy.go
  • internal/update/upgrade/strategy_test.go

@decode2

decode2 commented Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

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.

@Alan-TheGentleman Alan-TheGentleman enabled auto-merge (squash) June 26, 2026 08:31

@Alan-TheGentleman Alan-TheGentleman 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.

Good direction, but this cannot merge while the PR does not compile. The current Unit Tests job fails in internal/update/upgrade/strategy_test.go because two test call sites still call runStrategy(ctx, result, profile) after the function was changed to require the resolved install method as a fourth argument. Please update those remaining calls, rerun the unit tests, and ping for re-review.

decode2 added 6 commits June 26, 2026 10:51
On Linux systems with Linuxbrew installed for unrelated packages,
profile.PackageManager is set to 'brew' even when tools were installed
via go install or binary download. This caused 'brew upgrade <tool>'
to fail with 'No available formula' errors.

Added isBrewManaged() helper that probes 'brew list --versions <tool>'
to verify the tool is actually managed by Homebrew. effectiveMethod()
now checks this before routing to InstallBrew, falling through to
go-install or the declared method when the tool is not brew-managed.

Closes Gentleman-Programming#186
brew list --versions without explicit type flags has inconsistent exit-code
semantics across Homebrew versions. Probe --formula and --cask separately
to ensure correct detection regardless of Homebrew version.
effectiveMethod is called multiple times per tool during planning,
execution, and verification. Each call previously spawned 2 brew
processes (one for --formula, one for --cask). Cache the result per
tool name to avoid redundant brew list calls during a single upgrade
session.

Also adds tests for mixed probe scenarios (formula fails + cask
succeeds) and cache behavior verification.

Addresses review feedback from @Alan-TheGentleman
… brew routing

Update the runStrategy documentation comment to accurately reflect that
brew routing is now conditional on isBrewManaged(ctx, toolName), not
unconditional for brew profiles.

Addresses CodeRabbit review feedback.
auto-merge was automatically disabled June 26, 2026 14:04

Head branch was pushed to by a user without write access

@decode2 decode2 force-pushed the fix/brew-upgrade-non-brew-tools branch from 52c03b8 to c2c51d2 Compare June 26, 2026 14:04
@decode2

decode2 commented Jun 26, 2026

Copy link
Copy Markdown
Contributor Author

@Alan-TheGentleman ready for re-review! I've rebased the branch on main and resolved the compilation failures in strategy_test.go by updating the outdated runStrategy call sites.

@decode2

decode2 commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

@Alan-TheGentleman all your review points are addressed on the current head (c2c51d2):

  • The resolved brew method is now cached per tool (brewManagedCache) so there are no repeated brew list probes, with the mixed-probe regression test (TestIsBrewManaged_MixedProbe, --formula fails / --cask succeeds).
  • The compile break is fixed — both runStrategy test call sites now pass the 4th update.InstallInstaller arg.

CI is green and CodeRabbit's latest pass has no actionable comments. Ready for re-review whenever you have a moment 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type:bug Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(upgrade): brew upgrade fails for non-brew tools on Linux with Linuxbrew

2 participants