fix(vscode): reuse IPC server port across restarts (#252) (#256)

Persist the IPC port in workspaceState and try to rebind to it on
activation so restored terminals still have a valid PLANNOTATOR_VSCODE_PORT.
Falls back to a random port if the preferred one is taken.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Michael Ramos
2026-03-09 06:56:03 -07:00
committed by GitHub
parent 190f7ec412
commit 8201fe3537
2 changed files with 36 additions and 25 deletions
+5 -2
View File
@@ -48,13 +48,16 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
vscode.window.showInformationMessage("Plannotator panel opened");
};
// Start local IPC server to receive URLs from the router script
// Start local IPC server to receive URLs from the router script.
// Reuse the last port so restored terminals still have a valid PLANNOTATOR_VSCODE_PORT.
const lastPort = context.workspaceState.get<number>("ipcPort");
const { server, port } = await createIpcServer((url) => {
openInPanel(url).catch((err) => {
log.error(`[open] failed: ${err}`);
vscode.window.showErrorMessage(`Plannotator: ${err}`);
});
});
}, lastPort);
context.workspaceState.update("ipcPort", port);
context.subscriptions.push({ dispose: () => server.close() });
// Inject env vars into integrated terminals
+31 -23
View File
@@ -6,31 +6,39 @@ import * as http from "http";
*/
export function createIpcServer(
onUrl: (url: string) => void,
preferredPort?: number,
): Promise<{ server: http.Server; port: number }> {
return new Promise((resolve, reject) => {
const server = http.createServer((req, res) => {
const parsed = new globalThis.URL(req.url!, "http://localhost");
const targetUrl = parsed.searchParams.get("url");
const handler = (req: http.IncomingMessage, res: http.ServerResponse) => {
const parsed = new globalThis.URL(req.url!, "http://localhost");
const targetUrl = parsed.searchParams.get("url");
if (req.method === "GET" && parsed.pathname === "/open" && targetUrl) {
onUrl(targetUrl);
res.writeHead(200);
res.end("ok");
} else {
res.writeHead(404);
res.end("not found");
}
if (req.method === "GET" && parsed.pathname === "/open" && targetUrl) {
onUrl(targetUrl);
res.writeHead(200);
res.end("ok");
} else {
res.writeHead(404);
res.end("not found");
}
};
function listen(port: number): Promise<{ server: http.Server; port: number }> {
return new Promise((resolve, reject) => {
const server = http.createServer(handler);
server.listen(port, "127.0.0.1", () => {
const addr = server.address();
if (addr && typeof addr === "object") {
resolve({ server, port: addr.port });
} else {
reject(new Error("Failed to get server address"));
}
});
server.on("error", reject);
});
}
server.listen(0, "127.0.0.1", () => {
const addr = server.address();
if (addr && typeof addr === "object") {
resolve({ server, port: addr.port });
} else {
reject(new Error("Failed to get server address"));
}
});
server.on("error", reject);
});
if (preferredPort) {
return listen(preferredPort).catch(() => listen(0));
}
return listen(0);
}