diff --git a/ops-agent/src/routes/exec.ts b/ops-agent/src/routes/exec.ts index 9a393ec..d017246 100644 --- a/ops-agent/src/routes/exec.ts +++ b/ops-agent/src/routes/exec.ts @@ -108,20 +108,33 @@ export async function execRoutes(app: FastifyInstance): Promise { sendEvent('stderr', chunk.toString()); }); - child.on('close', (code) => { - auditLog(command_key, args, code, Date.now() - startedAt); - reply.raw.write(`event: exit\ndata: ${JSON.stringify({ code })}\n\n`); - reply.raw.end(); - }); - - child.on('error', (err) => { - auditLog(command_key, args, null, Date.now() - startedAt); - reply.raw.write(`event: error\ndata: ${JSON.stringify({ message: err.message })}\n\n`); - reply.raw.end(); - }); - - req.raw.on('close', () => { - child.kill(); - }); + // Houd de route-handler open totdat het kind klaar is. Zonder dit return-t + // de async functie meteen, finaliseert Fastify de reply, en triggert dat + // `req.raw.on('close')` → `child.kill()` voordat het kind iets kon doen. + await new Promise((resolve) => { + let settled = false + const finish = () => { + if (settled) return + settled = true + resolve() + } + child.on('close', (code) => { + auditLog(command_key, args, code, Date.now() - startedAt) + reply.raw.write(`event: exit\ndata: ${JSON.stringify({ code })}\n\n`) + reply.raw.end() + finish() + }) + child.on('error', (err) => { + auditLog(command_key, args, null, Date.now() - startedAt) + reply.raw.write(`event: error\ndata: ${JSON.stringify({ message: err.message })}\n\n`) + reply.raw.end() + finish() + }) + // Detect client disconnect via response stream (niet request stream — + // die fired al direct na request body parse). + reply.raw.on('close', () => { + if (!settled) child.kill() + }) + }) }); }