Skip to content

Commit e6190f6

Browse files
Dumbrisclaude
andauthored
fix(release): inject httpapi.buildVersion everywhere + guard update check against non-semver versions (#814)
Release QA on v0.47.0-rc.4 found the installed app reporting version 'development' on every httpapi-backed surface, with the Spec 079 update banner, 'mcpproxy doctor' and 'mcpproxy status' all offering v0.46.0 — a DOWNGRADE prompt — and telemetry heartbeats reporting 'development' (corrupting Spec 080 churn metrics for the whole RC channel). Two root causes, both fixed: 1. Wrong/missing -X ldflags paths. Go silently ignores -X with a wrong package path; the module is github.com/smart-mcp-proxy/mcpproxy-go: - prerelease.yml used mcpproxy-go/internal/httpapi.buildVersion (ignored) and the bogus mcpproxy-go/cmd/mcpproxy.version → every RC binary shipped with buildVersion='development' - scripts/build-windows-installer.ps1 (used by BOTH release.yml and prerelease.yml) never set httpapi.buildVersion → stable Windows installer builds have the same bug - scripts/build.ps1 and pr-build.yml aligned for consistency 2. Checker.CheckNow() (the /api/v1/info forced-refresh path used by the Web UI banner and doctor) lacked the non-semver guard that Start() has, and compareVersions() ranks an invalid current version below any release. A 'development' build therefore saw every stable release as an available 'update'. CheckNow now skips like Start, and compareVersions returns false for a non-semver current version. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 321ff89 commit e6190f6

6 files changed

Lines changed: 58 additions & 10 deletions

File tree

.github/workflows/pr-build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ jobs:
207207
CGO_CFLAGS: "-mmacosx-version-min=13.0"
208208
CGO_LDFLAGS: "-mmacosx-version-min=13.0"
209209
run: |
210-
LDFLAGS="-s -w -X mcpproxy-go/cmd/mcpproxy.version=${VERSION} -X main.version=${VERSION}"
210+
LDFLAGS="-s -w -X main.version=${VERSION} -X github.com/smart-mcp-proxy/mcpproxy-go/internal/httpapi.buildVersion=${VERSION}"
211211
212212
# Determine clean binary name
213213
if [ "${{ matrix.goos }}" = "windows" ]; then

.github/workflows/prerelease.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,9 @@ jobs:
205205
fi
206206
207207
echo "Building version: ${VERSION}"
208-
LDFLAGS="-s -w -X mcpproxy-go/cmd/mcpproxy.version=${VERSION} -X main.version=${VERSION} -X mcpproxy-go/internal/httpapi.buildVersion=${VERSION}"
208+
# -X paths must use the full module path (go.mod: github.com/smart-mcp-proxy/mcpproxy-go);
209+
# a wrong path is silently ignored and the binary reports "development".
210+
LDFLAGS="-s -w -X main.version=${VERSION} -X github.com/smart-mcp-proxy/mcpproxy-go/internal/httpapi.buildVersion=${VERSION}"
209211
210212
# Determine clean binary name
211213
if [ "${{ matrix.goos }}" = "windows" ]; then

internal/updatecheck/checker.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,12 @@ func (c *Checker) compareVersions(current, latest string) bool {
334334
currentSemver := ensureVPrefix(current)
335335
latestSemver := ensureVPrefix(latest)
336336

337+
// semver.Compare ranks an invalid version below every valid one, which
338+
// would flag any published release as an "update" for a dev build.
339+
if !semver.IsValid(currentSemver) {
340+
return false
341+
}
342+
337343
// semver.Compare returns -1 if current < latest
338344
return semver.Compare(currentSemver, latestSemver) < 0
339345
}
@@ -377,6 +383,14 @@ func (c *Checker) CheckNow() *VersionInfo {
377383
c.logger.Debug("Immediate update check skipped: update checking disabled")
378384
return nil
379385
}
386+
// Same guard as Start(): a non-semver current version (e.g. "development")
387+
// cannot be meaningfully compared, and offering the latest stable release
388+
// against it is a downgrade prompt, not an update.
389+
if !c.isValidSemver() {
390+
c.logger.Debug("Immediate update check skipped: non-semver version",
391+
zap.String("version", c.version))
392+
return nil
393+
}
380394
c.logger.Debug("Performing immediate update check")
381395
c.check()
382396
return c.GetVersionInfo()

internal/updatecheck/checker_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,35 @@ func TestChecker_CheckNow_NoUpdate(t *testing.T) {
7979
}
8080
}
8181

82+
// TestChecker_CheckNow_NonSemverVersionSkipsCheck verifies that the forced
83+
// refresh path has the same non-semver guard as Start(): a "development"
84+
// build must never run a check, because compareVersions would rank the
85+
// invalid current version below ANY published release and every surface
86+
// (Web UI banner, doctor, status) would offer a stable-version "update" —
87+
// a downgrade prompt on dev/misbuilt binaries.
88+
func TestChecker_CheckNow_NonSemverVersionSkipsCheck(t *testing.T) {
89+
for _, version := range []string{"development", "dev", ""} {
90+
t.Run("version_"+version, func(t *testing.T) {
91+
checker := New(zaptest.NewLogger(t), version)
92+
93+
checked := false
94+
checker.SetCheckFunc(func() (*GitHubRelease, error) {
95+
checked = true
96+
return &GitHubRelease{TagName: "v1.1.0", HTMLURL: "https://example.com"}, nil
97+
})
98+
99+
info := checker.CheckNow()
100+
101+
if checked {
102+
t.Error("CheckNow ran a network check for a non-semver version")
103+
}
104+
if info != nil && info.UpdateAvailable {
105+
t.Errorf("UpdateAvailable = true for non-semver version %q", version)
106+
}
107+
})
108+
}
109+
}
110+
82111
// TestChecker_UpdateAvailableLoggedOncePerVersion verifies the "Update available"
83112
// Info log is emitted exactly once per detected latest version, not on every
84113
// periodic tick (FR-004 of specs/079-upgrade-nudge: no repeated log spam).
@@ -168,6 +197,10 @@ func TestChecker_CompareVersions(t *testing.T) {
168197
{"1.0.0", "1.1.0", true}, // Without v prefix
169198
{"v0.11.1", "v0.11.3", true},
170199
{"v0.11.2", "v0.11.2", false},
200+
// An invalid current version must never be treated as "older than
201+
// everything" — that turns a dev build into a permanent downgrade nudge.
202+
{"development", "v1.0.0", false},
203+
{"", "v1.0.0", false},
171204
}
172205

173206
for _, tc := range tests {

scripts/build-windows-installer.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ Write-Host " Building mcpproxy.exe..." -ForegroundColor White
4848
$env:CGO_ENABLED = "0"
4949
$CoreBinary = Join-Path $BinDir "mcpproxy.exe"
5050
$CoreCmd = Join-Path $RepoRoot "cmd/mcpproxy"
51-
go build -buildvcs=false -ldflags "-s -w -X main.version=$Version" -o $CoreBinary $CoreCmd
51+
go build -buildvcs=false -ldflags "-s -w -X main.version=$Version -X github.com/smart-mcp-proxy/mcpproxy-go/internal/httpapi.buildVersion=$Version" -o $CoreBinary $CoreCmd
5252

5353
if ($LASTEXITCODE -ne 0) {
5454
Write-Host " Failed to build mcpproxy.exe" -ForegroundColor Red
@@ -61,7 +61,7 @@ Write-Host " Building mcpproxy-tray.exe..." -ForegroundColor White
6161
$env:CGO_ENABLED = "1"
6262
$TrayBinary = Join-Path $BinDir "mcpproxy-tray.exe"
6363
$TrayCmd = Join-Path $RepoRoot "cmd/mcpproxy-tray"
64-
go build -buildvcs=false -ldflags "-s -w -X main.version=$Version -H windowsgui" -o $TrayBinary $TrayCmd
64+
go build -buildvcs=false -ldflags "-s -w -X main.version=$Version -X github.com/smart-mcp-proxy/mcpproxy-go/internal/httpapi.buildVersion=$Version -H windowsgui" -o $TrayBinary $TrayCmd
6565

6666
if ($LASTEXITCODE -ne 0) {
6767
Write-Host " Failed to build mcpproxy-tray.exe" -ForegroundColor Red

scripts/build.ps1

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ Write-Host "Commit: $Commit" -ForegroundColor Green
4242
Write-Host "Date: $Date" -ForegroundColor Green
4343
Write-Host ""
4444

45-
$LDFLAGS = "-X main.version=$Version -X main.commit=$Commit -X main.date=$Date -s -w"
45+
$LDFLAGS = "-X main.version=$Version -X main.commit=$Commit -X main.date=$Date -X github.com/smart-mcp-proxy/mcpproxy-go/internal/httpapi.buildVersion=$Version -s -w"
4646

4747
# Array to track built binaries
4848
$BuiltBinaries = @()
@@ -96,7 +96,7 @@ if (!$OnlyCurrentPlatform) {
9696
if ($BuildTray) {
9797
Write-Host ""
9898
Write-Host "Building mcpproxy-tray binaries..." -ForegroundColor Magenta
99-
99+
100100
# Build tray for current platform (Windows with CGO)
101101
Write-Host "Building tray for Windows..." -ForegroundColor Cyan
102102
$env:CGO_ENABLED = "1"
@@ -109,7 +109,7 @@ if ($BuildTray) {
109109
Write-Host "✓ Windows tray binary built successfully" -ForegroundColor Green
110110
$BuiltBinaries += "mcpproxy-tray.exe"
111111
}
112-
112+
113113
if (!$OnlyCurrentPlatform) {
114114
# Build tray for macOS (requires macOS host for proper CGO compilation)
115115
Write-Host "Attempting to build tray for macOS..." -ForegroundColor Cyan
@@ -123,7 +123,7 @@ if ($BuildTray) {
123123
Write-Host "✓ macOS tray binary built successfully" -ForegroundColor Green
124124
$BuiltBinaries += "mcpproxy-tray-darwin-amd64"
125125
}
126-
126+
127127
# Build tray for macOS ARM64
128128
Write-Host "Attempting to build tray for macOS ARM64..." -ForegroundColor Cyan
129129
$env:CGO_ENABLED = "1"
@@ -139,7 +139,7 @@ if ($BuildTray) {
139139
} else {
140140
Write-Host "Skipping cross-platform tray builds (OnlyCurrentPlatform flag is set)" -ForegroundColor Yellow
141141
}
142-
142+
143143
# Reset environment variables
144144
Remove-Item Env:CGO_ENABLED -ErrorAction SilentlyContinue
145145
Remove-Item Env:GOOS -ErrorAction SilentlyContinue
@@ -172,4 +172,3 @@ if ($BuiltFiles.Count -gt 0) {
172172
Write-Host ""
173173
Write-Host "Test version info:" -ForegroundColor Cyan
174174
& .\mcpproxy.exe --version
175-

0 commit comments

Comments
 (0)