Skip to content

Add release automation and self-update functionality#22

Merged
jfox85 merged 5 commits intomainfrom
jf-release-process
Oct 2, 2025
Merged

Add release automation and self-update functionality#22
jfox85 merged 5 commits intomainfrom
jf-release-process

Conversation

@jfox85
Copy link
Owner

@jfox85 jfox85 commented Oct 1, 2025

Summary

This PR adds comprehensive release automation and self-update capabilities to devx:

Release Automation

  • GoReleaser v2 configuration for multi-platform builds (macOS Intel/ARM, Linux Intel/ARM)
  • GitHub Actions workflow for automated releases on tag push
  • Homebrew tap integration for brew install support
  • Universal install script for manual installation
  • Updated README with installation instructions

Self-Update Feature

  • Automatic update checking on startup (configurable interval, default 24h)
  • devx update command to download and install latest version
  • devx version --check-updates flag to check for updates
  • TUI integration with update banner and 'u' key binding
  • Installation method detection (Homebrew vs manual vs go install)
  • Smart handling to direct Homebrew users to brew upgrade
  • Rate limiting with cached state to avoid excessive GitHub API calls
  • Graceful handling of dev versions and private repositories

Configuration

New config options:

  • auto_check_updates - Enable/disable automatic update checking (default: true)
  • update_check_interval - How often to check for updates (default: 24h)

Testing

  • Comprehensive test coverage for update functionality
  • All pre-commit checks passing (gofmt, go vet, go test -race)
  • Verified installation methods work correctly

Notes

  • Update checking will work once repository is made public
  • First release (v0.1.0) already created and tested
  • Homebrew tap configured at github.com/jfox85/homebrew-devx

Installation Methods After Merge

# Homebrew (recommended for macOS)
brew install jfox85/devx/devx

# Universal installer
curl -fsSL https://raw.githubusercontent.com/jfox85/devx/main/install/install.sh | bash

# Go install
go install github.com/jfox85/devx@latest

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Background automatic update checks with configurable interval and user notifications.
    • New update CLI to check/perform self-updates (supports --check and --force) and version flag --check-updates.
    • TUI shows an update banner, adds an Update key binding (press 'u'), and provides guided update flow and status.
    • Detects installation method and shows tailored upgrade instructions; disables self-update when managed by Homebrew.
    • Update checks use caching and suppress repeated notifications.
  • Tests

    • Added tests for update checking, state persistence, installer detection, and notification logic.
  • Chores

    • Updated packaging metadata, added dependencies, and updated .gitignore.

jfox85 and others added 2 commits October 1, 2025 15:01
- Update description to remove leading article
- Use bin/"devx" syntax instead of "#{bin}/devx" in test

These changes address brew audit warnings for better formula style compliance.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Implements automatic update checking and self-update capabilities:
- Background update checks on startup (configurable interval, default 24h)
- CLI commands for checking and installing updates
- TUI integration with update banner and 'u' key binding
- Installation method detection (Homebrew vs manual vs go install)
- Rate limiting with cached state to avoid excessive API calls
- Graceful handling of dev versions and private repositories

New features:
- `devx update` - Download and install latest version
- `devx version --check-updates` - Check for available updates
- Config options: auto_check_updates, update_check_interval
- Update notifications in TUI with visual banner
- Smart detection to skip self-update for Homebrew installations

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Oct 1, 2025

Walkthrough

Adds a non-blocking auto-update subsystem: background and on-demand update checks with cached state and notification suppression, CLI update command and version-check flag, TUI banner and interactive update flow, install-method detection and self-update execution, persistence/tests, and small release/config adjustments.

Changes

Cohort / File(s) Summary
Project config
\.gitignore, \.goreleaser.yaml, go.mod
Ignore dist/ and .devx/; reorder .tmuxp.yaml entry; update Homebrew description and test invocation in .goreleaser.yaml; add semver and self-update dependencies and related indirect deps in go.mod.
CLI: root/version/update
cmd/root.go, cmd/update.go, cmd/version.go
Add non-blocking background update check on startup (config-driven); new update command with --check and --force; add --check-updates flag to version to query and display update info.
Config: update state
config/updatecheck.go, config/updatecheck_test.go
New UpdateCheckState type and path/load/save helpers; ShouldCheckForUpdates logic; persistence to config dir and tests for state persistence and interval behavior.
TUI integration
tui/model.go
Add Update key binding (u), model fields/messages for update state, async update check at init, update banner rendering in list/preview, confirmation/update flow, and status/instructions handling.
Update subsystem: detection & checking
update/checker.go, update/checker_test.go, update/detector.go, update/detector_test.go, update/installer.go
New GitHub release detection and cached check logic, notification rules, state-marking (MarkUpdateNotified), install-method detection (Homebrew/Go/manual), self-update execution (PerformUpdate/UpdateAvailable), and unit tests for notification and detector behaviors.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant User
  participant CLI as devx (startup)
  participant Cfg as config
  participant Update as update.checker
  Note over CLI: Startup triggers non-blocking check
  CLI->>Cfg: LoadUpdateCheckState()
  CLI->>Update: CheckForUpdatesWithCache(interval)
  Update->>Cfg: Evaluate ShouldCheckForUpdates / SaveUpdateCheckState
  Update-->>CLI: UpdateInfo, checked?
  CLI-->>User: Print notification if ShouldNotifyUser (async)
Loading
sequenceDiagram
  autonumber
  participant User
  participant CLI as devx update cmd
  participant Detect as update.detector
  participant Update as update.checker/installer
  User->>CLI: devx update [--check|--force]
  alt --check
    CLI->>Update: CheckForUpdates()
    Update-->>CLI: UpdateInfo
    CLI-->>User: Print current/latest, availability, URL
  else perform update
    CLI->>Detect: CanSelfUpdate()
    alt Cannot self-update (Homebrew)
      Detect-->>CLI: false
      CLI-->>User: Print Homebrew upgrade instructions
    else Self-update allowed
      CLI->>Update: CheckForUpdates()
      alt update available or --force
        CLI->>Update: PerformUpdate(force)
        Update-->>CLI: success/error
        CLI-->>User: Print status and release notes
      else up-to-date
        CLI-->>User: Confirm latest
      end
    end
  end
Loading
sequenceDiagram
  autonumber
  participant User
  participant TUI as devx TUI
  participant Update as update.checker/installer
  participant Detect as update.detector
  Note over TUI: Init performs async check
  TUI->>Update: CheckForUpdates (async)
  Update-->>TUI: updateAvailableMsg(current, latest)
  TUI-->>User: Show update banner
  User->>TUI: Press 'u' (Update)
  TUI->>Detect: CanSelfUpdate()
  alt self-update allowed
    TUI-->>User: Confirm update?
    User->>TUI: Yes
    TUI->>Update: PerformUpdate(false)
    Update-->>TUI: Result
    TUI-->>User: Success/failure message
  else not allowed
    TUI-->>User: Show install instructions
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

codex

Poem

Thump— I checked the versions, one by one,
I nudged a banner bright as morning sun.
Press "u" if you dare, I'll fetch the prize,
If brew holds you back, I'll show where wisdom lies.
Hop on — devx updated, carrot-sized.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title “Add release automation and self-update functionality” succinctly captures the two primary enhancements introduced by this changeset, aligning directly with the PR’s objectives of adding both automated release workflows and in-application update capabilities without extraneous detail.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch jf-release-process

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

Copy link

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

🧹 Nitpick comments (2)
update/detector.go (2)

45-68: Consider supporting custom Homebrew prefixes.

The current implementation hardcodes common Homebrew paths (/usr/local, /opt/homebrew), which covers most cases. However, users can set custom prefixes via HOMEBREW_PREFIX. For completeness, consider checking this environment variable.

 func isHomebrewManaged(path string) bool {
+	// Check HOMEBREW_PREFIX if set
+	if prefix := os.Getenv("HOMEBREW_PREFIX"); prefix != "" {
+		if strings.Contains(path, filepath.Join(prefix, "Cellar", "devx")) {
+			return true
+		}
+	}
+
 	// Check for Cellar paths (direct installation)
 	if strings.Contains(path, "/usr/local/Cellar/devx") ||

77-91: Consider centralizing the repository path.

The GitHub repository path is hardcoded at line 85. For maintainability, consider defining it as a package-level constant or configuration value, especially since it's likely used in other parts of the update system.

+const (
+	defaultRepo = "github.com/jfox85/devx"
+)
+
 func GetUpdateInstructions() string {
 	method := DetectInstallMethod()
 
 	switch method {
 	case InstallMethodHomebrew:
 		return "Please update using Homebrew:\n  brew upgrade devx\n\nOr:\n  brew update && brew upgrade devx"
 	case InstallMethodGoInstall:
-		return "Please update using go install:\n  go install github.com/jfox85/devx@latest"
+		return fmt.Sprintf("Please update using go install:\n  go install %s@latest", defaultRepo)

Note: This would require adding fmt to imports.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8db8a29 and 4751e6c.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (14)
  • .gitignore (1 hunks)
  • .goreleaser.yaml (2 hunks)
  • cmd/root.go (3 hunks)
  • cmd/update.go (1 hunks)
  • cmd/version.go (3 hunks)
  • config/updatecheck.go (1 hunks)
  • config/updatecheck_test.go (1 hunks)
  • go.mod (3 hunks)
  • tui/model.go (12 hunks)
  • update/checker.go (1 hunks)
  • update/checker_test.go (1 hunks)
  • update/detector.go (1 hunks)
  • update/detector_test.go (1 hunks)
  • update/installer.go (1 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.go: Format all Go code with gofmt before committing
Wrap errors consistently with context throughout the codebase

Files:

  • update/checker_test.go
  • update/installer.go
  • cmd/version.go
  • cmd/root.go
  • config/updatecheck.go
  • update/checker.go
  • tui/model.go
  • config/updatecheck_test.go
  • update/detector_test.go
  • update/detector.go
  • cmd/update.go
**/*_test.go

📄 CodeRabbit inference engine (CLAUDE.md)

**/*_test.go: Place unit tests alongside source files using the *_test.go naming convention
Use the standard Go testing package for tests

Files:

  • update/checker_test.go
  • config/updatecheck_test.go
  • update/detector_test.go
cmd/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

cmd/**/*.go: Place all CLI commands using Cobra under cmd/
When modifying CLI commands, update help text and examples

Files:

  • cmd/version.go
  • cmd/root.go
  • cmd/update.go
cmd/root.go

📄 CodeRabbit inference engine (CLAUDE.md)

Keep base command setup and TUI launcher in cmd/root.go

Files:

  • cmd/root.go
config/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

config/**/*.go: Keep configuration and settings management under config/ (Viper, discovery, projects)
Respect the configuration hierarchy in configuration-related changes

Files:

  • config/updatecheck.go
  • config/updatecheck_test.go
go.mod

📄 CodeRabbit inference engine (CLAUDE.md)

Ensure go.mod is tidy before committing

Files:

  • go.mod
tui/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

tui/**/*.go: Implement the Bubble Tea TUI under tui/ (model, run, styles)
Preserve keyboard navigation patterns when changing the TUI

Files:

  • tui/model.go
🧠 Learnings (1)
📚 Learning: 2025-10-01T21:30:09.446Z
Learnt from: CR
PR: jfox85/devx#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-01T21:30:09.446Z
Learning: Follow configuration hierarchy: project .devx/ overrides global ~/.config/devx/ and env vars override both

Applied to files:

  • .gitignore
🧬 Code graph analysis (8)
update/installer.go (2)
version/version.go (1)
  • Version (10-10)
update/checker.go (2)
  • GitHubRepo (19-19)
  • CheckForUpdates (32-64)
cmd/version.go (1)
update/checker.go (1)
  • CheckForUpdates (32-64)
cmd/root.go (1)
update/checker.go (2)
  • CheckForUpdatesWithCache (67-96)
  • ShouldNotifyUser (102-114)
config/updatecheck.go (1)
config/discovery.go (1)
  • GetConfigDir (97-110)
update/checker.go (2)
version/version.go (1)
  • Version (10-10)
config/updatecheck.go (3)
  • LoadUpdateCheckState (23-44)
  • ShouldCheckForUpdates (67-72)
  • SaveUpdateCheckState (47-64)
tui/model.go (4)
update/detector.go (2)
  • CanSelfUpdate (71-75)
  • GetUpdateInstructions (78-91)
update/installer.go (1)
  • PerformUpdate (13-51)
update/checker.go (1)
  • CheckForUpdates (32-64)
version/version.go (1)
  • Version (10-10)
config/updatecheck_test.go (1)
config/updatecheck.go (4)
  • UpdateCheckState (11-14)
  • SaveUpdateCheckState (47-64)
  • LoadUpdateCheckState (23-44)
  • ShouldCheckForUpdates (67-72)
update/detector_test.go (1)
update/detector.go (8)
  • DetectInstallMethod (20-43)
  • InstallMethod (10-10)
  • InstallMethodHomebrew (13-13)
  • InstallMethodGoInstall (14-14)
  • InstallMethodManual (15-15)
  • InstallMethodUnknown (16-16)
  • CanSelfUpdate (71-75)
  • GetUpdateInstructions (78-91)
🪛 GitHub Actions: Lint
config/updatecheck_test.go

[error] 22-22: golangci-lint: os.Chdir error return value is not checked (errcheck)

🪛 GitHub Check: Lint
config/updatecheck_test.go

[failure] 108-108:
Error return value of os.Chdir is not checked (errcheck)


[failure] 23-23:
Error return value of os.Chdir is not checked (errcheck)


[failure] 22-22:
Error return value of os.Chdir is not checked (errcheck)

🪛 OSV Scanner (2.2.2)
go.mod

[HIGH] 54-54: golang.org/x/crypto 0.32.0: Potential denial of service in golang.org/x/crypto

(GO-2025-3487)


[HIGH] 54-54: golang.org/x/crypto 0.32.0: golang.org/x/crypto Vulnerable to Denial of Service (DoS) via Slow or Incomplete Key Exchange

(GHSA-hcg3-q754-cr77)


[HIGH] 56-56: golang.org/x/oauth2 0.25.0: Unexpected memory consumption during token parsing in golang.org/x/oauth2

(GO-2025-3488)


[HIGH] 56-56: golang.org/x/oauth2 0.25.0: golang.org/x/oauth2 Improper Validation of Syntactic Correctness of Input vulnerability

(GHSA-6v2p-p543-phr9)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build
  • GitHub Check: Build
  • GitHub Check: Test Windows
🔇 Additional comments (4)
.gitignore (1)

3-5: Ignore build and config artifacts correctly.

Adding dist/ and .devx/ keeps release outputs and local config out of Git; reordering .tmuxp.yaml is harmless.

update/detector.go (3)

1-7: LGTM!

Clean package declaration and minimal standard library imports appropriate for the file's functionality.


9-17: LGTM!

Type-safe enum pattern with clear constant values covering all expected installation methods plus an appropriate fallback.


70-75: LGTM!

Clear logic that correctly prevents self-update for Homebrew installations, directing those users to brew upgrade instead.

Comment on lines 21 to 24
originalWd, _ := os.Getwd()
os.Chdir(tempDir)
defer os.Chdir(originalWd)

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Handle os.Chdir errors so lint and tests pass.
The new tests ignore the return value of os.Chdir, which trips golangci-lint’s errcheck (and currently fails CI). Please assert the error and fail fast on failure when swapping directories. Example:

-	originalWd, _ := os.Getwd()
-	os.Chdir(tempDir)
-	defer os.Chdir(originalWd)
+	originalWd, err := os.Getwd()
+	if err != nil {
+		t.Fatalf("getwd: %v", err)
+	}
+	if err := os.Chdir(tempDir); err != nil {
+		t.Fatalf("chdir temp dir: %v", err)
+	}
+	t.Cleanup(func() {
+		if err := os.Chdir(originalWd); err != nil {
+			t.Fatalf("restore cwd: %v", err)
+		}
+	})

Apply the same pattern to the other os.Chdir call in this file.

Also applies to: 107-110

🧰 Tools
🪛 GitHub Actions: Lint

[error] 22-22: golangci-lint: os.Chdir error return value is not checked (errcheck)

🪛 GitHub Check: Lint

[failure] 23-23:
Error return value of os.Chdir is not checked (errcheck)


[failure] 22-22:
Error return value of os.Chdir is not checked (errcheck)

🤖 Prompt for AI Agents
In config/updatecheck_test.go around lines 21 to 24 (and similarly at 107-110),
the os.Chdir calls ignore returned errors; change both to check the error and
fail the test immediately on error (e.g. if err := os.Chdir(tempDir); err != nil
{ t.Fatalf("chdir to tempDir failed: %v", err) }) and likewise for restoring the
original working dir (if err := os.Chdir(originalWd); err != nil {
t.Fatalf("restore wd failed: %v", err) }), ensuring both occurrences are updated
so lint (errcheck) and tests pass.

Comment on lines +37 to +39
if strings.Contains(exePath, "/go/bin/") {
return InstallMethodGoInstall
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fragile detection pattern for go install.

The substring check /go/bin/ can produce false positives (e.g., /myapp/go/bin/) and doesn't respect GOBIN or GOPATH. Consider a more robust check that examines the actual Go environment or matches standard Go paths more precisely.

Example improvement:

-	// Check for go install
-	if strings.Contains(exePath, "/go/bin/") {
-		return InstallMethodGoInstall
-	}
+	// Check for go install (GOPATH/bin or GOBIN)
+	if strings.Contains(exePath, "/go/bin/devx") || 
+		strings.HasSuffix(exePath, "/bin/devx") && (strings.Contains(exePath, filepath.Join("go", "bin")) {
+		return InstallMethodGoInstall
+	}

Alternatively, check for GOPATH or GOBIN environment variables and compare against those paths.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In update/detector.go around lines 37 to 39, the current
strings.Contains(exePath, "/go/bin/") check is fragile and can yield false
positives; replace it with a robust comparison that determines the actual Go
install bin directory(s): obtain GOBIN and GOPATH (first via os.Getenv("GOBIN")
and os.Getenv("GOPATH"), falling back to running `go env GOBIN GOPATH` or using
the default GOPATH if empty), derive the bin path(s) (GOBIN and
filepath.Join(GOPATH, "bin")), normalize them with filepath.Clean, then check
exePath against those directories using filepath.Rel or a path-segment-aware
prefix check (ensure matches a directory boundary) and return
InstallMethodGoInstall only if exePath resides exactly in one of those bin dirs.

errcheck was complaining about unchecked os.Chdir error returns.
Added proper error handling with t.Fatalf for initial chdir and
t.Errorf for deferred directory restoration.
Copy link

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

🧹 Nitpick comments (1)
update/checker_test.go (1)

11-63: Consider removing redundant description field.

The test cases are well-structured and provide good coverage. However, the description field on lines 17, 28, 39, 50, and 61 largely duplicates the information already present in the name field. Since the description is only used in the error message on line 99, you could simplify by using tt.name directly in the error message instead.

Apply this diff to remove the redundant field:

 func TestShouldNotifyUser(t *testing.T) {
 	tests := []struct {
-		name         string
-		info         *UpdateInfo
-		lastNotified string
-		expected     bool
-		description  string
+		name         string
+		info         *UpdateInfo
+		lastNotified string
+		expected     bool
 	}{
 		{
 			name: "new update available, never notified",
 			info: &UpdateInfo{
 				CurrentVersion: "0.1.0",
 				LatestVersion:  "0.2.0",
 				Available:      true,
 			},
 			lastNotified: "",
 			expected:     true,
-			description:  "Should notify when update is available and never notified",
 		},
 		{
 			name: "same update already notified",
 			info: &UpdateInfo{
 				CurrentVersion: "0.1.0",
 				LatestVersion:  "0.2.0",
 				Available:      true,
 			},
 			lastNotified: "0.2.0",
 			expected:     false,
-			description:  "Should not notify if already notified about this version",
 		},
 		{
 			name: "newer update available than last notification",
 			info: &UpdateInfo{
 				CurrentVersion: "0.1.0",
 				LatestVersion:  "0.3.0",
 				Available:      true,
 			},
 			lastNotified: "0.2.0",
 			expected:     true,
-			description:  "Should notify if a newer version is available than last notified",
 		},
 		{
 			name: "no update available",
 			info: &UpdateInfo{
 				CurrentVersion: "0.2.0",
 				LatestVersion:  "0.2.0",
 				Available:      false,
 			},
 			lastNotified: "",
 			expected:     false,
-			description:  "Should not notify when no update is available",
 		},
 	}

And update the error message:

 			if result != tt.expected {
-				t.Errorf("%s: got %v, want %v", tt.description, result, tt.expected)
+				t.Errorf("%s: got %v, want %v", tt.name, result, tt.expected)
 			}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4751e6c and 1a8227c.

📒 Files selected for processing (2)
  • config/updatecheck_test.go (1 hunks)
  • update/checker_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • config/updatecheck_test.go
🧰 Additional context used
📓 Path-based instructions (2)
**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.go: Format all Go code with gofmt before committing
Wrap errors consistently with context throughout the codebase

Files:

  • update/checker_test.go
**/*_test.go

📄 CodeRabbit inference engine (CLAUDE.md)

**/*_test.go: Place unit tests alongside source files using the *_test.go naming convention
Use the standard Go testing package for tests

Files:

  • update/checker_test.go
🧬 Code graph analysis (1)
update/checker_test.go (2)
update/checker.go (2)
  • UpdateInfo (23-29)
  • ShouldNotifyUser (102-114)
config/updatecheck.go (2)
  • UpdateCheckState (11-14)
  • SaveUpdateCheckState (47-64)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build
  • GitHub Check: Test Windows
  • GitHub Check: Test (1.23)
🔇 Additional comments (1)
update/checker_test.go (1)

1-9: LGTM!

Package declaration and imports are clean and follow standard Go conventions.

Address CodeRabbit review feedback:

1. Fix notification flow bug (CRITICAL)
   - Moved LastNotifiedVersion update AFTER showing notification
   - Added MarkUpdateNotified() function for proper state management
   - Users now correctly see update notifications on first check

2. Fix dev version handling (CRITICAL)
   - Non-semver versions (like "dev") now treated as 0.0.0
   - Allows self-update to work from development builds
   - Mirrors the logic already in CheckForUpdates()

3. Update vulnerable dependencies (CRITICAL)
   - golang.org/x/crypto: v0.32.0 → v0.35.0 (fixes DoS vulnerability)
   - golang.org/x/oauth2: v0.25.0 → v0.27.0 (fixes memory exhaustion)
   - golang.org/x/text: v0.21.0 → v0.22.0 (transitive update)

All tests passing. Security vulnerabilities resolved.
Copy link

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

🧹 Nitpick comments (1)
go.mod (1)

8-8: Consider upgrading to blang/semver/v4 for module compatibility.

The current dependency uses v3.5.1+incompatible because v3 lacks a go.mod file. The v4 release is module-aware and provides better Go modules compatibility with a proper module path. Based on learnings.

To migrate to v4, update the import path in your code:

-	"github.com/blang/semver"
+	"github.com/blang/semver/v4"

And update go.mod:

-	github.com/blang/semver v3.5.1+incompatible
+	github.com/blang/semver/v4 v4.0.0

Then run go mod tidy.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1a8227c and ae39818.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (4)
  • cmd/root.go (3 hunks)
  • go.mod (3 hunks)
  • update/checker.go (1 hunks)
  • update/installer.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • cmd/root.go
🧰 Additional context used
📓 Path-based instructions (2)
**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.go: Format all Go code with gofmt before committing
Wrap errors consistently with context throughout the codebase

Files:

  • update/checker.go
  • update/installer.go
go.mod

📄 CodeRabbit inference engine (CLAUDE.md)

Ensure go.mod is tidy before committing

Files:

  • go.mod
🧬 Code graph analysis (2)
update/checker.go (2)
version/version.go (1)
  • Version (10-10)
config/updatecheck.go (4)
  • LoadUpdateCheckState (23-44)
  • ShouldCheckForUpdates (67-72)
  • SaveUpdateCheckState (47-64)
  • UpdateCheckState (11-14)
update/installer.go (2)
version/version.go (1)
  • Version (10-10)
update/checker.go (2)
  • GitHubRepo (19-19)
  • CheckForUpdates (32-64)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Build
  • GitHub Check: Build
  • GitHub Check: Test Windows
  • GitHub Check: Test (1.22)
🔇 Additional comments (9)
go.mod (2)

54-54: Security vulnerabilities resolved.

The critical DoS vulnerabilities in golang.org/x/crypto and golang.org/x/oauth2 have been addressed by upgrading to the patched versions (v0.35.0 and v0.27.0 respectively).

Also applies to: 56-56


1-60: go.mod tidiness verified. Running go mod tidy produced no changes to go.mod or go.sum, confirming the files are clean.

update/checker.go (5)

14-20: LGTM!

The default 24-hour check interval and repository constant are appropriate for update checking functionality.


22-29: LGTM!

The UpdateInfo struct provides a clean public API for update information with all necessary fields.


31-64: LGTM!

The function correctly handles dev versions by treating unparseable versions as 0.0.0, ensuring update checks work in development builds. Error wrapping is consistent throughout.


66-93: Past review concern resolved.

The function now correctly updates only LastCheck and leaves LastNotifiedVersion for the separate MarkUpdateNotified flow. This ensures the first update notification is not suppressed. The comment on line 85 explicitly documents this fix.


95-111: LGTM!

The notification logic correctly determines when to alert users based on version tracking. The default-to-notify behavior on state load errors ensures users don't miss updates.

update/installer.go (2)

12-56: Past review concern resolved.

The function now gracefully handles dev versions by falling back to 0.0.0 (lines 18-20), mirroring the pattern in CheckForUpdates. This allows devx update to work correctly in development builds.


58-66: LGTM!

The function provides a clean, lightweight API for checking update availability without triggering a download.

Comment on lines +113 to +128
// MarkUpdateNotified marks an update version as having been shown to the user
// This should be called after successfully displaying an update notification
func MarkUpdateNotified(latestVersion string) error {
state, err := config.LoadUpdateCheckState()
if err != nil {
// If we can't load state, create a new one
state = &config.UpdateCheckState{}
}

state.LastNotifiedVersion = latestVersion
if err := config.SaveUpdateCheckState(state); err != nil {
return fmt.Errorf("saving notification state: %w", err)
}

return nil
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify callers invoke MarkUpdateNotified after displaying notifications.

The function correctly persists notification state, but ensure all code paths that display update notifications (TUI banner, CLI output) call this function afterward to prevent duplicate notifications.


🏁 Script executed:

#!/bin/bash
# Search for update notification display logic and verify MarkUpdateNotified is called
rg -nC5 --type=go 'MarkUpdateNotified|update.*banner|update.*available' \
  -g '!update/checker.go' -g '!update/checker_test.go'

Length of output: 148


🏁 Script executed:

#!/bin/bash
# Search for calls to MarkUpdateNotified in all Go files except the checker itself
rg -nC5 'MarkUpdateNotified' --glob '*.go' --invert-glob 'update/checker.go'

# Search for “update available” or similar display logic in all Go files
rg -nC5 '(?i)update.*available' --glob '*.go'

Length of output: 12666


Add MarkUpdateNotified calls after displaying update notifications

  • In cmd/root.go, immediately after the fmt.Printf("\n💡 devx %s is available…") on line 135, invoke update.MarkUpdateNotified(info.LatestVersion)
  • In cmd/version.go, immediately after the fmt.Printf("🆙 A newer version is available:…") on line 76, invoke update.MarkUpdateNotified(updateInfo.LatestVersion)
🤖 Prompt for AI Agents
In update/checker.go lines 113-128, the MarkUpdateNotified function exists but
the review requests calling it where notifications are printed: add
update.MarkUpdateNotified(info.LatestVersion) in cmd/root.go immediately after
the fmt.Printf("\n💡 devx %s is available…") call around line 135, and add
update.MarkUpdateNotified(updateInfo.LatestVersion) in cmd/version.go
immediately after the fmt.Printf("🆙 A newer version is available:…") call
around line 76; ensure you import the update package where needed and handle the
returned error (log it or ignore with a comment) so notification state is saved
after the message is shown.

Ensure notification state is saved after showing update message in
'devx version --check-updates' to prevent repeated notifications.

Addresses CodeRabbit review feedback.
Copy link

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

🧹 Nitpick comments (1)
cmd/version.go (1)

58-85: Clean implementation with minor style suggestion.

The update checking logic is well-structured with appropriate error handling and user feedback. The silent failure for MarkUpdateNotified is correctly handled since notification state is non-critical.

Minor style suggestion: Line 83's explicit _ = err assignment is redundant. You can simply not call MarkUpdateNotified without assigning its return value, or remove the blank identifier assignment:

 	// Mark as notified so we don't spam on every command
 	if err := update.MarkUpdateNotified(updateInfo.LatestVersion); err != nil {
 		// Silently ignore - notification state is not critical
-		_ = err
 	}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ae39818 and d14ed9c.

📒 Files selected for processing (1)
  • cmd/version.go (3 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.go: Format all Go code with gofmt before committing
Wrap errors consistently with context throughout the codebase

Files:

  • cmd/version.go
cmd/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

cmd/**/*.go: Place all CLI commands using Cobra under cmd/
When modifying CLI commands, update help text and examples

Files:

  • cmd/version.go
🧬 Code graph analysis (1)
cmd/version.go (1)
update/checker.go (2)
  • CheckForUpdates (32-64)
  • MarkUpdateNotified (115-128)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build
  • GitHub Check: Build
  • GitHub Check: Test Windows
🔇 Additional comments (4)
cmd/version.go (4)

7-7: LGTM!

The import is necessary for the new update checking functionality and is correctly placed.


15-15: LGTM!

The flag variable follows existing patterns and naming conventions.


29-29: LGTM!

Flag registration follows Cobra conventions with clear help text.

As per coding guidelines


50-56: LGTM!

The conditional update check is well-integrated with clear separation from version output.

@jfox85 jfox85 merged commit 2609db3 into main Oct 2, 2025
23 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