mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-03 12:21:20 +00:00
refactor(cliproxy): remove orphaned bg keepalive wiring (#1353)
Red-team review of #1340 found that backgroundProbe / shouldKeepSessionProxiesAlive / startKeepAliveWatcher and related types were never reachable: the sole caller (claude-launcher.ts) passed no hasBackgroundWorkerUsingBaseUrl detector, so backgroundProbe was always undefined and the keepalive branch never executed. Remove the unreachable scaffold: - Delete CLAUDE_BG_WORKER_ARGS, hasClaudeBackgroundWorkerUsingBaseUrl{,InProcessList}, shouldKeepSessionProxiesAlive, ShouldKeepSessionProxiesAliveOptions, and the SessionProxyKeepAliveOptions interface from session-bridge.ts - Remove the backgroundProbe/startKeepAliveWatcher block and keepalive exit branch from setupCleanupHandlers; the function now always calls stopSessionResources on Claude exit - Remove backgroundKeepAliveBaseUrl wiring from claude-launcher.ts - Remove the execFileSync import (was only used by the deleted ps-based detector) - Update test file: remove tests for deleted exports, keep placeholder A trusted bg-worker detector can be re-added in a future PR if the feature is actually needed; the keepalive logic in shouldKeepSessionProxiesAlive can be restored then.
This commit is contained in:
@@ -163,9 +163,6 @@ export async function launchClaude(context: ClaudeLaunchContext): Promise<ChildP
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasSessionProxy = Boolean(codexReasoningProxy || toolSanitizationProxy || httpsTunnel);
|
|
||||||
const backgroundKeepAliveBaseUrl = hasSessionProxy ? tracedEnv.ANTHROPIC_BASE_URL : undefined;
|
|
||||||
|
|
||||||
// Wire cleanup handlers (process exit, SIGINT, SIGTERM, proxy teardown)
|
// Wire cleanup handlers (process exit, SIGINT, SIGTERM, proxy teardown)
|
||||||
setupCleanupHandlers(
|
setupCleanupHandlers(
|
||||||
claude,
|
claude,
|
||||||
@@ -174,8 +171,7 @@ export async function launchClaude(context: ClaudeLaunchContext): Promise<ChildP
|
|||||||
codexReasoningProxy,
|
codexReasoningProxy,
|
||||||
toolSanitizationProxy,
|
toolSanitizationProxy,
|
||||||
httpsTunnel,
|
httpsTunnel,
|
||||||
verbose,
|
verbose
|
||||||
{ backgroundKeepAliveBaseUrl }
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return claude;
|
return claude;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
* - Startup lock coordination
|
* - Startup lock coordination
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ChildProcess, execFileSync } from 'child_process';
|
import { ChildProcess } from 'child_process';
|
||||||
import { info, warn } from '../../utils/ui';
|
import { info, warn } from '../../utils/ui';
|
||||||
import { getInstalledCliproxyVersion } from '../binary-manager';
|
import { getInstalledCliproxyVersion } from '../binary-manager';
|
||||||
import { CLIProxyBackend } from '../types';
|
import { CLIProxyBackend } from '../types';
|
||||||
@@ -33,70 +33,6 @@ export interface ProxySessionResult {
|
|||||||
shouldSpawn: boolean;
|
shouldSpawn: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SessionProxyKeepAliveOptions {
|
|
||||||
backgroundKeepAliveBaseUrl?: string;
|
|
||||||
keepAlivePollIntervalMs?: number;
|
|
||||||
hasBackgroundWorkerUsingBaseUrl?: (baseUrl: string) => boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ShouldKeepSessionProxiesAliveOptions {
|
|
||||||
code: number | null;
|
|
||||||
signal: NodeJS.Signals | null;
|
|
||||||
backgroundKeepAliveBaseUrl?: string;
|
|
||||||
hasBackgroundWorkerUsingBaseUrl: (baseUrl: string) => boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const CLAUDE_BG_WORKER_ARGS = ['--bg-spare', '--bg-pty-host'] as const;
|
|
||||||
|
|
||||||
export function hasClaudeBackgroundWorkerUsingBaseUrlInProcessList(
|
|
||||||
processList: string,
|
|
||||||
baseUrl: string
|
|
||||||
): boolean {
|
|
||||||
const envNeedle = `ANTHROPIC_BASE_URL=${baseUrl}`;
|
|
||||||
return processList
|
|
||||||
.split(/\r?\n/)
|
|
||||||
.some(
|
|
||||||
(line) => line.includes(envNeedle) && CLAUDE_BG_WORKER_ARGS.some((arg) => line.includes(arg))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function hasClaudeBackgroundWorkerUsingBaseUrl(baseUrl: string): boolean {
|
|
||||||
if (process.platform === 'win32') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const args of [['auxEww'], ['auxeww'], ['eww', '-axo', 'pid=,command=']]) {
|
|
||||||
try {
|
|
||||||
const processList = execFileSync('ps', args, {
|
|
||||||
encoding: 'utf8',
|
|
||||||
stdio: ['ignore', 'pipe', 'ignore'],
|
|
||||||
});
|
|
||||||
if (hasClaudeBackgroundWorkerUsingBaseUrlInProcessList(processList, baseUrl)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function shouldKeepSessionProxiesAlive(
|
|
||||||
options: ShouldKeepSessionProxiesAliveOptions
|
|
||||||
): boolean {
|
|
||||||
if (options.code !== 0 || options.signal) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const baseUrl = options.backgroundKeepAliveBaseUrl;
|
|
||||||
if (!baseUrl) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return options.hasBackgroundWorkerUsingBaseUrl(baseUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check for existing proxy and handle version mismatch, or determine if new spawn needed
|
* Check for existing proxy and handle version mismatch, or determine if new spawn needed
|
||||||
*/
|
*/
|
||||||
@@ -244,8 +180,7 @@ export function setupCleanupHandlers(
|
|||||||
codexReasoningProxy: unknown,
|
codexReasoningProxy: unknown,
|
||||||
toolSanitizationProxy: unknown,
|
toolSanitizationProxy: unknown,
|
||||||
httpsTunnel: unknown,
|
httpsTunnel: unknown,
|
||||||
verbose: boolean,
|
verbose: boolean
|
||||||
keepAliveOptions: SessionProxyKeepAliveOptions = {}
|
|
||||||
): void {
|
): void {
|
||||||
const log = (msg: string) => {
|
const log = (msg: string) => {
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
@@ -275,20 +210,6 @@ export function setupCleanupHandlers(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Security hardening: only allow keepalive when caller provides an explicit,
|
|
||||||
// trusted detector. Falling back to global process-list probing is spoofable.
|
|
||||||
const backgroundProbe = keepAliveOptions.hasBackgroundWorkerUsingBaseUrl;
|
|
||||||
|
|
||||||
const startKeepAliveWatcher = (baseUrl: string) => {
|
|
||||||
const timer = setInterval(() => {
|
|
||||||
if (backgroundProbe?.(baseUrl)) return;
|
|
||||||
log('No Claude bg worker is using the session proxy; cleaning up');
|
|
||||||
stopSessionResources();
|
|
||||||
process.exit(0);
|
|
||||||
}, keepAliveOptions.keepAlivePollIntervalMs ?? 30000);
|
|
||||||
timer.unref();
|
|
||||||
};
|
|
||||||
|
|
||||||
const cleanup = () => {
|
const cleanup = () => {
|
||||||
log('Parent signal received, cleaning up');
|
log('Parent signal received, cleaning up');
|
||||||
stopSessionResources();
|
stopSessionResources();
|
||||||
@@ -297,24 +218,6 @@ export function setupCleanupHandlers(
|
|||||||
|
|
||||||
claude.on('exit', (code, signal) => {
|
claude.on('exit', (code, signal) => {
|
||||||
log(`Claude exited: code=${code}, signal=${signal}`);
|
log(`Claude exited: code=${code}, signal=${signal}`);
|
||||||
const backgroundKeepAliveBaseUrl = keepAliveOptions.backgroundKeepAliveBaseUrl;
|
|
||||||
|
|
||||||
if (
|
|
||||||
backgroundProbe &&
|
|
||||||
shouldKeepSessionProxiesAlive({
|
|
||||||
code,
|
|
||||||
signal,
|
|
||||||
backgroundKeepAliveBaseUrl,
|
|
||||||
hasBackgroundWorkerUsingBaseUrl: backgroundProbe,
|
|
||||||
}) &&
|
|
||||||
backgroundKeepAliveBaseUrl
|
|
||||||
) {
|
|
||||||
stopQuotaMonitor();
|
|
||||||
log(`Keeping session proxy alive for Claude bg worker: ${backgroundKeepAliveBaseUrl}`);
|
|
||||||
startKeepAliveWatcher(backgroundKeepAliveBaseUrl);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
stopSessionResources();
|
stopSessionResources();
|
||||||
|
|
||||||
if (signal) {
|
if (signal) {
|
||||||
|
|||||||
@@ -1,57 +1,17 @@
|
|||||||
import { describe, it, expect } from 'bun:test';
|
/**
|
||||||
import {
|
* The bg-keepalive feature (shouldKeepSessionProxiesAlive, hasClaudeBackgroundWorker*,
|
||||||
hasClaudeBackgroundWorkerUsingBaseUrlInProcessList,
|
* backgroundProbe) was removed as dead code: the sole caller (claude-launcher.ts) never
|
||||||
shouldKeepSessionProxiesAlive,
|
* provided a trusted hasBackgroundWorkerUsingBaseUrl detector, so the keepalive path was
|
||||||
} from '../../../src/cliproxy/executor/session-bridge';
|
* unreachable. The ps-based detector was also spoofable (fixed in #1340). This test file
|
||||||
|
* is kept as a placeholder to prevent the test suite from failing on missing imports.
|
||||||
|
*
|
||||||
|
* If a trusted bg-worker detector is implemented in the future, new tests belong here.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it } from 'bun:test';
|
||||||
|
|
||||||
describe('session bridge background proxy keepalive', () => {
|
describe('session bridge background proxy keepalive', () => {
|
||||||
it('keeps per-session proxies alive after a clean Claude exit when a bg worker inherited the base URL', () => {
|
it('bg keepalive wiring removed — no dead code to test', () => {
|
||||||
expect(
|
// No-op: feature scaffold removed per red-team finding on #1340.
|
||||||
shouldKeepSessionProxiesAlive({
|
|
||||||
code: 0,
|
|
||||||
signal: null,
|
|
||||||
backgroundKeepAliveBaseUrl: 'http://127.0.0.1:64314/api/provider/codex',
|
|
||||||
hasBackgroundWorkerUsingBaseUrl: () => true,
|
|
||||||
})
|
|
||||||
).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('cleans up per-session proxies when no bg worker inherited the base URL', () => {
|
|
||||||
expect(
|
|
||||||
shouldKeepSessionProxiesAlive({
|
|
||||||
code: 0,
|
|
||||||
signal: null,
|
|
||||||
backgroundKeepAliveBaseUrl: 'http://127.0.0.1:64314/api/provider/codex',
|
|
||||||
hasBackgroundWorkerUsingBaseUrl: () => false,
|
|
||||||
})
|
|
||||||
).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('cleans up per-session proxies for signaled Claude exits', () => {
|
|
||||||
expect(
|
|
||||||
shouldKeepSessionProxiesAlive({
|
|
||||||
code: null,
|
|
||||||
signal: 'SIGTERM',
|
|
||||||
backgroundKeepAliveBaseUrl: 'http://127.0.0.1:64314/api/provider/codex',
|
|
||||||
hasBackgroundWorkerUsingBaseUrl: () => true,
|
|
||||||
})
|
|
||||||
).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('detects Claude bg workers that inherited the exact base URL', () => {
|
|
||||||
const baseUrl = 'http://127.0.0.1:64314/api/provider/codex';
|
|
||||||
const processList = `
|
|
||||||
123 /opt/claude/claude.exe --bg-spare /tmp/claim.sock ANTHROPIC_BASE_URL=${baseUrl} ANTHROPIC_AUTH_TOKEN=ccs-internal-managed
|
|
||||||
456 /opt/claude/claude.exe --bg-spare /tmp/claim.sock ANTHROPIC_BASE_URL=http://127.0.0.1:62075/api/provider/codex
|
|
||||||
789 /opt/claude/claude.exe --print ANTHROPIC_BASE_URL=${baseUrl}
|
|
||||||
`;
|
|
||||||
|
|
||||||
expect(hasClaudeBackgroundWorkerUsingBaseUrlInProcessList(processList, baseUrl)).toBe(true);
|
|
||||||
expect(
|
|
||||||
hasClaudeBackgroundWorkerUsingBaseUrlInProcessList(
|
|
||||||
processList,
|
|
||||||
'http://127.0.0.1:65535/api/provider/codex'
|
|
||||||
)
|
|
||||||
).toBe(false);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user