fix: better shutdown logic (#543)

Make cancellation a no-op for unknown sessions, close each
session query during disposal, and run the same cleanup path on
SIGINT and SIGTERM in CLI mode.

Follow up to #530
This commit is contained in:
Ben Brandt
2026-04-13 18:52:44 +02:00
committed by GitHub
parent 5c81e99fe5
commit 9fb631f5d7
3 changed files with 14 additions and 7 deletions

View File

@@ -968,7 +968,7 @@ export class ClaudeAcpAgent implements Agent {
async cancel(params: CancelNotification): Promise<void> {
const session = this.sessions[params.sessionId];
if (!session) {
throw new Error("Session not found");
return;
}
session.cancelled = true;
for (const [, pending] of session.pendingMessages) {
@@ -988,6 +988,7 @@ export class ClaudeAcpAgent implements Agent {
await this.cancel({ sessionId });
session.settingsManager.dispose();
session.abortController.abort();
session.query.close();
delete this.sessions[sessionId];
}

View File

@@ -26,15 +26,20 @@ if (process.argv.includes("--cli")) {
const { connection, agent } = runAcp();
// Exit cleanly when the ACP connection closes (e.g. stdin EOF, transport
// error). Without this, `process.stdin.resume()` keeps the event loop
// alive indefinitely, causing orphan process accumulation in oneshot mode.
connection.closed.then(async () => {
async function shutdown() {
await agent.dispose().catch((err) => {
console.error("Error during cleanup:", err);
});
process.exit(0);
});
}
// Exit cleanly when the ACP connection closes (e.g. stdin EOF, transport
// error). Without this, `process.stdin.resume()` keeps the event loop
// alive indefinitely, causing orphan process accumulation in oneshot mode.
connection.closed.then(shutdown);
process.on("SIGTERM", shutdown);
process.on("SIGINT", shutdown);
// Keep process alive while connection is open
process.stdin.resume();

View File

@@ -1538,7 +1538,7 @@ describe("session/close", () => {
function injectSession(agent: ClaudeAcpAgent, sessionId: string) {
function* empty() {}
const gen = Object.assign(empty(), { interrupt: vi.fn() });
const gen = Object.assign(empty(), { interrupt: vi.fn(), close: vi.fn() });
agent.sessions[sessionId] = {
query: gen as any,
input: new Pushable(),
@@ -1633,6 +1633,7 @@ describe("getOrCreateSession param change detection", () => {
function* empty() {}
const gen = Object.assign(empty(), {
interrupt: vi.fn(),
close: vi.fn(),
supportedCommands: vi.fn().mockResolvedValue([]),
});
agent.sessions[sessionId] = {