fix(cliproxy): prevent OAuth process hang on Qwen device code flow

- Escalate SIGTERM to SIGKILL after 3s if Go binary is stuck in blocking HTTP call
- Extend timeout to 300s for device code flows matching CLIProxy binary polling window
- Apply SIGKILL escalation to both SIGINT cleanup and timeout handlers

Closes #314
This commit is contained in:
Tam Nhu Tran
2026-02-11 01:58:26 +07:00
parent 72ee3122bd
commit 086bf958e9
+19 -5
View File
@@ -6,6 +6,19 @@
*/
import { spawn, ChildProcess } from 'child_process';
/**
* Kill process with SIGTERM, escalating to SIGKILL if it doesn't exit
*/
function killWithEscalation(proc: ChildProcess, gracePeriodMs = 3000): void {
proc.kill('SIGTERM');
const timer = setTimeout(() => {
if (proc.exitCode === null) {
proc.kill('SIGKILL');
}
}, gracePeriodMs);
proc.once('exit', () => clearTimeout(timer));
}
import { ok, fail, info, warn } from '../../utils/ui';
import { tryKiroImport } from './kiro-import';
import { CLIProxyProvider } from '../types';
@@ -333,8 +346,8 @@ export function executeOAuthProcess(options: OAuthProcessOptions): Promise<Accou
// H8: Also clear stdinKeepalive interval to prevent memory leak
const cleanup = () => {
if (stdinKeepalive) clearInterval(stdinKeepalive);
if (authProcess && !authProcess.killed) {
authProcess.kill('SIGTERM');
if (authProcess && authProcess.exitCode === null) {
killWithEscalation(authProcess);
}
};
process.on('SIGINT', cleanup);
@@ -425,7 +438,8 @@ export function executeOAuthProcess(options: OAuthProcessOptions): Promise<Accou
}, 2000);
// Timeout handling
const timeoutMs = headless ? 300000 : 120000;
// Device code flows need longer timeout to match CLIProxy binary's polling window (60 attempts × 5s = 300s)
const timeoutMs = headless || isDeviceCodeFlow ? 300000 : 120000;
const timeout = setTimeout(() => {
// H7: Clear stdin keepalive interval
if (stdinKeepalive) clearInterval(stdinKeepalive);
@@ -435,9 +449,9 @@ export function executeOAuthProcess(options: OAuthProcessOptions): Promise<Accou
authSessionEvents.removeListener('session:cancelled', handleCancel);
unregisterAuthSession(state.sessionId);
cancelProjectSelection(state.sessionId);
authProcess.kill();
killWithEscalation(authProcess);
console.log('');
console.log(fail(`OAuth timed out after ${headless ? 5 : 2} minutes`));
console.log(fail(`OAuth timed out after ${timeoutMs / 60000} minutes`));
for (const line of getTimeoutTroubleshooting(provider, callbackPort ?? null)) {
console.log(line);
}