fix(upgrade): verify tool is brew-managed before routing to brew upgrade#904
fix(upgrade): verify tool is brew-managed before routing to brew upgrade#904decode2 wants to merge 4 commits into
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthrough
ChangesBrew-managed detection for upgrade strategy
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (5)
internal/cli/run.gointernal/cli/run_component_paths_test.gointernal/update/upgrade/executor.gointernal/update/upgrade/strategy.gointernal/update/upgrade/strategy_test.go
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.
601dd6f to
5c412c4
Compare
There was a problem hiding this comment.
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
📒 Files selected for processing (3)
internal/update/upgrade/executor.gointernal/update/upgrade/strategy.gointernal/update/upgrade/strategy_test.go
| Method: effectiveMethod(ctx, r.Tool, profile), | ||
| Status: UpgradeSkipped, |
There was a problem hiding this comment.
Resolve the install method once per tool and pass it through execution.
Line 521 and Line 576 now recompute effectiveMethod, and runStrategy computes it again downstream. Because brew detection now shells out (isBrewManaged), this creates repeated external probes per tool and a TOCTOU risk where the reported Method can diverge from the method actually executed if probes fail intermittently.
Suggested fix
diff --git a/internal/update/upgrade/executor.go b/internal/update/upgrade/executor.go
@@
- method := effectiveMethod(ctx, r.Tool, profile)
+ method := effectiveMethod(ctx, r.Tool, profile)
msg := fmt.Sprintf("Upgrading %s via %s (%s → %s)", r.Tool.Name, method, r.InstalledVersion, r.LatestVersion)
sp := NewSpinner(pw, msg)
- toolResult := executeOne(ctx, r, profile, dryRun)
+ toolResult := executeOne(ctx, r, profile, dryRun, method)
@@
-func executeOne(ctx context.Context, r update.UpdateResult, profile system.PlatformProfile, dryRun bool) ToolUpgradeResult {
+func executeOne(ctx context.Context, r update.UpdateResult, profile system.PlatformProfile, dryRun bool, method update.InstallMethod) ToolUpgradeResult {
base := ToolUpgradeResult{
ToolName: r.Tool.Name,
OldVersion: r.InstalledVersion,
NewVersion: r.LatestVersion,
- Method: effectiveMethod(ctx, r.Tool, profile),
+ Method: method,
}
@@
- exitReq, err := runStrategy(ctx, r, profile)
+ exitReq, err := runStrategy(ctx, r, profile, method)diff --git a/internal/update/upgrade/strategy.go b/internal/update/upgrade/strategy.go
@@
-func runStrategy(ctx context.Context, r update.UpdateResult, profile system.PlatformProfile) (bool, error) {
+func runStrategy(ctx context.Context, r update.UpdateResult, profile system.PlatformProfile, method update.InstallMethod) (bool, error) {
@@
- method := effectiveMethod(ctx, r.Tool, profile)
-
switch method {Also applies to: 513-514, 521-525, 571-577
🤖 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 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.
Alan-TheGentleman
left a comment
There was a problem hiding this comment.
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.
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
There was a problem hiding this comment.
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 winRefresh 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
📒 Files selected for processing (2)
internal/update/upgrade/strategy.gointernal/update/upgrade/strategy_test.go
… 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.
|
Changes requested have been applied: ✅ Cache �ffectiveMethod result per tool to avoid repeated �rew list probes 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. |
|
@Alan-TheGentleman Changes requested have been applied and are ready for re-review. |
Summary
Fixes upgrade failures on Linux systems where Linuxbrew is installed for unrelated packages. Previously,
effectiveMethod()would route ALL tools tobrew upgradewhenprofile.PackageManager == "brew", even if the tool was installed viago installor 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 tobrew upgrade, which failed with "No available formula" for tools not managed by Homebrew.Fix
Added
isBrewManaged(ctx, toolName)helper instrategy.gothat probesbrew list --versions <toolName>to verify the tool is actually installed via Homebrew.Updated
effectiveMethod()to checkisBrewManaged()before routing toInstallBrew. If the tool is not brew-managed, it falls through to the next priority (go-install or declared method).Added
execCommandContextpackage var for testability (same pattern as existingexecCommand).Tests
TestEffectiveMethodwith new cases for issue fix(upgrade): brew upgrade fails for non-brew tools on Linux with Linuxbrew #186:brew profile + tool NOT brew-managed + go-install → go-installbrew profile + tool NOT brew-managed + binary → binarymockExitCommandhelper for simulatingbrew listexit codesCloses #186
Summary by CodeRabbit