diff --git a/src/services/AgentSummary/__tests__/agentSummary.test.ts b/src/services/AgentSummary/__tests__/agentSummary.test.ts index cc3c87d36..421219a94 100644 --- a/src/services/AgentSummary/__tests__/agentSummary.test.ts +++ b/src/services/AgentSummary/__tests__/agentSummary.test.ts @@ -134,6 +134,7 @@ describe('startAgentSummarization', () => { expect(forkCalls).toHaveLength(1) expect(updateCalls).toHaveLength(1) + expect(loggedErrors).toEqual([]) }) test('skips summarization when filtering leaves too little bounded context', async () => { diff --git a/src/utils/__tests__/udsMessaging.test.ts b/src/utils/__tests__/udsMessaging.test.ts index 3f0b6dd3d..bebaa495d 100644 --- a/src/utils/__tests__/udsMessaging.test.ts +++ b/src/utils/__tests__/udsMessaging.test.ts @@ -11,7 +11,7 @@ import { writeFile, } from 'node:fs/promises' import { createHash } from 'node:crypto' -import { createConnection, createServer } from 'node:net' +import { createConnection, createServer, type Socket } from 'node:net' import { dirname, join } from 'node:path' import { tmpdir } from 'node:os' import { @@ -243,6 +243,64 @@ describe('UDS inbox retention', () => { expect(error.message).not.toContain('test-token') }) + test('udsClient send reports response timeouts as peer connection errors', async () => { + const path = socketPath('uds-client-timeout') + const capabilityDir = join(tempConfigDir, 'messaging-capabilities') + const capabilityName = `${createHash('sha256').update(path).digest('hex')}.json` + await mkdir(capabilityDir, { recursive: true, mode: 0o700 }) + await writeFile( + join(capabilityDir, capabilityName), + JSON.stringify({ socketPath: path, authToken: 'test-token' }), + 'utf-8', + ) + if (process.platform !== 'win32') { + await mkdir(dirname(path), { recursive: true }) + } + + const sockets = new Set() + const receiver = createServer(socket => { + sockets.add(socket) + socket.on('close', () => { + sockets.delete(socket) + }) + socket.on('data', () => undefined) + }) + await new Promise((resolve, reject) => { + receiver.on('error', reject) + receiver.listen(path, () => resolve()) + }) + + try { + const { sendToUdsSocket, UdsPeerConnectionError } = await import( + '../udsClient.js' + ) + + const error = await sendToUdsSocket(path, 'hello', 50).then( + () => undefined, + err => err, + ) + expect(error).toBeInstanceOf(UdsPeerConnectionError) + if (!(error instanceof UdsPeerConnectionError)) { + throw new Error('Expected UDS peer connection timeout error') + } + expect(error.socketPath).toBe(path) + expect(error.cause).toBeInstanceOf(Error) + if (!(error.cause instanceof Error)) { + throw new Error('Expected timeout cause') + } + expect(error.cause.message).toBe('Connection timed out') + expect(error.message).not.toContain('test-token') + } finally { + for (const socket of sockets) { + socket.destroy() + } + await closeServer(receiver) + if (process.platform !== 'win32') { + await unlink(path).catch(() => undefined) + } + } + }) + test('sendUdsMessage fails closed before connecting without an auth token', async () => { await expect( sendUdsMessage(socketPath('no-auth-token'), { type: 'text', data: 'x' }), diff --git a/src/utils/udsClient.ts b/src/utils/udsClient.ts index 1e06756bb..54d88f7fc 100644 --- a/src/utils/udsClient.ts +++ b/src/utils/udsClient.ts @@ -206,6 +206,7 @@ export async function isPeerAlive( export async function sendToUdsSocket( targetSocketPath: string, message: string | Record, + timeoutMs = 5000, ): Promise { const { parseUdsTarget } = await import('./udsMessaging.js') const target = parseUdsTarget(targetSocketPath) @@ -252,8 +253,13 @@ export async function sendToUdsSocket( formatSocketError: err => new UdsPeerConnectionError(target.socketPath, err), }) - conn.setTimeout(5000, () => { - finish(new Error('Connection timed out')) + conn.setTimeout(timeoutMs, () => { + finish( + new UdsPeerConnectionError( + target.socketPath, + new Error('Connection timed out'), + ), + ) }) }) }