fix: auto-detect and safely connect to existing OpenCode server on startup (#402)
Add probeExternalOpenCode() to detect a running OpenCode instance via GET /global/health before attempting to spawn a new one. When an external server is detected (or OPENCODE_SKIP_START is set), the isExternalOpenCode flag prevents gracefulShutdown() and restartOpenCode() from killing the user's independent OpenCode process. - Probe configured port, then default 4096 on startup - Use /global/health + body.healthy === true for reliable detection - Skip kill -9 on shutdown/restart when server is externally owned - Re-probe (instead of kill + respawn) on restart for external servers
This commit is contained in:
@@ -2687,6 +2687,7 @@ let openCodeApiDetectionTimer = null;
|
||||
let lastOpenCodeError = null;
|
||||
let isOpenCodeReady = false;
|
||||
let openCodeNotReadySince = 0;
|
||||
let isExternalOpenCode = false;
|
||||
let exitOnShutdown = true;
|
||||
let uiAuthController = null;
|
||||
let cloudflareTunnelController = null;
|
||||
@@ -2740,6 +2741,36 @@ async function isOpenCodeProcessHealthy() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Probe if an external OpenCode instance is already running on the given port.
|
||||
* Unlike isOpenCodeProcessHealthy(), this doesn't require openCodeProcess to be set.
|
||||
* Used to auto-detect and connect to an existing OpenCode instance on startup.
|
||||
*/
|
||||
async function probeExternalOpenCode(port) {
|
||||
if (!port || port <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), 3000);
|
||||
const response = await fetch(`http://127.0.0.1:${port}/global/health`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
...getOpenCodeAuthHeaders(),
|
||||
},
|
||||
signal: controller.signal,
|
||||
});
|
||||
clearTimeout(timeout);
|
||||
if (!response.ok) return false;
|
||||
const body = await response.json().catch(() => null);
|
||||
return body?.healthy === true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const ENV_CONFIGURED_OPENCODE_PORT = (() => {
|
||||
const raw =
|
||||
process.env.OPENCODE_PORT ||
|
||||
@@ -4338,6 +4369,31 @@ async function restartOpenCode() {
|
||||
openCodeNotReadySince = Date.now();
|
||||
console.log('Restarting OpenCode process...');
|
||||
|
||||
// For external OpenCode servers, re-probe instead of kill + respawn
|
||||
if (isExternalOpenCode) {
|
||||
console.log('Re-probing external OpenCode server...');
|
||||
const probePort = openCodePort || ENV_CONFIGURED_OPENCODE_PORT || 4096;
|
||||
const healthy = await probeExternalOpenCode(probePort);
|
||||
if (healthy) {
|
||||
console.log(`External OpenCode server on port ${probePort} is healthy`);
|
||||
setOpenCodePort(probePort);
|
||||
isOpenCodeReady = true;
|
||||
lastOpenCodeError = null;
|
||||
openCodeNotReadySince = 0;
|
||||
syncToHmrState();
|
||||
} else {
|
||||
lastOpenCodeError = `External OpenCode server on port ${probePort} is not responding`;
|
||||
console.error(lastOpenCodeError);
|
||||
throw new Error(lastOpenCodeError);
|
||||
}
|
||||
|
||||
if (expressApp) {
|
||||
setupProxy(expressApp);
|
||||
ensureOpenCodeApiPrefix();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const portToKill = openCodePort;
|
||||
|
||||
if (openCodeProcess) {
|
||||
@@ -5018,7 +5074,7 @@ async function gracefulShutdown(options = {}) {
|
||||
}
|
||||
|
||||
// Only stop OpenCode if we started it ourselves (not when using external server)
|
||||
if (!ENV_SKIP_OPENCODE_START) {
|
||||
if (!ENV_SKIP_OPENCODE_START && !isExternalOpenCode) {
|
||||
const portToKill = openCodePort;
|
||||
|
||||
if (openCodeProcess) {
|
||||
@@ -10592,6 +10648,23 @@ Context:
|
||||
console.log(`Using external OpenCode server on port ${ENV_CONFIGURED_OPENCODE_PORT} (skip-start mode)`);
|
||||
setOpenCodePort(ENV_CONFIGURED_OPENCODE_PORT);
|
||||
isOpenCodeReady = true;
|
||||
isExternalOpenCode = true;
|
||||
lastOpenCodeError = null;
|
||||
openCodeNotReadySince = 0;
|
||||
syncToHmrState();
|
||||
} else if (ENV_CONFIGURED_OPENCODE_PORT && await probeExternalOpenCode(ENV_CONFIGURED_OPENCODE_PORT)) {
|
||||
console.log(`Auto-detected existing OpenCode server on port ${ENV_CONFIGURED_OPENCODE_PORT}`);
|
||||
setOpenCodePort(ENV_CONFIGURED_OPENCODE_PORT);
|
||||
isOpenCodeReady = true;
|
||||
isExternalOpenCode = true;
|
||||
lastOpenCodeError = null;
|
||||
openCodeNotReadySince = 0;
|
||||
syncToHmrState();
|
||||
} else if (!ENV_CONFIGURED_OPENCODE_PORT && await probeExternalOpenCode(4096)) {
|
||||
console.log('Auto-detected existing OpenCode server on default port 4096');
|
||||
setOpenCodePort(4096);
|
||||
isOpenCodeReady = true;
|
||||
isExternalOpenCode = true;
|
||||
lastOpenCodeError = null;
|
||||
openCodeNotReadySince = 0;
|
||||
syncToHmrState();
|
||||
|
||||
Reference in New Issue
Block a user