Merge pull request #1970 from pankajkgarg/codex/detect-auth-errors-in-setup
[codex] detect setup auth ping failures
This commit is contained in:
30
setup/lib/agent-ping.test.ts
Normal file
30
setup/lib/agent-ping.test.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
import { classifyPingResult } from './agent-ping.js';
|
||||||
|
|
||||||
|
describe('classifyPingResult', () => {
|
||||||
|
it('treats a normal text reply as ok', () => {
|
||||||
|
expect(classifyPingResult(0, 'pong\n')).toBe('ok');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('detects Anthropic auth errors printed as a chat reply', () => {
|
||||||
|
expect(
|
||||||
|
classifyPingResult(
|
||||||
|
0,
|
||||||
|
'Failed to authenticate. API Error: 401 {"type":"error","error":{"type":"authentication_error","message":"Invalid bearer token"}}',
|
||||||
|
),
|
||||||
|
).toBe('auth_error');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('detects auth errors on stderr too', () => {
|
||||||
|
expect(classifyPingResult(1, '', 'Authentication error')).toBe('auth_error');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('preserves socket errors', () => {
|
||||||
|
expect(classifyPingResult(2, '')).toBe('socket_error');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('treats empty output as no reply', () => {
|
||||||
|
expect(classifyPingResult(0, '')).toBe('no_reply');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -13,7 +13,21 @@
|
|||||||
*/
|
*/
|
||||||
import { spawn } from 'child_process';
|
import { spawn } from 'child_process';
|
||||||
|
|
||||||
export type PingResult = 'ok' | 'no_reply' | 'socket_error';
|
export type PingResult = 'ok' | 'no_reply' | 'socket_error' | 'auth_error';
|
||||||
|
|
||||||
|
export function classifyPingResult(exitCode: number | null, stdout: string, stderr = ''): PingResult {
|
||||||
|
const output = `${stdout}\n${stderr}`;
|
||||||
|
if (
|
||||||
|
/Invalid bearer token/i.test(output) ||
|
||||||
|
/authentication[_ ]error/i.test(output) ||
|
||||||
|
/Failed to authenticate/i.test(output)
|
||||||
|
) {
|
||||||
|
return 'auth_error';
|
||||||
|
}
|
||||||
|
if (exitCode === 2) return 'socket_error';
|
||||||
|
if (exitCode === 0 && stdout.trim().length > 0) return 'ok';
|
||||||
|
return 'no_reply';
|
||||||
|
}
|
||||||
|
|
||||||
export function pingCliAgent(timeoutMs = 30_000): Promise<PingResult> {
|
export function pingCliAgent(timeoutMs = 30_000): Promise<PingResult> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
@@ -21,6 +35,7 @@ export function pingCliAgent(timeoutMs = 30_000): Promise<PingResult> {
|
|||||||
stdio: ['ignore', 'pipe', 'pipe'],
|
stdio: ['ignore', 'pipe', 'pipe'],
|
||||||
});
|
});
|
||||||
let stdout = '';
|
let stdout = '';
|
||||||
|
let stderr = '';
|
||||||
let settled = false;
|
let settled = false;
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
if (settled) return;
|
if (settled) return;
|
||||||
@@ -32,13 +47,14 @@ export function pingCliAgent(timeoutMs = 30_000): Promise<PingResult> {
|
|||||||
child.stdout.on('data', (chunk: Buffer) => {
|
child.stdout.on('data', (chunk: Buffer) => {
|
||||||
stdout += chunk.toString('utf-8');
|
stdout += chunk.toString('utf-8');
|
||||||
});
|
});
|
||||||
|
child.stderr.on('data', (chunk: Buffer) => {
|
||||||
|
stderr += chunk.toString('utf-8');
|
||||||
|
});
|
||||||
child.on('close', (code) => {
|
child.on('close', (code) => {
|
||||||
if (settled) return;
|
if (settled) return;
|
||||||
settled = true;
|
settled = true;
|
||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
if (code === 2) resolve('socket_error');
|
resolve(classifyPingResult(code, stdout, stderr));
|
||||||
else if (code === 0 && stdout.trim().length > 0) resolve('ok');
|
|
||||||
else resolve('no_reply');
|
|
||||||
});
|
});
|
||||||
child.on('error', () => {
|
child.on('error', () => {
|
||||||
if (settled) return;
|
if (settled) return;
|
||||||
|
|||||||
@@ -220,7 +220,7 @@ export async function run(_args: string[]): Promise<void> {
|
|||||||
|
|
||||||
// 7. End-to-end: ping the CLI agent and confirm it replies. Only run if
|
// 7. End-to-end: ping the CLI agent and confirm it replies. Only run if
|
||||||
// everything upstream looks healthy, since a broken socket would just hang.
|
// everything upstream looks healthy, since a broken socket would just hang.
|
||||||
let agentPing: 'ok' | 'no_reply' | 'socket_error' | 'skipped' = 'skipped';
|
let agentPing: 'ok' | 'no_reply' | 'socket_error' | 'auth_error' | 'skipped' = 'skipped';
|
||||||
if (service === 'running' && registeredGroups > 0) {
|
if (service === 'running' && registeredGroups > 0) {
|
||||||
log.info('Pinging CLI agent');
|
log.info('Pinging CLI agent');
|
||||||
agentPing = await pingCliAgent();
|
agentPing = await pingCliAgent();
|
||||||
|
|||||||
Reference in New Issue
Block a user