feat(update): update_check config block + dismissible Web UI update banner (Spec 079 US1)#805
Conversation
…nce (Spec 079 US1)
Add the update_check.{enabled,channel} config group (FR-012/FR-013):
- enabled (default true) gates BOTH the background poll and the manual
CheckNow (/api/v1/info?refresh=true) path; when disabled no network
check runs and GetVersionInfo returns nil so /api/v1/info omits the
update object entirely — every surface (banner, badge, status/doctor
annotation) goes quiet (FR-015).
- channel: "stable" (default; prereleases never offered) or "rc"
(prereleases included), validated; the config-file equivalent of
MCPPROXY_ALLOW_PRERELEASE_UPDATES.
- Precedence (FR-014): the existing env switches WIN over config —
MCPPROXY_DISABLE_AUTO_UPDATE=true force-disables and
MCPPROXY_ALLOW_PRERELEASE_UPDATES=true force-includes prereleases.
The spec leaves precedence open; env-wins is the operator-override
reading, documented in code and docs.
- Hot-reload: DetectConfigChanges reports "update_check"; ApplyConfig
(API path) and ReloadConfiguration (disk path) both re-gate the
running checker. The background loop now stays alive while
config-disabled, and a re-enable/channel switch triggers a prompt
re-check instead of waiting up to the 4h interval.
- make swagger: config.UpdateCheckConfig surfaced on config.Config.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ec 079 US1) FR-005: non-modal alert banner on the dashboard when the checker reports an update — "Update available: vX — you are running vY" with a release-notes link and a dismiss (X) button. Dismissal persists the dismissed latest_version in localStorage, so the same version never re-nags across reloads while a newer release shows the banner again. The existing sidebar badge + manual-check toast are unchanged. When update_check.enabled=false the daemon omits the update object from /api/v1/info, so the banner (and badge) are naturally absent; the manual "check for updates" action now says checks are disabled instead of the misleading "You are running the latest version". Tests: 6 vitest cases (render, no-update, absent update object, dismiss persists, stays dismissed on remount, newer version reappears). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… and the Web UI banner (Spec 079 US1)
- features/version-updates.md: update_check.{enabled,channel} reference,
hot-reload behavior, explicit env-vs-config precedence (env wins, one
direction only), config-based examples, dismissible per-version banner.
- configuration.md + configuration/config-file.md (served reference):
new Update Check sections + complete-reference key.
- configuration/environment-variables.md: auto-update table is core+tray
(not tray-only) and points at the config-file equivalent.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e nudge/cache Address Codex review findings on the Spec 079 US1 branch: - Go tray self-update (major): the tray's independent daily release check (checkForUpdates) now reads update_check.enabled from the shared config file and skips the network check when disabled (FR-015: no check on any surface). Fails open on missing/unreadable config, matching the pre-079 default; MCPPROXY_DISABLE_AUTO_UPDATE still wins (FR-014). Full FR-001a convergence (tray consuming the shared checker, incl. channel) remains a separate 079 work item — docs updated to say exactly that. - Tray stale nudge (minor): checkUpdateFromAPI treats an absent update object in /api/v1/info (checker disabled via hot-reload) as "no update", clearing state and hiding the menu item instead of returning early and leaving a stale "New version available" entry until restart. - Checker races (minor): SetConfig now bumps a config generation and drops the cached VersionInfo on any effective change; check() captures the generation and updateVersionInfo discards results from a stale generation or while disabled. An in-flight check can no longer publish/announce after disable, and a channel switch or re-enable never briefly serves wrong-channel cached info (FR-013/FR-015). - UpdateBanner (minor): localStorage reads/writes wrapped in try/catch, degrading to session-only dismissal when storage is blocked (precedent: stores/system.ts), so blocked storage cannot break Dashboard setup. Tests: new unit tests for the tray config gate, the stale-nudge clear (httptest core stub), the checker generation/disable races, and a blocked-localStorage banner spec. go test -race, vitest (253 pass), golangci-lint v2, and make build all green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- rest-api.md: /api/v1/info omits the update object when checking is disabled (update_check.enabled=false or MCPPROXY_DISABLE_AUTO_UPDATE); note refresh=true no-ops while disabled and point at the update_check config block. - cli/status-command.md: document the disabled case (no update object, version shown without annotation). - prerelease-builds.md: RC opt-in now also possible via update_check.channel=rc for the core checker (Go tray self-update check stays env-only until FR-001a). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Deploying mcpproxy-docs with
|
| Latest commit: |
fe1d102
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://42b98f4f.mcpproxy-docs.pages.dev |
| Branch Preview URL: | https://feat-079-banner-config.mcpproxy-docs.pages.dev |
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
📦 Build ArtifactsWorkflow Run: View Run Available Artifacts
How to DownloadOption 1: GitHub Web UI (easiest)
Option 2: GitHub CLI gh run download 28641746072 --repo smart-mcp-proxy/mcpproxy-go
|
…bled Codex final-review findings on PR #805 (Spec 079 US1): - Major: ServerAdapter.GetConfigPath() hardcoded ~/.mcpproxy/mcp_config.json, ignoring MCPPROXY_TRAY_CONFIG_PATH — the very path the tray launches core with as --config (buildCoreArgs). So the update_check gate read the wrong file under a custom config path and failed open, letting the tray-owned daily GitHub check run despite update_check.enabled=false. Resolve the env override first, matching what core actually uses. - Minor: check() captured cfgGen but not the enabled flag, so a disable racing after the caller's outer Enabled() gate (loop tick / re-enable goroutine) still issued a GitHub request. The generation guard dropped the result, but the request fired — violating FR-015 "no network check on any surface". Re-read enabled under the same lock as gen and bail before checkFunc. Both covered by new tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codex final review — verdict: REQUEST_CHANGES → resolvedRan a read-only Codex ( OpenAPI regen integrity (focus a): clean. Findings (both confirmed real, both fixed)
Both covered by new tests ( Verification
Other reviewed areas (config tri-state + nil-safe accessors + channel validation, SetConfig generation-guard, env-narrows precedence, RWMutex non-reentrancy via Net verdict after fixes: APPROVE. |
The tray must interact with the core only over the socket/REST API and hold no state (CLAUDE.md). The previous update-check gate violated this by calling config.LoadFromFile on the core's mcp_config.json. Replace updateCheckEnabledByConfig with fetchCoreUpdateInfo, which asks the core via GET /api/v1/info (the same endpoint checkUpdateFromAPI uses). The core omits the update object when update_check.enabled=false, making its config the single source of truth: - core reports an update object -> run the legacy GitHub self-update flow - core omits the update object -> skip (checking disabled / no update) - core unreachable -> skip this tick (do not fall open to a network check the operator may have disabled); the 24h ticker retries checkUpdateFromAPI now shares fetchCoreUpdateInfo. File-based gate tests are replaced with API-based ones asserting the network path (injected selfUpdateFunc) runs only when the core reports an update. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Architecture correction: tray self-update gate now uses the core API, not the config filePer maintainer review — the tray must interact with the core only over the socket/REST API and hold no state (CLAUDE.md: "the tray holds no state; it reads/writes core config via REST + SSE"). The previous update-check gate violated this by calling What changed (
The core's Tests replaced with API-based ones (core stub via Pre-existing violation flagged (not fixed here — out of scope): the OAuth login path at |
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Motivation
Spec 079 (
specs/079-upgrade-nudge) US1: make version updates visible and controllable in the personal edition. This PR delivers the US1 remainder — a user-facing update banner in the Web UI plus a first-classupdate_checkconfig block (FR-005, FR-012–FR-015) so update checking can be disabled or pointed at the RC channel from config, not just env vars. Roadmap: personal-edition polish initiative (upgrade-nudge task).Implementation
update_checkconfig block (internal/config):{ "enabled": bool (default true), "channel": "stable"|"rc" }with nil-safe accessors and validation.enabled=falsegates both the background poll and manualCheckNow(/api/v1/info?refresh=truebecomes a no-op — no network call), andGetVersionInfo()returns nil so/api/v1/infoomits theupdateobject entirely. Every surface (banner, sidebar badge,status/doctorannotations) goes quiet (FR-015).channel:"rc"is the config-file equivalent ofMCPPROXY_ALLOW_PRERELEASE_UPDATES; resolved per-check so a stable⇄rc switch applies immediately (FR-013).MCPPROXY_DISABLE_AUTO_UPDATE=trueforce-disables,MCPPROXY_ALLOW_PRERELEASE_UPDATES=trueforce-includes prereleases; env cannot re-enable config-disabled checking.DetectConfigChangesreportsupdate_check; both reload paths (runtimeApplyConfigAPI path and lifecycleReloadConfigurationdisk path) re-gate the running checker. The background loop stays alive while disabled; re-enable/channel switch triggers a prompt re-check instead of waiting up to the 4h interval.UpdateBanner.vueon the Dashboard — "Update available: vX — you are running vY" with release-notes link. Dismissal persists the dismissedlatest_versionin localStorage (same version never re-nags; a newer release shows again). Manual "check for updates" reports checks-disabled instead of a misleading "you are running the latest version".make swagger:config.UpdateCheckConfigsurfaced inoas/.Codex review outcome — applied
All findings applied in
411c69b2(none rebutted):update_check.enabledfrom the shared config file and skips the network check when disabled (FR-015 across all surfaces). Fails open on missing/unreadable config;MCPPROXY_DISABLE_AUTO_UPDATEstill wins. Full FR-001a tray convergence (shared checker incl. channel) is a separate 079 work item — docs say exactly that.updateobject in/api/v1/infoas "no update" (clears stale nudge after hot-reload disable instead of leaving it until restart).SetConfigbumps a config generation and drops cachedVersionInfo; in-flight checks from a stale generation are discarded, so a disable/channel-switch race can never publish wrong-channel or post-disable results.UpdateBannerlocalStorage access wrapped in try/catch — degrades to session-only dismissal when storage is blocked.Live verification (worktree, isolated instance)
go build -ldflags "-X main.version=v0.40.0 ..."→mcpproxy --version=MCPProxy v0.40.0 (personal) darwin/arm64;make frontend-buildregeneratedweb/frontend/dist.127.0.0.1:18095:/api/v1/inforeturned theupdateobject with the real latest release; Dashboard rendered the banner, dismiss persisted across reload, sidebar badge unchanged.update_check.enabled=falseand hot-reloaded:updateobject omitted from/api/v1/info, banner/badge disappeared,?refresh=trueno-op (no outbound check); re-enable triggered a prompt re-check.go test -race ./internal/..., vitest (253 pass),golangci-lintv2 (.github/.golangci.yml),make build— all green.Docs updated
docs/features/version-updates.md—update_checkreference, hot-reload, env-vs-config precedence, banner behaviordocs/configuration.md,docs/configuration/config-file.md— Update Check sections + complete-reference keydocs/configuration/environment-variables.md— auto-update table is core+tray, points at config equivalentdocs/api/rest-api.md—/api/v1/infoomitsupdatewhen disabled;refresh=trueno-op notedocs/cli/status-command.md— disabled case (no update object / annotation)docs/prerelease-builds.md— RC opt-in viaupdate_check.channel=rcOut of scope
update_check.enabled, but env-only for channel); consuming the shared checker incl. channel is a separate 079 work item.updateobject.🤖 Generated with Claude Code