From 7929f57460e465c988a9c0bcad2ef00c3f433db3 Mon Sep 17 00:00:00 2001 From: Artus KG Date: Sat, 17 Jan 2026 18:11:21 +0100 Subject: [PATCH 1/5] macos: keep CLI install build suffix --- apps/macos/Sources/Clawdbot/CLIInstaller.swift | 2 +- .../Sources/Clawdbot/GatewayEnvironment.swift | 15 +++++++++++++-- apps/macos/Sources/Clawdbot/Onboarding.swift | 2 +- .../GatewayEnvironmentTests.swift | 1 + 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/apps/macos/Sources/Clawdbot/CLIInstaller.swift b/apps/macos/Sources/Clawdbot/CLIInstaller.swift index 41abb29e6..d967002f5 100644 --- a/apps/macos/Sources/Clawdbot/CLIInstaller.swift +++ b/apps/macos/Sources/Clawdbot/CLIInstaller.swift @@ -35,7 +35,7 @@ enum CLIInstaller { } static func install(statusHandler: @escaping @MainActor @Sendable (String) async -> Void) async { - let expected = GatewayEnvironment.expectedGatewayVersion()?.description ?? "latest" + let expected = GatewayEnvironment.expectedGatewayVersionString() ?? "latest" let prefix = Self.installPrefix() await statusHandler("Installing clawdbot CLI…") let cmd = self.installScriptCommand(version: expected, prefix: prefix) diff --git a/apps/macos/Sources/Clawdbot/GatewayEnvironment.swift b/apps/macos/Sources/Clawdbot/GatewayEnvironment.swift index 6560b1870..c8f6ed621 100644 --- a/apps/macos/Sources/Clawdbot/GatewayEnvironment.swift +++ b/apps/macos/Sources/Clawdbot/GatewayEnvironment.swift @@ -80,8 +80,13 @@ enum GatewayEnvironment { } static func expectedGatewayVersion() -> Semver? { + Semver.parse(self.expectedGatewayVersionString()) + } + + static func expectedGatewayVersionString() -> String? { let bundleVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String - return Semver.parse(bundleVersion) + let trimmed = bundleVersion?.trimmingCharacters(in: .whitespacesAndNewlines) + return (trimmed?.isEmpty == false) ? trimmed : nil } // Exposed for tests so we can inject fake version checks without rewriting bundle metadata. @@ -220,8 +225,14 @@ enum GatewayEnvironment { } static func installGlobal(version: Semver?, statusHandler: @escaping @Sendable (String) -> Void) async { + await self.installGlobal(versionString: version?.description, statusHandler: statusHandler) + } + + static func installGlobal(versionString: String?, statusHandler: @escaping @Sendable (String) -> Void) async { let preferred = CommandResolver.preferredPaths().joined(separator: ":") - let target = version?.description ?? "latest" + let target = versionString? + .trimmingCharacters(in: .whitespacesAndNewlines) + .flatMap { $0.isEmpty ? nil : $0 } ?? "latest" let npm = CommandResolver.findExecutable(named: "npm") let pnpm = CommandResolver.findExecutable(named: "pnpm") let bun = CommandResolver.findExecutable(named: "bun") diff --git a/apps/macos/Sources/Clawdbot/Onboarding.swift b/apps/macos/Sources/Clawdbot/Onboarding.swift index 0af5e94e5..f45eee8ce 100644 --- a/apps/macos/Sources/Clawdbot/Onboarding.swift +++ b/apps/macos/Sources/Clawdbot/Onboarding.swift @@ -155,7 +155,7 @@ struct OnboardingView: View { var canAdvance: Bool { !self.isWizardBlocking } var devLinkCommand: String { - let version = GatewayEnvironment.expectedGatewayVersion()?.description ?? "latest" + let version = GatewayEnvironment.expectedGatewayVersionString() ?? "latest" return "npm install -g clawdbot@\(version)" } diff --git a/apps/macos/Tests/ClawdbotIPCTests/GatewayEnvironmentTests.swift b/apps/macos/Tests/ClawdbotIPCTests/GatewayEnvironmentTests.swift index 069e5b7fa..fb01ea638 100644 --- a/apps/macos/Tests/ClawdbotIPCTests/GatewayEnvironmentTests.swift +++ b/apps/macos/Tests/ClawdbotIPCTests/GatewayEnvironmentTests.swift @@ -44,6 +44,7 @@ import Testing @Test func expectedGatewayVersionFromStringUsesParser() { #expect(GatewayEnvironment.expectedGatewayVersion(from: "v9.1.2") == Semver(major: 9, minor: 1, patch: 2)) + #expect(GatewayEnvironment.expectedGatewayVersion(from: "2026.1.11-4") == Semver(major: 2026, minor: 1, patch: 11)) #expect(GatewayEnvironment.expectedGatewayVersion(from: nil) == nil) } } From 84cdd2df7306887d11fc6230128b8a0050771601 Mon Sep 17 00:00:00 2001 From: Artus KG Date: Sat, 17 Jan 2026 18:12:11 +0100 Subject: [PATCH 2/5] changelog: note CLI install build suffix fix --- CHANGELOG.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9ee126b2..5111ea119 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,12 +6,8 @@ Docs: https://docs.clawd.bot ### Changes - macOS: strip prerelease/build suffixes when parsing gateway semver patches. (#1110) — thanks @zerone0x. -- Models: add Kimi Code provider onboarding and docs. (#1085) — thanks @dan-dr. +- macOS: keep CLI install pinned to the full build suffix. (#1111) — thanks @artuskg. -### Fixes -- Matrix: send voice/image-specific media payloads and keep legacy poll parsing. (#1088) — thanks @sibbl. -- Telegram: allow media-only message tool sends to request voice notes via `asVoice`. (#1099) — thanks @mukhtharcm. -- Discord: soften logs for expired interactions and stale component clicks. ## 2026.1.16-2 ### Changes From 5599e4cf3513a773b260a6ce41ee614b96a680a3 Mon Sep 17 00:00:00 2001 From: Artus KG Date: Sat, 17 Jan 2026 18:21:04 +0100 Subject: [PATCH 3/5] test: make session update timestamp UTC --- src/auto-reply/reply/session-updates.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/auto-reply/reply/session-updates.test.ts b/src/auto-reply/reply/session-updates.test.ts index 4287c9c9e..05def80da 100644 --- a/src/auto-reply/reply/session-updates.test.ts +++ b/src/auto-reply/reply/session-updates.test.ts @@ -7,7 +7,7 @@ import { prependSystemEvents } from "./session-updates.js"; describe("prependSystemEvents", () => { it("adds a UTC timestamp to queued system events", async () => { vi.useFakeTimers(); - const timestamp = new Date("2026-01-12T20:19:17"); + const timestamp = new Date("2026-01-12T20:19:17Z"); vi.setSystemTime(timestamp); enqueueSystemEvent("Model switched.", { sessionKey: "agent:main:main" }); From cee4149884d802e4b707c6e0f05468e2e30f4ba4 Mon Sep 17 00:00:00 2001 From: Artus KG Date: Sat, 17 Jan 2026 18:32:51 +0100 Subject: [PATCH 4/5] macos: handle empty install version safely --- apps/macos/Sources/Clawdbot/GatewayEnvironment.swift | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/macos/Sources/Clawdbot/GatewayEnvironment.swift b/apps/macos/Sources/Clawdbot/GatewayEnvironment.swift index c8f6ed621..682c7e969 100644 --- a/apps/macos/Sources/Clawdbot/GatewayEnvironment.swift +++ b/apps/macos/Sources/Clawdbot/GatewayEnvironment.swift @@ -230,9 +230,13 @@ enum GatewayEnvironment { static func installGlobal(versionString: String?, statusHandler: @escaping @Sendable (String) -> Void) async { let preferred = CommandResolver.preferredPaths().joined(separator: ":") - let target = versionString? - .trimmingCharacters(in: .whitespacesAndNewlines) - .flatMap { $0.isEmpty ? nil : $0 } ?? "latest" + let trimmed = versionString?.trimmingCharacters(in: .whitespacesAndNewlines) + let target: String + if let trimmed, !trimmed.isEmpty { + target = trimmed + } else { + target = "latest" + } let npm = CommandResolver.findExecutable(named: "npm") let pnpm = CommandResolver.findExecutable(named: "pnpm") let bun = CommandResolver.findExecutable(named: "bun") From ec9ba5b78445b5244d3fc0972c25c2a631d781ee Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 17 Jan 2026 17:44:39 +0000 Subject: [PATCH 5/5] fix: show full gateway version string in status (#1111) (thanks @artuskg) --- .../Sources/Clawdbot/GatewayEnvironment.swift | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/apps/macos/Sources/Clawdbot/GatewayEnvironment.swift b/apps/macos/Sources/Clawdbot/GatewayEnvironment.swift index 682c7e969..032275bba 100644 --- a/apps/macos/Sources/Clawdbot/GatewayEnvironment.swift +++ b/apps/macos/Sources/Clawdbot/GatewayEnvironment.swift @@ -105,6 +105,7 @@ enum GatewayEnvironment { } } let expected = self.expectedGatewayVersion() + let expectedString = self.expectedGatewayVersionString() let projectRoot = CommandResolver.projectRoot() let projectEntrypoint = CommandResolver.gatewayEntrypoint(in: projectRoot) @@ -115,8 +116,8 @@ enum GatewayEnvironment { kind: .missingNode, nodeVersion: nil, gatewayVersion: nil, - requiredGateway: expected?.description, - message: RuntimeLocator.describeFailure(err)) + requiredGateway: expectedString, + message: RuntimeLocator.describeFailure(err)) case let .success(runtime): let gatewayBin = CommandResolver.clawdbotExecutable() @@ -125,7 +126,7 @@ enum GatewayEnvironment { kind: .missingGateway, nodeVersion: runtime.version.description, gatewayVersion: nil, - requiredGateway: expected?.description, + requiredGateway: expectedString, message: "clawdbot CLI not found in PATH; install the CLI.") } @@ -133,13 +134,14 @@ enum GatewayEnvironment { ?? self.readLocalGatewayVersion(projectRoot: projectRoot) if let expected, let installed, !installed.compatible(with: expected) { + let expectedText = expectedString ?? expected.description return GatewayEnvironmentStatus( - kind: .incompatible(found: installed.description, required: expected.description), + kind: .incompatible(found: installed.description, required: expectedText), nodeVersion: runtime.version.description, gatewayVersion: installed.description, - requiredGateway: expected.description, + requiredGateway: expectedText, message: """ - Gateway version \(installed.description) is incompatible with app \(expected.description); + Gateway version \(installed.description) is incompatible with app \(expectedText); install or update the global package. """) } @@ -157,7 +159,7 @@ enum GatewayEnvironment { kind: .ok, nodeVersion: runtime.version.description, gatewayVersion: gatewayVersionText, - requiredGateway: expected?.description, + requiredGateway: expectedString, message: "Node \(runtime.version.description); gateway \(gatewayVersionText) \(gatewayLabelText)") } }