{"record":{"id":"7781ca7589f8e689","repo":"slopus/happy","slug":"timeout","errorCode":null,"errorMessage":"timeout","messagePattern":"timeout","errorType":"http","errorClass":null,"httpStatus":408,"severity":"warning","filePath":"packages/happy-cli/src/claude/utils/startHookServer.ts","lineNumber":105,"sourceCode":"\n/**\n * Start a dedicated HTTP server for receiving Claude session hooks\n * \n * @param options - Server options including the session hook callback\n * @returns Promise resolving to the server instance with port info\n */\nexport async function startHookServer(options: HookServerOptions): Promise<HookServer> {\n    const { onSessionHook } = options;\n\n    return new Promise((resolve, reject) => {\n        const server: Server = createServer(async (req: IncomingMessage, res: ServerResponse) => {\n            // Only handle POST to /hook/session-start\n            if (req.method === 'POST' && req.url === '/hook/session-start') {\n                // Set timeout to prevent hanging if Claude doesn't close stdin\n                const timeout = setTimeout(() => {\n                    if (!res.headersSent) {\n                        logger.debug('[hookServer] Request timeout');\n                        res.writeHead(408).end('timeout');\n                    }\n                }, 5000);\n\n                try {\n                    const chunks: Buffer[] = [];\n                    for await (const chunk of req) {\n                        chunks.push(chunk as Buffer);\n                    }\n                    clearTimeout(timeout);\n                    \n                    const body = Buffer.concat(chunks).toString('utf-8');\n                    logger.debug('[hookServer] Received session hook:', body);\n\n                    let data: SessionHookData = {};\n                    try {\n                        data = JSON.parse(body);\n                    } catch (parseError) {\n                        logger.debug('[hookServer] Failed to parse hook data as JSON:', parseError);","sourceCodeStart":87,"sourceCodeEnd":123,"githubUrl":"https://github.com/slopus/happy/blob/b824cd0a4681d41af631a8e422a813873e4455b0/packages/happy-cli/src/claude/utils/startHookServer.ts#L87-L123","documentation":"The hook server's /hook/session-start handler enforces a 5-second timeout: if the request body isn't fully received/processed in time, it responds 408 with body 'timeout'. This guards against Claude never closing stdin.","triggerScenarios":"POST to /hook/session-start where the client doesn't close the request stream (stdin not closed) or body reading takes longer than 5 seconds; the timeout fires and writes the 408 response.","commonSituations":"Hook integrations that keep the request/stream open; large session payloads over a slow pipe; a hung Claude process that never signals end-of-input.","solutions":["Ensure the hook client closes stdin after writing the session-start payload","Send the payload in one write and end the request immediately","Increase or remove the 5s timeout if payloads are legitimately large","Check hook client logs for why the request never completed"],"exampleFix":"// before\nres.writeHead(408).end('timeout');\n// after\nreq.destroy();\nres.writeHead(408).end('timeout');","handlingStrategy":"retry","validationCode":"const payload = JSON.stringify(sessionData);\nconst res = await fetch(url, { method: 'POST', body: payload, signal: AbortSignal.timeout(4000) });\nawait res.body?.cancel(); // ensure stream completes","typeGuard":null,"tryCatchPattern":"try {\n  const res = await fetch(hookUrl, { method: 'POST', body: payload });\n  if (res.status === 408) {\n    // retry once with the payload written and stream closed promptly\n  }\n} catch (e) { /* handle */ }","preventionTips":["Always close stdin/end the request body immediately after writing the payload","Keep session-start payloads small and write them in one chunk","Treat a 408 from the hook server as a client-side hang and fix the sender","Avoid retry loops that hold the request open longer than 5s"],"tags":["http","timeout","hooks"],"backgroundTag":"request-timeout","analyzedSha":"b824cd0a4681d41af631a8e422a813873e4455b0","analyzedAt":"2026-08-31T23:12:36.205Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}