fix: sub-agent hang/crash stranding runs mid-phase with no save
Some checks failed
port-to-omp / port (push) Failing after 2s
Some checks failed
port-to-omp / port (push) Failing after 2s
A stalled sub-agent (provider stream never settling after the final tool call, or a throwing session-event listener recursing through the SDK's run-failure path to stack overflow) left the phase stuck in_progress with no state save, no error, and no completion message — observed twice in freno-dev, both times after comments wrote findings.md. - agent-runner: 60-min settle watchdog on every agent phase (PYGIENIUM_AGENT_TIMEOUT_MS, env-tunable); timeout disposes the session and fails the phase loudly with a /pygienium-resume hint instead of hanging the check-runner's await forever. - agent-runner: applySessionEvent — the session.subscribe listener can no longer throw into the SDK event pipeline (guards for partial/malformed events, optional-chained message_update, throwing chat forwarder). - comments: scan no longer regenerates the full report as its final message (findings.md is the source of truth); fix phase reads findings.md with embedded fallback. - check-runner: failing state-save inside the catch path can't double- fault or escape as an unhandled rejection; completion posting is best-effort. - tests: watchdog timeout test, comments task-text regression tests, applySessionEvent malformed-shape unit tests. 146 pass, tsc clean. - README: document PYGIENIUM_AGENT_TIMEOUT_MS.
This commit is contained in:
@@ -142,7 +142,12 @@ export async function runCheck(
|
||||
): Promise<CheckRunOutcome> {
|
||||
const startMs = Date.now();
|
||||
const outcome = await runCheckImpl(opts);
|
||||
postCheckCompletion(opts, outcome, Date.now() - startMs);
|
||||
try {
|
||||
postCheckCompletion(opts, outcome, Date.now() - startMs);
|
||||
} catch {
|
||||
// Completion posting is best-effort chat UI: a renderer or send
|
||||
// failure must never reject the run after its state was persisted.
|
||||
}
|
||||
return outcome;
|
||||
}
|
||||
|
||||
@@ -383,7 +388,16 @@ async function runCheckImplInner(
|
||||
error = err instanceof Error ? err.message : String(err);
|
||||
markCheckStatus(state, check.name, "failed", error);
|
||||
markRunStatus(state, reconcileRunStatus(state));
|
||||
await saveRunState(state);
|
||||
try {
|
||||
await saveRunState(state);
|
||||
} catch (saveErr) {
|
||||
// A failing state save inside the error path must not mask the
|
||||
// original error or escape as an unhandled rejection (which would
|
||||
// kill the run with nothing persisted or reported).
|
||||
error += `; (also failed to persist run-state: ${
|
||||
saveErr instanceof Error ? saveErr.message : String(saveErr)
|
||||
})`;
|
||||
}
|
||||
return { status: "failed", error, findings, changes, state };
|
||||
} finally {
|
||||
strip.done();
|
||||
|
||||
Reference in New Issue
Block a user