Skip to content

feat(cli): add version update notification#2491

Merged
jakobmoellerdev merged 20 commits into
open-component-model:mainfrom
jakobmoellerdev:vk/ce90-versioning-check
May 18, 2026
Merged

feat(cli): add version update notification#2491
jakobmoellerdev merged 20 commits into
open-component-model:mainfrom
jakobmoellerdev:vk/ce90-versioning-check

Conversation

@jakobmoellerdev

@jakobmoellerdev jakobmoellerdev commented May 11, 2026

Copy link
Copy Markdown
Member

Summary

Adds a non-blocking version update notification to the OCM CLI that warns users when a newer stable release is available.

  • Async version check via goroutine + cobra.OnFinalize — no CLI slowdown
  • Compares running binary version against latest cli/v* GitHub release
  • 24h cache in $XDG_CACHE_HOME/ocm/version-check.json
  • Rate-limited warnings via slog.Warn (max once per 24h)
  • Silent failure on network errors / offline / air-gapped environments
  • Only considers stable releases (excludes drafts, pre-releases, non-CLI tags)

Opt-out mechanisms (priority order)

  1. OCM_DISABLE_VERSION_CHECK=1 environment variable
  2. --version-check=disable persistent flag this was cut during review
  3. OCM config file: versioncheck.cli.config.ocm.software/v1alpha1 with disabled: true

Skip conditions

  • Dev builds (BuildVersion == "n/a")
  • The ocm version command itself
  • Any of the above opt-out mechanisms active

Resolves: open-component-model/ocm-project#1044

Test plan

  • Unit tests for versioncheck package (83.6% coverage)
  • Integration tests hitting real GitHub API (6 tests)
  • go build ./... passes
  • go vet ./... passes
  • All existing unit tests pass (no regressions)
  • Manual test with BuildVersion=0.0.1 shows slog warning
  • Manual test with OCM_DISABLE_VERSION_CHECK=1 suppresses warning
  • Manual test offline — no error, no delay

Add non-blocking version check that compares the running binary version
against the latest published GitHub release. Warns to stderr when a newer
stable version is available, with results cached locally for 24h.

Features:
- Async check via goroutine + cobra.OnFinalize (no CLI slowdown)
- 24h cache in XDG_CACHE_HOME/ocm/version-check.json
- Rate-limited warnings (max once per 24h)
- Silent failure on network errors / offline environments
- Opt-out via OCM_DISABLE_VERSION_CHECK=1, --version-check=disable flag,
  or versioncheck.config.ocm.software/v1alpha1 config type
- Skips dev builds (version "n/a") and the version command itself
- Only considers stable cli/* tagged releases (excludes drafts/prereleases)

Resolves: open-component-model/ocm-project#1044

On-behalf-of: @SAP <jakob.moeller@sap.com>
Signed-off-by: Jakob Möller <contact@jakob-moeller.com>
On-behalf-of: @SAP <jakob.moeller@sap.com>
Simplify opt-out mechanism to env var (OCM_DISABLE_VERSION_CHECK) and
--version-check=disable flag only. Config type was over-engineered for
the current use case.

On-behalf-of: @SAP <jakob.moeller@sap.com>
Signed-off-by: Jakob Möller <contact@jakob-moeller.com>
On-behalf-of: @SAP <jakob.moeller@sap.com>
Auto-generated documentation updated to reflect the new
--version-check persistent flag available on all commands.

On-behalf-of: @SAP <jakob.moeller@sap.com>
Signed-off-by: Jakob Möller <contact@jakob-moeller.com>
On-behalf-of: @SAP <jakob.moeller@sap.com>
Reverts the removal of the versioncheck.config.ocm.software/v1alpha1
config type. All three opt-out mechanisms are retained:
- OCM_DISABLE_VERSION_CHECK env var
- --version-check=disable flag
- Config file with disabled: true

On-behalf-of: @SAP <jakob.moeller@sap.com>
Signed-off-by: Jakob Möller <contact@jakob-moeller.com>
On-behalf-of: @SAP <jakob.moeller@sap.com>
…b API

Tests verify:
- Fetching latest version from real GitHub releases
- Update detection with old version (0.0.1)
- No false positive with future version (999.999.999)
- Opt-out via OCM_DISABLE_VERSION_CHECK env var
- Opt-out via --version-check=disable flag
- Cache persistence across calls

On-behalf-of: @SAP <jakob.moeller@sap.com>
Signed-off-by: Jakob Möller <contact@jakob-moeller.com>
On-behalf-of: @SAP <jakob.moeller@sap.com>
Only match the versioned config type since unversioned lookup is not
needed for this configuration.

On-behalf-of: @SAP <jakob.moeller@sap.com>
Signed-off-by: Jakob Möller <contact@jakob-moeller.com>
On-behalf-of: @SAP <jakob.moeller@sap.com>
Add comprehensive documentation:
- doc.go with package-level overview of behavior, release filtering,
  opt-out mechanisms, and cache location
- GoDoc comments on all exported types, functions, and constants
- Inline comments explaining non-obvious implementation details

Also uncomments the config scheme registration which is required
for the config type to be decodable at runtime.

On-behalf-of: @SAP <jakob.moeller@sap.com>
Signed-off-by: Jakob Möller <contact@jakob-moeller.com>
On-behalf-of: @SAP <jakob.moeller@sap.com>
Replace raw fmt.Fprintf to stderr with structured slog.Warn call.
This respects the user's --loglevel, --logformat, and --logoutput
preferences. Users who set --loglevel=error will no longer see
version notifications, which is consistent with opting out of warnings.

On-behalf-of: @SAP <jakob.moeller@sap.com>
Signed-off-by: Jakob Möller <contact@jakob-moeller.com>
On-behalf-of: @SAP <jakob.moeller@sap.com>
@netlify

netlify Bot commented May 11, 2026

Copy link
Copy Markdown

Deploy Preview for ocm-website ready!

Name Link
🔨 Latest commit 43241cf
🔍 Latest deploy log https://app.netlify.com/projects/ocm-website/deploys/6a0aa1023361040008d810c9
😎 Deploy Preview https://deploy-preview-2491--ocm-website.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai

coderabbitai Bot commented May 11, 2026

Copy link
Copy Markdown
Contributor

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

Adds a non-blocking CLI version check that queries GitHub Releases for the latest stable CLI tag, caches results on-disk (24h), rate-limits stderr warnings (24h), supports env/config opt-out, runs asynchronously on command start, and defers printing a structured warning until command finalize.

Changes

CLI Version Check Feature

Layer / File(s) Summary
Version check engine and unit tests
cli/internal/versioncheck/check.go, cli/internal/versioncheck/check_test.go
Implements Check/MarkWarned, Options/Result types, fetchLatestVersion filtering GitHub releases (exclude drafts/prereleases, match cli/v prefix, ignore semver prereleases), semver compare, and unit tests for selection, timeouts, error cases, and cached-result behavior.
On-disk cache implementation and tests
cli/internal/versioncheck/cache.go, cli/internal/versioncheck/cache_test.go
Adds CacheEntry with LatestVersion, CheckedAt, WarnedAt, freshness and warn-suppression logic, OS cache dir and file helpers, Read/Write cache JSON IO, and unit tests for freshness, warn suppression, corrupt/missing reads, and directory creation.
Config schema and generated helpers
cli/internal/versioncheck/config.go, cli/internal/versioncheck/config_test.go, cli/internal/versioncheck/schemas/Config.schema.json, cli/internal/versioncheck/zz_generated.*.go, cli/internal/versioncheck/doc.go
Defines OCM Config type with Policy (auto/disable), registers runtime type, provides LookupConfig() with validation/defaulting, includes JSON Schema and generated DeepCopy/JSONSchema/SetType/GetType helpers, package docs, and tests.
CLI integration and async finalize logic
cli/cmd/setup/versioncheck.go, cli/cmd/setup/hooks/pre_run.go
Adds VersionCheck(cmd, currentVersion) invoked from PreRunEWithConfig; skips when disabled (env/config/version command/unset build version), spawns background versioncheck.Check, registers cobra.OnFinalize to wait briefly and emit structured slog.Warn with current/available version and GitHub releases URL when update available, and marks warning as emitted in cache.
Integration tests
cli/integration/version_check_integration_test.go
End-to-end tests validate GitHub fetching (skippable), UpdateAvailable semantics, CLI version execution with BuildVersion overrides, env var suppression (OCM_DISABLE_VERSION_CHECK), and cache reuse across invocations.

Sequence Diagram

sequenceDiagram
  participant CLI as CLI Command
  participant VC as VersionCheck
  participant C as Cache
  participant GH as GitHub API
  participant F as cobra.OnFinalize
  participant S as slog.Warn

  CLI->>VC: VersionCheck(cmd, BuildVersion) (start background goroutine)
  VC->>C: ReadCache(cacheDir)
  alt cache fresh
    C-->>VC: cached entry (no network)
  else cache stale/missing
    VC->>GH: GET /repos/:owner/:repo/releases
    GH-->>VC: releases JSON
    VC->>C: WriteCache(updated entry)
  end
  CLI->>F: command completes, finalize triggers
  F->>VC: wait for result (short timeout)
  alt UpdateAvailable
    VC->>S: slog.Warn(current, available, releases URL)
    VC->>C: MarkWarned(cacheDir)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested labels

size/m

Suggested reviewers

  • fabianburth
  • frewilhelm
  • Skarlso

Poem

🐰 I hopped through tags beneath the log,
Found newer builds hiding in GitHub's fog.
I cached my find for a day of rest,
Whispered once at finish — then gave my best request.
Update or opt-out — the rabbit nods with zest.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.41% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'feat(cli): add version update notification' directly and concisely summarizes the main feature added: a version update notification system for the CLI.
Description check ✅ Passed The PR description is comprehensive and clearly related to the changeset, detailing the implementation approach, features, opt-out mechanisms, and test coverage.
Linked Issues check ✅ Passed All coding objectives from issue #1044 are met: version check implemented with caching [#1044], opt-out via environment variable and config [#1044], comprehensive unit and integration tests [#1044], and silent failure handling [#1044].
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing the version check feature: new versioncheck package, CLI integration, configuration support, tests, and documentation.

✏️ 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 and usage tips.

@github-actions github-actions Bot added kind/feature new feature, enhancement, improvement, extension size/l Large labels May 11, 2026
- Regenerate Config.schema.json with updated descriptions from doc comments
- Use 0o600 instead of 0o644 for cache file (gosec G306)

On-behalf-of: @SAP <jakob.moeller@sap.com>
Signed-off-by: Jakob Möller <contact@jakob-moeller.com>
On-behalf-of: @SAP <jakob.moeller@sap.com>
…ck config

Change the config from `disabled: true` to `policy: disable|auto`.
This aligns with the CLI flag pattern and enables a layered override model:

  1. Environment variable (OCM_DISABLE_VERSION_CHECK) — hard override
  2. CLI flag (--version-check=auto|disable) — overrides config when explicitly set
  3. Config file policy — sets the default baseline

This allows administrators to disable version checks org-wide via config
while individual users can re-enable with --version-check=auto.

On-behalf-of: @SAP <jakob.moeller@sap.com>
Signed-off-by: Jakob Möller <contact@jakob-moeller.com>
On-behalf-of: @SAP <jakob.moeller@sap.com>
On-behalf-of: @SAP <jakob.moeller@sap.com>
Signed-off-by: Jakob Möller <contact@jakob-moeller.com>
On-behalf-of: @SAP <jakob.moeller@sap.com>
Replace `_ = WriteCache(...)` with proper error logging via slog.Debug.
If cache writes fail consistently, users would get network calls on every
invocation — logging makes this diagnosable.

ReadCache errors are still discarded intentionally (cache miss on first
run is expected flow, not an error condition).

On-behalf-of: @SAP <jakob.moeller@sap.com>
Signed-off-by: Jakob Möller <contact@jakob-moeller.com>
On-behalf-of: @SAP <jakob.moeller@sap.com>
@jakobmoellerdev jakobmoellerdev marked this pull request as ready for review May 12, 2026 06:30
@jakobmoellerdev jakobmoellerdev requested a review from a team as a code owner May 12, 2026 06:30
Comment thread cli/docs/reference/ocm.md Outdated
Remove the --version-check persistent flag from the root command.
Opt-out is now handled exclusively via:
  1. OCM_DISABLE_VERSION_CHECK environment variable
  2. OCM config file with policy: disable

The flag polluted every subcommand's help output unnecessarily.
Config-based control is sufficient for persistent preferences.

Regenerated command reference documentation.

On-behalf-of: @SAP <jakob.moeller@sap.com>
Signed-off-by: Jakob Möller <contact@jakob-moeller.com>
On-behalf-of: @SAP <jakob.moeller@sap.com>

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

🧹 Nitpick comments (5)
cli/integration/version_check_integration_test.go (1)

41-60: ⚡ Quick win

Test does not verify its stated purpose.

The test name Test_Integration_VersionCheck_PrintsWarningToStderr implies it verifies that a warning is printed, but the comment on lines 57-59 acknowledges that "the warning may or may not appear depending on timing" and the test only verifies that Execute() doesn't error.

Either:

  1. Fix the test to reliably verify the warning appears (e.g., add a small sleep or synchronization mechanism after Execute() completes and before checking stderr)
  2. Rename the test to reflect what it actually verifies (e.g., Test_Integration_VersionCheck_DoesNotErrorWithOldVersion)
🤖 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 `@cli/integration/version_check_integration_test.go` around lines 41 - 60, The
test Test_Integration_VersionCheck_PrintsWarningToStderr currently sets
version.BuildVersion and captures stderr but never asserts that a warning was
printed (it only checks rootCmd.Execute() succeeds), so either make the test
deterministic by waiting for the async version check to complete and then assert
stderr contains the expected warning, or rename the test to reflect current
behavior; to fix, either add synchronization (e.g., have the version check
signal completion or poll/wait briefly after rootCmd.Execute() before reading
stderr) and assert stderr.String() includes the warning text, or change the test
name to something like Test_Integration_VersionCheck_DoesNotErrorWithOldVersion
so it matches the existing assertion that rootCmd.Execute() returns no error
(use the existing symbols Test_Integration_VersionCheck_PrintsWarningToStderr,
rootCmd.Execute, stderr, and version.BuildVersion to locate and update the
test).
cli/internal/versioncheck/cache_test.go (2)

10-45: ⚡ Quick win

Consider using fixed timestamps for deterministic tests.

The test uses time.Now() both when constructing test cases and when calling IsFresh(). If the system clock advances between these calls (even by microseconds), the test assertions might occasionally fail or pass unexpectedly.

♻️ Proposed fix using fixed timestamps
 func TestCacheEntry_IsFresh(t *testing.T) {
+	now := time.Date(2026, 5, 12, 12, 0, 0, 0, time.UTC)
 	tests := []struct {
 		name      string
 		checkedAt time.Time
-		now       time.Time
 		want      bool
 	}{
 		{
 			name:      "fresh cache",
-			checkedAt: time.Now().Add(-1 * time.Hour),
-			now:       time.Now(),
+			checkedAt: now.Add(-1 * time.Hour),
 			want:      true,
 		},
 		{
 			name:      "stale cache",
-			checkedAt: time.Now().Add(-25 * time.Hour),
-			now:       time.Now(),
+			checkedAt: now.Add(-25 * time.Hour),
 			want:      false,
 		},
 		{
 			name:      "exactly at boundary",
-			checkedAt: time.Now().Add(-24 * time.Hour),
-			now:       time.Now(),
+			checkedAt: now.Add(-24 * time.Hour),
 			want:      false,
 		},
 	}
 
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			entry := &CacheEntry{CheckedAt: tt.checkedAt}
-			if got := entry.IsFresh(tt.now); got != tt.want {
+			if got := entry.IsFresh(now); got != tt.want {
 				t.Errorf("IsFresh() = %v, want %v", got, tt.want)
 			}
 		})
 	}
 }
🤖 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 `@cli/internal/versioncheck/cache_test.go` around lines 10 - 45, The test
TestCacheEntry_IsFresh is non-deterministic because it calls time.Now() multiple
times; replace those dynamic calls with a single fixed base time (e.g. base :=
time.Date(...)) and build each test case's checkedAt and now relative to that
base so CacheEntry{CheckedAt: ...} and entry.IsFresh(now) use deterministic
timestamps; update the three cases (fresh, stale, exactly at boundary) to use
base.Add(...) offsets to preserve the intended expectations for
CacheEntry.IsFresh.

47-82: ⚡ Quick win

Consider using fixed timestamps for deterministic tests.

Similar to TestCacheEntry_IsFresh, this test uses time.Now() in test case construction, which could lead to flaky behavior.

♻️ Proposed fix using fixed timestamps
 func TestCacheEntry_ShouldWarn(t *testing.T) {
+	now := time.Date(2026, 5, 12, 12, 0, 0, 0, time.UTC)
 	tests := []struct {
 		name    string
 		warnedAt time.Time
-		now      time.Time
 		want     bool
 	}{
 		{
 			name:     "never warned",
 			warnedAt: time.Time{},
-			now:      time.Now(),
 			want:     true,
 		},
 		{
 			name:     "warned recently",
-			warnedAt: time.Now().Add(-1 * time.Hour),
-			now:      time.Now(),
+			warnedAt: now.Add(-1 * time.Hour),
 			want:     false,
 		},
 		{
 			name:     "warned long ago",
-			warnedAt: time.Now().Add(-25 * time.Hour),
-			now:      time.Now(),
+			warnedAt: now.Add(-25 * time.Hour),
 			want:     true,
 		},
 	}
 
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			entry := &CacheEntry{WarnedAt: tt.warnedAt}
-			if got := entry.ShouldWarn(tt.now); got != tt.want {
+			if got := entry.ShouldWarn(now); got != tt.want {
 				t.Errorf("ShouldWarn() = %v, want %v", got, tt.want)
 			}
 		})
 	}
 }
🤖 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 `@cli/internal/versioncheck/cache_test.go` around lines 47 - 82, The test
TestCacheEntry_ShouldWarn is using time.Now() which makes it flaky; update the
test to use fixed deterministic timestamps (e.g., base := time.Date(2023, 1, 1,
12, 0, 0, 0, time.UTC)) and create warnedAt and now values relative to that base
(e.g., base, base.Add(-1*time.Hour), base.Add(-25*time.Hour)) when constructing
the test cases, then use those values to initialize CacheEntry{WarnedAt: ...}
and pass now into entry.ShouldWarn so the expectations are stable.
cli/internal/versioncheck/check.go (2)

160-220: ⚖️ Poor tradeoff

Consider documenting the single-page limitation.

The implementation fetches only the first page of releases (20 items). If more than 20 releases have been published since the last stable CLI release, the version check will fail to find an update.

While this is unlikely given the PR description notes that the latest stable release is "typically within the first page," adding a code comment near line 163 could help future maintainers understand this design choice:

 func fetchLatestVersion(ctx context.Context, opts Options) (string, error) {
+	// Note: We fetch only the first page of releases. If the latest stable CLI
+	// release is beyond the first 20 releases, it will not be detected.
 	url := fmt.Sprintf("%s/repos/%s/%s/releases?per_page=%d",
 		opts.BaseURL, opts.GitHubOwner, opts.GitHubRepo, releasesPerPage)
🤖 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 `@cli/internal/versioncheck/check.go` around lines 160 - 220, In
fetchLatestVersion, add a concise comment near the URL/release-per-page
construction (around the releasesPerPage usage) documenting that the function
intentionally only requests the first page of GitHub releases
(per_page=releasesPerPage) and therefore may miss updates if more than that many
releases exist; mention the chosen page-size (releasesPerPage) and the
rationale/assumption (latest stable release is typically on page one) and note
that paging could be added later if needed so future maintainers understand this
limitation.

99-103: 💤 Low value

Consider logging cache read errors for debugging.

The code silently ignores cache read errors. While this aligns with the "silent failure" requirement, distinguishing between "cache not found" (expected on first run) and "permission denied" or "corrupted cache" (unexpected issues) could help users debug cache problems.

♻️ Optional: Add debug logging for cache read errors
 	// Use cached result if the last check was recent enough.
-	cache, _ := ReadCache(cacheDir)
+	cache, err := ReadCache(cacheDir)
+	if err != nil && !os.IsNotExist(err) {
+		slog.Debug("version check: cache read failed", slog.String("error", err.Error()))
+	}
 	if cache != nil && cache.IsFresh(now) {
 		return compareVersions(current, cache.LatestVersion)
 	}
🤖 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 `@cli/internal/versioncheck/check.go` around lines 99 - 103, The ReadCache call
currently ignores its error, so permission or corruption issues are silently
lost; update the logic around ReadCache(cacheDir) in check.go to capture the
returned error, and when an error is non-nil, log or debug-log it (distinguish
expected "not found" vs unexpected errors) before proceeding with the existing
fallback; keep the existing behavior of using cache only when cache != nil &&
cache.IsFresh(now) and then calling compareVersions(current,
cache.LatestVersion).
🤖 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 `@cli/integration/version_check_integration_test.go`:
- Around line 14-26: The integration test
Test_Integration_VersionCheck_FetchesLatestFromGitHub directly calls the live
GitHub API and runs in parallel, so add a short-mode skip at the start of the
test to avoid flaky CI runs: check testing.Short() and call t.Skip("skipping
GitHub API integration test in short mode") when true; apply the same pattern to
the other integration tests referenced in this file (the other functions around
the indicated ranges) to ensure live-GitHub tests are skipped during
short/test-fast runs while keeping the rest of the test logic
(versioncheck.Check, options, assertions) unchanged.

In `@cli/internal/versioncheck/config.go`:
- Around line 68-76: The loop decodes each entry into Config but only checks for
non-empty Policy and can return invalid values; update the block after
configScheme.Convert to validate that config.Policy is exactly one of the
allowed constants (PolicyAuto or PolicyDisable) and if not return an error (e.g.
fmt.Errorf("invalid versioncheck policy: %q", config.Policy)); keep the existing
non-empty check but replace the unconditional return of &config with this
validation so only permitted policy values are accepted.

---

Nitpick comments:
In `@cli/integration/version_check_integration_test.go`:
- Around line 41-60: The test
Test_Integration_VersionCheck_PrintsWarningToStderr currently sets
version.BuildVersion and captures stderr but never asserts that a warning was
printed (it only checks rootCmd.Execute() succeeds), so either make the test
deterministic by waiting for the async version check to complete and then assert
stderr contains the expected warning, or rename the test to reflect current
behavior; to fix, either add synchronization (e.g., have the version check
signal completion or poll/wait briefly after rootCmd.Execute() before reading
stderr) and assert stderr.String() includes the warning text, or change the test
name to something like Test_Integration_VersionCheck_DoesNotErrorWithOldVersion
so it matches the existing assertion that rootCmd.Execute() returns no error
(use the existing symbols Test_Integration_VersionCheck_PrintsWarningToStderr,
rootCmd.Execute, stderr, and version.BuildVersion to locate and update the
test).

In `@cli/internal/versioncheck/cache_test.go`:
- Around line 10-45: The test TestCacheEntry_IsFresh is non-deterministic
because it calls time.Now() multiple times; replace those dynamic calls with a
single fixed base time (e.g. base := time.Date(...)) and build each test case's
checkedAt and now relative to that base so CacheEntry{CheckedAt: ...} and
entry.IsFresh(now) use deterministic timestamps; update the three cases (fresh,
stale, exactly at boundary) to use base.Add(...) offsets to preserve the
intended expectations for CacheEntry.IsFresh.
- Around line 47-82: The test TestCacheEntry_ShouldWarn is using time.Now()
which makes it flaky; update the test to use fixed deterministic timestamps
(e.g., base := time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)) and create warnedAt
and now values relative to that base (e.g., base, base.Add(-1*time.Hour),
base.Add(-25*time.Hour)) when constructing the test cases, then use those values
to initialize CacheEntry{WarnedAt: ...} and pass now into entry.ShouldWarn so
the expectations are stable.

In `@cli/internal/versioncheck/check.go`:
- Around line 160-220: In fetchLatestVersion, add a concise comment near the
URL/release-per-page construction (around the releasesPerPage usage) documenting
that the function intentionally only requests the first page of GitHub releases
(per_page=releasesPerPage) and therefore may miss updates if more than that many
releases exist; mention the chosen page-size (releasesPerPage) and the
rationale/assumption (latest stable release is typically on page one) and note
that paging could be added later if needed so future maintainers understand this
limitation.
- Around line 99-103: The ReadCache call currently ignores its error, so
permission or corruption issues are silently lost; update the logic around
ReadCache(cacheDir) in check.go to capture the returned error, and when an error
is non-nil, log or debug-log it (distinguish expected "not found" vs unexpected
errors) before proceeding with the existing fallback; keep the existing behavior
of using cache only when cache != nil && cache.IsFresh(now) and then calling
compareVersions(current, cache.LatestVersion).
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 84cd3cad-2d6b-4c96-86a8-798909d11a0f

📥 Commits

Reviewing files that changed from the base of the PR and between f2715fd and 634ac3d.

📒 Files selected for processing (14)
  • cli/cmd/setup/hooks/pre_run.go
  • cli/cmd/setup/versioncheck.go
  • cli/integration/version_check_integration_test.go
  • cli/internal/versioncheck/cache.go
  • cli/internal/versioncheck/cache_test.go
  • cli/internal/versioncheck/check.go
  • cli/internal/versioncheck/check_test.go
  • cli/internal/versioncheck/config.go
  • cli/internal/versioncheck/config_test.go
  • cli/internal/versioncheck/doc.go
  • cli/internal/versioncheck/schemas/Config.schema.json
  • cli/internal/versioncheck/zz_generated.deepcopy.go
  • cli/internal/versioncheck/zz_generated.ocm_jsonschema.go
  • cli/internal/versioncheck/zz_generated.ocm_type.go

Comment thread cli/integration/version_check_integration_test.go
Comment thread cli/internal/versioncheck/config.go
- Add runtime validation for policy values in config (reject invalid)
- Add testing.Short() skip for integration tests hitting GitHub API
- Use fixed timestamps in cache tests for determinism
- Rename misleading test to reflect actual behavior
- Log non-NotExist cache read errors via slog.Debug

On-behalf-of: @SAP <jakob.moeller@sap.com>
Signed-off-by: Jakob Möller <contact@jakob-moeller.com>
On-behalf-of: @SAP <jakob.moeller@sap.com>

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

Nits. Good work.

Comment thread cli/cmd/setup/versioncheck.go
Comment thread cli/cmd/setup/versioncheck.go Outdated
Comment thread cli/internal/versioncheck/check.go
- Use strconv.ParseBool for env var parsing (supports all Go bool formats)
- Rename printWarning → printUpgradeWarning for clarity
- Add comment explaining why draft/prerelease filtering is client-side
  (GitHub list API has no server-side filter for these fields)

On-behalf-of: @SAP <jakob.moeller@sap.com>
Signed-off-by: Jakob Möller <contact@jakob-moeller.com>
On-behalf-of: @SAP <jakob.moeller@sap.com>

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

🧹 Nitpick comments (1)
cli/integration/version_check_integration_test.go (1)

47-61: 💤 Low value

Mutating version.BuildVersion (a package global) inside a t.Parallel() test is racy.

version.BuildVersion is a process-wide variable. If any other parallel test in this package (now or later) reads or writes it, the writes here race with those reads under -race. The current siblings happen to be safe because Test_Integration_VersionCheck_DisabledByEnvVar calls t.Setenv (which forces serial execution), but this is fragile — adding any future parallel test that touches BuildVersion (or any cmd path that reads it) reintroduces the race.

Two safer options:

  • Drop t.Parallel() from tests that mutate the global, or
  • Plumb the version via an option/parameter instead of mutating the package var.
🤖 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 `@cli/integration/version_check_integration_test.go` around lines 47 - 61,
Test_Integration_VersionCheck_DoesNotErrorWithOldVersion mutates the
process-global version.BuildVersion while calling t.Parallel(), which creates a
data race; remove the t.Parallel() call from this test so the global mutation
and cleanup are executed serially, or alternatively refactor the code path
(e.g., cmd.New or the command execution) to accept an injected version value
instead of reading version.BuildVersion so the test can pass a custom version
without touching the package global; update
Test_Integration_VersionCheck_DoesNotErrorWithOldVersion to use the chosen
approach and keep the existing cleanup that restores version.BuildVersion if you
keep the global mutation.
🤖 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 `@cli/cmd/setup/versioncheck.go`:
- Around line 99-112: The G706 false positive comes from logging the env var
value in envDisabled; update envDisabled to avoid logging the raw
user-controlled value by removing the slog.String("value", val) attribute from
the slog.Debug call (keep slog.String("var", VersionCheckEnvVar) and the
message) or alternatively add an inline suppression comment on the slog.Debug
line like //nolint:gosec // G706 false positive: slog.String is structured and
not a format directive; reference symbols: envDisabled, VersionCheckEnvVar, val,
slog.String.

In `@cli/integration/version_check_integration_test.go`:
- Around line 47-80: The tests use "--help" which short-circuits before
PersistentPreRunE so setup.VersionCheck inside PreRunEWithConfig() never runs;
update both Test_Integration_VersionCheck_DoesNotErrorWithOldVersion and
Test_Integration_VersionCheck_DisabledByEnvVar to call a real subcommand (for
example set rootCmd.SetArgs([]string{"version"}) or another subcommand that
triggers PersistentPreRunE) instead of "--help" so PreRunEWithConfig() and
setup.VersionCheck are executed, or alternatively attach a capturing slog
handler to the logger and directly invoke PreRunEWithConfig()/setup.VersionCheck
to assert the version-check behavior and stderr output; ensure you still mutate
version.BuildVersion and the env var OCM_DISABLE_VERSION_CHECK as the tests
expect.

---

Nitpick comments:
In `@cli/integration/version_check_integration_test.go`:
- Around line 47-61: Test_Integration_VersionCheck_DoesNotErrorWithOldVersion
mutates the process-global version.BuildVersion while calling t.Parallel(),
which creates a data race; remove the t.Parallel() call from this test so the
global mutation and cleanup are executed serially, or alternatively refactor the
code path (e.g., cmd.New or the command execution) to accept an injected version
value instead of reading version.BuildVersion so the test can pass a custom
version without touching the package global; update
Test_Integration_VersionCheck_DoesNotErrorWithOldVersion to use the chosen
approach and keep the existing cleanup that restores version.BuildVersion if you
keep the global mutation.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bd39a6af-841d-42d8-9f55-36af5f4c7a87

📥 Commits

Reviewing files that changed from the base of the PR and between 634ac3d and 7346fea.

📒 Files selected for processing (6)
  • cli/cmd/setup/versioncheck.go
  • cli/integration/version_check_integration_test.go
  • cli/internal/versioncheck/cache_test.go
  • cli/internal/versioncheck/check.go
  • cli/internal/versioncheck/config.go
  • cli/internal/versioncheck/config_test.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • cli/internal/versioncheck/config_test.go
  • cli/internal/versioncheck/cache_test.go
  • cli/internal/versioncheck/config.go
  • cli/internal/versioncheck/check.go

Comment thread cli/cmd/setup/versioncheck.go
Comment thread cli/integration/version_check_integration_test.go
- Replace slog.String("value", val) with slog.String("error", err.Error())
  to avoid gosec G706 log injection false positive
- Fix integration tests to use "version" subcommand with empty config
  instead of --help (which skips PersistentPreRunE entirely)
- Pass --config with empty YAML to avoid loading user's local OCM config
  which may reference unavailable plugins

On-behalf-of: @SAP <jakob.moeller@sap.com>
Signed-off-by: Jakob Möller <contact@jakob-moeller.com>
On-behalf-of: @SAP <jakob.moeller@sap.com>
Remove the error message from slog.Debug call since gosec considers
strconv.ParseBool error output as tainted (derived from user input).
Invalid env var values now silently treated as "disabled" for safety.

On-behalf-of: @SAP <jakob.moeller@sap.com>
Signed-off-by: Jakob Möller <contact@jakob-moeller.com>
On-behalf-of: @SAP <jakob.moeller@sap.com>

@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: 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 `@cli/integration/version_check_integration_test.go`:
- Around line 49-66: The tests (e.g.,
Test_Integration_VersionCheck_DoesNotErrorWithOldVersion and the similar test
covering lines 68-84) are leaking the real XDG cache; before calling
rootCmd.ExecuteContext(t.Context()) set the XDG_CACHE_HOME environment variable
to a temporary dir (use t.TempDir()) and restore it via t.Cleanup or defer, so
the version-check cache is isolated to the test workspace; ensure this is done
in both tests and, after execution, the env-based case can assert no real cache
file was written.
- Around line 96-113: The test currently only compares LatestVersion between two
versioncheck.Check calls which doesn't prove the cache was used; modify the test
around versioncheck.Check and versioncheck.ReadCache to capture the
cache.CheckedAt value after the first call (e.g., firstCheckedAt :=
cache.CheckedAt) and then call versioncheck.Check a second time and re-read the
cache and assert that cache.CheckedAt equals firstCheckedAt to ensure the second
call did not update the cache timestamp; reference versioncheck.Check,
versioncheck.ReadCache, cache.LatestVersion and cache.CheckedAt when adding the
assertion.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a0a14398-00e7-41cb-a958-e8897f4ac3fa

📥 Commits

Reviewing files that changed from the base of the PR and between 7346fea and 454cb08.

📒 Files selected for processing (2)
  • cli/cmd/setup/versioncheck.go
  • cli/integration/version_check_integration_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • cli/cmd/setup/versioncheck.go

Comment thread cli/integration/version_check_integration_test.go
Comment thread cli/integration/version_check_integration_test.go
Comment thread cli/internal/versioncheck/config.go
Comment thread cli/internal/versioncheck/check.go
Comment thread cli/internal/versioncheck/check.go
- Cache fetch failures with CheckedAt set so offline/firewalled users
  don't face network delays on every CLI invocation (respects 24h TTL)
- Log fetch errors via slog.Debug for debuggability
- Remove t.Parallel() from tests that mutate version.BuildVersion global
- Isolate XDG_CACHE_HOME in integration tests to prevent cache leakage
- Assert cache.CheckedAt unchanged on second call to prove cache hit

On-behalf-of: @SAP <jakob.moeller@sap.com>
Signed-off-by: Jakob Möller <contact@jakob-moeller.com>
On-behalf-of: @SAP <jakob.moeller@sap.com>
Comment thread cli/internal/versioncheck/config.go Outdated
@jakobmoellerdev jakobmoellerdev force-pushed the vk/ce90-versioning-check branch from bf0cca7 to 135320a Compare May 13, 2026 08:09
The CLI flag was dropped but the Config doc comment still referenced it.
Updated to accurately describe the config-only opt-out mechanism.

On-behalf-of: @SAP <jakob.moeller@sap.com>
Signed-off-by: Jakob Möller <contact@jakob-moeller.com>
On-behalf-of: @SAP <jakob.moeller@sap.com>
Signed-off-by: Jakob Möller <contact@jakob-moeller.com>
On-behalf-of: @SAP <jakob.moeller@sap.com>
Signed-off-by: Jakob Möller <contact@jakob-moeller.com>
On-behalf-of: @SAP <jakob.moeller@sap.com>
Comment thread cli/cmd/setup/hooks/pre_run.go
@jakobmoellerdev jakobmoellerdev enabled auto-merge (squash) May 18, 2026 05:18
@jakobmoellerdev jakobmoellerdev merged commit a5cee67 into open-component-model:main May 18, 2026
23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kind/feature new feature, enhancement, improvement, extension size/l Large

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add version update notification to OCM CLI

5 participants