fix(targets): harden adapter lifecycle and droid model edge cases

This commit is contained in:
Tam Nhu Tran
2026-02-17 04:09:48 +07:00
parent 02af8d5737
commit 3da3407f9a
12 changed files with 556 additions and 101 deletions
+2 -11
View File
@@ -7,7 +7,7 @@
import { spawn, spawnSync, ChildProcess } from 'child_process';
import { ErrorManager } from './error-manager';
import { getWebSearchHookEnv } from './websearch-manager';
import { forwardSignals } from './signal-forwarder';
import { wireChildProcessSignals } from './signal-forwarder';
/**
* Strip ANTHROPIC_* env vars from an environment object.
@@ -128,16 +128,7 @@ export function execClaude(
});
}
const cleanupSignalHandlers = forwardSignals(child);
child.on('exit', (code, signal) => {
cleanupSignalHandlers();
if (signal) process.kill(process.pid, signal as NodeJS.Signals);
else process.exit(code || 0);
});
child.on('error', async (err: NodeJS.ErrnoException) => {
cleanupSignalHandlers();
wireChildProcessSignals(child, async (err: NodeJS.ErrnoException) => {
if (err.code === 'EACCES') {
console.error(`[X] Claude CLI is not executable: ${claudeCli}`);
console.error(' Check file permissions and executable bit.');
+44
View File
@@ -31,3 +31,47 @@ export function forwardSignals(child: ChildProcess): () => void {
process.removeListener('SIGHUP', forwardSighup);
};
}
export type ChildProcessErrorHandler = (err: NodeJS.ErrnoException) => void | Promise<void>;
export type ChildProcessExitHandler = (code: number | null, signal: NodeJS.Signals | null) => void;
function defaultExitHandler(code: number | null, signal: NodeJS.Signals | null): void {
if (signal) process.kill(process.pid, signal);
else process.exit(code || 0);
}
/**
* Attach shared signal-forwarding lifecycle handlers to a child process.
* Ensures signal listeners are always cleaned up on child exit/error.
*/
export function wireChildProcessSignals(
child: ChildProcess,
onError: ChildProcessErrorHandler,
onExit: ChildProcessExitHandler = defaultExitHandler
): void {
const cleanupSignalHandlers = forwardSignals(child);
let settled = false;
const settle = (): boolean => {
if (settled) return false;
settled = true;
cleanupSignalHandlers();
return true;
};
child.on('exit', (code, signal) => {
if (!settle()) return;
onExit(code, signal);
});
child.on('error', async (err: NodeJS.ErrnoException) => {
if (!settle()) return;
try {
await onError(err);
} catch (handlerErr) {
const message = handlerErr instanceof Error ? handlerErr.message : String(handlerErr);
console.error(`[X] Failed to handle child process error: ${message}`);
process.exit(1);
}
});
}