From 1f6eff361459ba811f0f91472becee380bf8a172 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Fri, 24 Jul 2026 20:21:57 +0000 Subject: [PATCH] chat: reap orphaned per-cwd opencode serves on startup Each pooled per-cwd server is a ~0.5GB process; across officer restarts the previous instance's servers would orphan and pile up. On module load, kill any opencode serve on a non-fixed port (the fixed OPENCODE_SERVER_URL port is preserved). Co-Authored-By: Claude Opus 4.8 --- .../api/chat/opencode/server-manager.ts | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/servers/api/chat/opencode/server-manager.ts b/src/servers/api/chat/opencode/server-manager.ts index d6d0f79f..61586e31 100644 --- a/src/servers/api/chat/opencode/server-manager.ts +++ b/src/servers/api/chat/opencode/server-manager.ts @@ -22,6 +22,30 @@ type OpenCodeServer = { baseUrl: string; proc: Subprocess }; const servers = new Map(); const starting = new Map>(); +// On startup, reap orphaned per-cwd serves left by a previous officer instance (each ~0.5GB) so they +// don't accumulate across restarts. The fixed server (OPENCODE_SERVER_URL) is preserved by port. +(function reapOrphanServers() { + const fixedPort = (() => { + try { + return new URL(OPENCODE_SERVER_URL).port || '4096'; + } catch { + return '4096'; + } + })(); + try { + Bun.spawnSync([ + 'bash', + '-c', + `ss -tlnp 2>/dev/null | grep -F opencode | grep -oP '127\\.0\\.0\\.1:\\K[0-9]+' | sort -u | while read -r p; do ` + + `[ "$p" = "${fixedPort}" ] && continue; ` + + `pid=$(ss -tlnp 2>/dev/null | grep "127.0.0.1:$p " | grep -oP 'pid=\\K[0-9]+' | head -1); ` + + `[ -n "$pid" ] && kill "$pid" 2>/dev/null; done`, + ]); + } catch { + /* best effort */ + } +})(); + async function isHealthy(baseUrl: string): Promise { try { const res = await fetch(`${baseUrl}/api/health`, { signal: AbortSignal.timeout(3000) });