mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
fix(cli): fix process exit handling deadlock and error masking in Claude SDK (#343)
This commit is contained in:
@@ -79,7 +79,16 @@ export async function claudeRemote(opts: {
|
||||
process.env.DISABLE_AUTOUPDATER = '1';
|
||||
|
||||
// Get initial message
|
||||
const initial = await opts.nextMessage();
|
||||
let initial;
|
||||
try {
|
||||
initial = await opts.nextMessage();
|
||||
} catch (e) {
|
||||
if (e instanceof AbortError) {
|
||||
logger.debug(`[claudeRemote] Aborted during initial message`);
|
||||
return;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
if (!initial) { // No initial message - exit
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -363,7 +363,8 @@ class ClaudeRemoteLauncher extends RemoteLauncherBase {
|
||||
} catch (e) {
|
||||
logger.debug('[remote]: launch error', e);
|
||||
if (!this.exitReason) {
|
||||
session.client.sendSessionEvent({ type: 'message', message: 'Process exited unexpectedly' });
|
||||
const detail = e instanceof Error ? e.message : String(e);
|
||||
session.client.sendSessionEvent({ type: 'message', message: `Process exited unexpectedly: ${detail}` });
|
||||
continue;
|
||||
}
|
||||
} finally {
|
||||
|
||||
+38
-17
@@ -88,6 +88,7 @@ export class Query implements AsyncIterableIterator<SDKMessage> {
|
||||
*/
|
||||
private async readMessages(): Promise<void> {
|
||||
const rl = createInterface({ input: this.childStdout })
|
||||
let hadError = false
|
||||
|
||||
try {
|
||||
for await (const line of rl) {
|
||||
@@ -118,9 +119,14 @@ export class Query implements AsyncIterableIterator<SDKMessage> {
|
||||
}
|
||||
await this.processExitPromise
|
||||
} catch (error) {
|
||||
hadError = true
|
||||
this.inputStream.error(error as Error)
|
||||
} finally {
|
||||
this.inputStream.done()
|
||||
// Only call done() on clean exit - calling done() after error()
|
||||
// would mask the error since Stream.next() checks isDone before hasError
|
||||
if (!hadError) {
|
||||
this.inputStream.done()
|
||||
}
|
||||
this.cleanupControllers()
|
||||
rl.close()
|
||||
}
|
||||
@@ -381,35 +387,50 @@ export function query(config: {
|
||||
process.on('exit', cleanup)
|
||||
|
||||
// Handle process exit
|
||||
const processExitPromise = new Promise<void>((resolve) => {
|
||||
child.on('close', (code) => {
|
||||
if (config.options?.abort?.aborted) {
|
||||
query.setError(new AbortError('Claude Code process aborted by user'))
|
||||
}
|
||||
if (code !== 0) {
|
||||
query.setError(new Error(`Claude Code process exited with code ${code}`))
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
let resolveExit: () => void
|
||||
let rejectExit: (error: Error) => void
|
||||
const processExitPromise = new Promise<void>((resolve, reject) => {
|
||||
resolveExit = resolve
|
||||
rejectExit = reject
|
||||
})
|
||||
|
||||
// Create query instance
|
||||
// Create query instance BEFORE registering close handler
|
||||
// to avoid temporal dependency on `query` variable
|
||||
const query = new Query(childStdin, child.stdout, processExitPromise, canCallTool)
|
||||
|
||||
// Register close handler - query is safely defined now
|
||||
child.on('close', (code) => {
|
||||
if (config.options?.abort?.aborted) {
|
||||
const err = new AbortError('Claude Code process aborted by user')
|
||||
query.setError(err)
|
||||
rejectExit(err)
|
||||
} else if (code !== 0) {
|
||||
const err = new Error(`Claude Code process exited with code ${code}`)
|
||||
query.setError(err)
|
||||
rejectExit(err)
|
||||
} else {
|
||||
resolveExit()
|
||||
}
|
||||
})
|
||||
|
||||
// Handle process errors
|
||||
child.on('error', (error) => {
|
||||
cleanupMcpConfig?.()
|
||||
if (config.options?.abort?.aborted) {
|
||||
query.setError(new AbortError('Claude Code process aborted by user'))
|
||||
const err = new AbortError('Claude Code process aborted by user')
|
||||
query.setError(err)
|
||||
rejectExit(err)
|
||||
} else {
|
||||
query.setError(new Error(`Failed to spawn Claude Code process: ${error.message}`))
|
||||
const err = new Error(`Failed to spawn Claude Code process: ${error.message}`)
|
||||
query.setError(err)
|
||||
rejectExit(err)
|
||||
}
|
||||
})
|
||||
|
||||
// Cleanup on exit
|
||||
processExitPromise.finally(() => {
|
||||
// Cleanup on exit (catch rejection to avoid unhandled promise warning)
|
||||
processExitPromise.catch(() => {}).finally(() => {
|
||||
cleanup()
|
||||
process.removeListener('exit', cleanup)
|
||||
config.options?.abort?.removeEventListener('abort', cleanup)
|
||||
if (process.env.CLAUDE_SDK_MCP_SERVERS) {
|
||||
delete process.env.CLAUDE_SDK_MCP_SERVERS
|
||||
|
||||
Reference in New Issue
Block a user