From dffa4907ecd6406d92ea0562118477eca56cad22 Mon Sep 17 00:00:00 2001 From: Shantur Rathore Date: Thu, 15 Jan 2026 20:47:30 +0000 Subject: [PATCH] fix(server): validate OpenCode binary by spawning --version --- packages/server/src/config/binaries.ts | 40 +++++++++++++++++++++-- packages/server/src/workspaces/runtime.ts | 6 ++-- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/packages/server/src/config/binaries.ts b/packages/server/src/config/binaries.ts index 7b3d4f52..56d86d50 100644 --- a/packages/server/src/config/binaries.ts +++ b/packages/server/src/config/binaries.ts @@ -4,10 +4,12 @@ import { BinaryUpdateRequest, BinaryValidationResult, } from "../api-types" +import { spawnSync } from "child_process" import { ConfigStore } from "./store" import { EventBus } from "../events/bus" import type { ConfigFile } from "./schema" import { Logger } from "../logger" +import { buildSpawnSpec } from "../workspaces/runtime" export class BinaryRegistry { constructor( @@ -135,8 +137,42 @@ export class BinaryRegistry { } private validateRecord(record: BinaryRecord): BinaryValidationResult { - // TODO: call actual binary -v check. - return { valid: true, version: record.version } + const inputPath = record.path + if (!inputPath) { + return { valid: false, error: "Missing binary path" } + } + + const spec = buildSpawnSpec(inputPath, ["--version"]) + + try { + const result = spawnSync(spec.command, spec.args, { + encoding: "utf8", + windowsVerbatimArguments: Boolean((spec.options as { windowsVerbatimArguments?: boolean }).windowsVerbatimArguments), + }) + + if (result.error) { + return { valid: false, error: result.error.message } + } + + if (result.status !== 0) { + const stderr = result.stderr?.trim() + const stdout = result.stdout?.trim() + const combined = stderr || stdout + const error = combined ? `Exited with code ${result.status}: ${combined}` : `Exited with code ${result.status}` + return { valid: false, error } + } + + const stdout = (result.stdout ?? "").trim() + const firstLine = stdout.split(/\r?\n/).find((line) => line.trim().length > 0) + const normalized = firstLine?.trim() + + const versionMatch = normalized?.match(/([0-9]+\.[0-9]+\.[0-9A-Za-z.-]+)/) + const version = versionMatch?.[1] + + return { valid: true, version } + } catch (error) { + return { valid: false, error: error instanceof Error ? error.message : String(error) } + } } private buildFallbackRecord(path: string): BinaryRecord { diff --git a/packages/server/src/workspaces/runtime.ts b/packages/server/src/workspaces/runtime.ts index 5754d4b7..203d984a 100644 --- a/packages/server/src/workspaces/runtime.ts +++ b/packages/server/src/workspaces/runtime.ts @@ -5,10 +5,10 @@ import { EventBus } from "../events/bus" import { LogLevel, WorkspaceLogEntry } from "../api-types" import { Logger } from "../logger" -const WINDOWS_CMD_EXTENSIONS = new Set([".cmd", ".bat"]) -const WINDOWS_POWERSHELL_EXTENSIONS = new Set([".ps1"]) +export const WINDOWS_CMD_EXTENSIONS = new Set([".cmd", ".bat"]) +export const WINDOWS_POWERSHELL_EXTENSIONS = new Set([".ps1"]) -function buildSpawnSpec(binaryPath: string, args: string[]) { +export function buildSpawnSpec(binaryPath: string, args: string[]) { if (process.platform !== "win32") { return { command: binaryPath, args, options: {} as const } }