MCPcopy Create free account
hub / github.com/tiann/hapi / onHappySessionWebhook

Function onHappySessionWebhook

cli/src/runner/run.ts:216–281  ·  view source on GitHub ↗
(sessionId: string, sessionMetadata: Metadata)

Source from the content-addressed store, hash-verified

214
215 // Handle webhook from HAPI session reporting itself
216 const onHappySessionWebhook = (sessionId: string, sessionMetadata: Metadata) => {
217 logger.debugLargeJson(`[RUNNER RUN] Session reported`, sessionMetadata);
218
219 const pid = sessionMetadata.hostPid;
220 if (!pid) {
221 logger.debug(`[RUNNER RUN] Session webhook missing hostPid for sessionId: ${sessionId}`);
222 return;
223 }
224
225 logger.debug(`[RUNNER RUN] Session webhook: ${sessionId}, PID: ${pid}, started by: ${sessionMetadata.startedBy || 'unknown'}`);
226 logger.debug(`[RUNNER RUN] Current tracked sessions before webhook: ${Array.from(pidToTrackedSession.keys()).join(', ')}`);
227
228 // Check if we already have this PID (runner-spawned)
229 const existingSession = pidToTrackedSession.get(pid);
230
231 if (existingSession && existingSession.startedBy === 'runner') {
232 // Update runner-spawned session with reported data
233 existingSession.happySessionId = sessionId;
234 existingSession.happySessionMetadataFromLocalWebhook = sessionMetadata;
235 logger.debug(`[RUNNER RUN] Updated runner-spawned session ${sessionId} with metadata`);
236
237 // Resolve any awaiter for this PID
238 const awaiter = pidToAwaiter.get(pid);
239 if (awaiter) {
240 pidToAwaiter.delete(pid);
241 pidToErrorAwaiter.delete(pid);
242 awaiter(existingSession);
243 logger.debug(`[RUNNER RUN] Resolved session awaiter for PID ${pid}`);
244 }
245 } else if (!existingSession) {
246 // No tracked session for this PID. Two possibilities:
247 // 1. The child was spawned externally from a terminal (legitimate).
248 // 2. The child was runner-spawned but already had its tracking
249 // entry removed because its webhook arrived after the timeout
250 // (orphaned / ghost-session case).
251 //
252 // Differentiate via the webhook's own `startedBy` field: genuine
253 // terminal-launched children report `startedBy: 'terminal'`, so
254 // anything claiming `'runner'` here must be the second case and
255 // should be ignored + terminated instead of silently promoted.
256 if (sessionMetadata.startedBy === 'runner') {
257 logger.debug(
258 `[RUNNER RUN] Ignoring late webhook from orphaned runner-spawned PID ${pid} (session ${sessionId}). Terminating child.`
259 );
260 // Use killProcess (SIGTERM → SIGKILL escalation) rather than a
261 // bare process.kill() so the orphan is reliably reaped even if
262 // it ignores SIGTERM. We don't have a ChildProcess reference
263 // here (tracking entry was already removed by the timeout
264 // handler), so tree-kill via killProcessByChildProcess is not
265 // available — but the timeout handler should have already
266 // tree-killed the process group; this is defence-in-depth.
267 void killProcess(pid);
268 return;
269 }
270
271 // New session started externally (terminal)
272 const trackedSession: TrackedSession = {
273 startedBy: 'hapi directly - likely by user from terminal',

Callers 1

startRunnerControlServerFunction · 0.85

Calls 4

killProcessFunction · 0.90
debugLargeJsonMethod · 0.80
debugMethod · 0.80
getMethod · 0.80

Tested by

no test coverage detected