fix(agent-cli): httpRequest rejects when the connection drops mid-body (#615 merge-time fix)

The response listened for 'data' and 'end' only. A socket closed after the
headers emits 'error' on the response and never 'end', so the promise never
settled. A one-shot `codeman agent` just exits, but the long-lived
`codeman mcp` left that tool call unanswered forever.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer committed 2026-10-13 17:47:09 +02:00
1 parent 71647e090d
commit dedf56e24b
2 files changed
+14

No files matched your search

+3
View File
@@ -302,6 +302,9 @@ export const httpRequest: ApiRequest = (ctx, options) => {
(res) => {
const chunks: Buffer[] = [];
res.on('data', (chunk: Buffer) => chunks.push(chunk));
// A connection dropped mid-body emits 'error' and never 'end'; without this the
// promise never settles (and a long-lived `codeman mcp` never answers the call).
res.on('error', reject);
res.on('end', () => {
const text = Buffer.concat(chunks).toString('utf-8');
let json: ApiEnvelope | undefined;
+11
View File
@@ -660,6 +660,13 @@ describe('httpRequest', () => {
res.writeHead(401, { 'Content-Type': 'text/plain' }).end('Unauthorized');
return;
}
if (req.url?.startsWith('/reset')) {
// Headers and half a body, then the connection drops (a restart mid-answer).
res.writeHead(200, { 'Content-Type': 'application/json' });
res.write('{"success":true,"data":');
setTimeout(() => req.socket.destroy(), 50);
return;
}
res
.writeHead(200, { 'Content-Type': 'application/json' })
.end(JSON.stringify({ success: true, data: { echo: true } }));
@@ -702,6 +709,10 @@ describe('httpRequest', () => {
expect(last.headers.authorization).toBeUndefined();
});
it('rejects, rather than hanging, when the connection drops mid-body', async () => {
await expect(httpRequest(ctx({ apiUrl }), { method: 'GET', path: '/reset', timeoutMs: 5000 })).rejects.toThrow();
});
it('keeps a plain-text body when the answer is not JSON', async () => {
const res = await httpRequest(ctx({ apiUrl }), { method: 'GET', path: '/plain' });
expect(res.status).toBe(401);