Merge PR #147 from aakhter/cod-168-pty-exit-breaker: scrub TMUX vars + PTY-exit circuit breaker (COD-115/COD-118)

Includes review fixes: breaker reset only on explicit clearBreaker restarts (auto-reattach never clears), trip observability survives listener detach, push notification wired into PUSH_EVENT_MAP.

# Conflicts:
#	src/session.ts
This commit is contained in:
Codeman maintainer
2026-07-12 20:07:19 +02:00
17 changed files with 743 additions and 19 deletions
+4
View File
@@ -246,6 +246,10 @@ export function registerRespawnRoutes(
}
}
// Re-attach listener wiring if a prior PTY exit detached it (the wiring exit
// handler removes ALL session listeners; idempotent — no-op while still attached).
await ctx.setupSessionListeners(session);
// Start interactive session
await session.startInteractive();
getLifecycleLog().log({
+25
View File
@@ -34,6 +34,7 @@ import {
FlickerFilterSchema,
QuickRunSchema,
QuickStartSchema,
InteractiveStartSchema,
} from '../schemas.js';
import {
autoConfigureRalph,
@@ -622,6 +623,14 @@ export function registerSessionRoutes(
app.post('/api/sessions/:id/interactive', async (req) => {
const { id } = req.params as { id: string };
// Body is optional (auto-reattach callers send none) — same idiom as /interactive-respawn.
const bodyResult = req.body
? InteractiveStartSchema.safeParse(req.body)
: { success: true as const, data: {} as { clearBreaker?: boolean } };
if (!bodyResult.success) {
return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'Invalid request body');
}
const { clearBreaker } = bodyResult.data;
const session = findSessionOrFail(ctx, id);
if (session.isBusy()) {
@@ -644,6 +653,20 @@ export function registerSessionRoutes(
}
}
// COD-118: ONLY an explicit user-initiated restart (body {clearBreaker:true})
// clears a tripped PTY-exit circuit breaker. This endpoint is ALSO the frontend's
// automatic re-attach path (selectSession auto-POSTs it for any pid===null
// session), so an unconditional reset here would re-arm the exact crash loop
// the breaker exists to stop — auto-reattach sends no body and must not clear.
if (clearBreaker) {
session.resetRespawnBreaker();
}
// Re-attach listener wiring if a prior PTY exit detached it: the wiring exit
// handler removes ALL session listeners (incl. respawnBreakerTripped), and only
// session-create/boot-recovery paths ran setupSessionListeners before this fix —
// without this, a re-attached session's SSE/terminal/trip events go unobserved.
// setupSessionListeners is idempotent (no-op while refs are still attached).
await ctx.setupSessionListeners(session);
await session.startInteractive();
getLifecycleLog().log({
event: 'started',
@@ -671,6 +694,8 @@ export function registerSessionRoutes(
}
try {
// Re-attach listener wiring if a prior PTY exit detached it (see /interactive).
await ctx.setupSessionListeners(session);
await session.startShell();
getLifecycleLog().log({
event: 'started',