fix: sub-agent hang/crash stranding runs mid-phase with no save
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:
2026-08-11 08:47:12 -04:00
parent 6d23b04ef6
commit 8768f9de97
7 changed files with 435 additions and 48 deletions

View File

@@ -92,8 +92,11 @@ ${scopeRulesMarkdown()}
3. Apply the rubric below to each comment and classify it: RESTATE, VERBOSE,
WHY, or OK.
4. Write a findings report to \`${findingsFile}\` with per-file line refs.
5. Return the findings report text as your final message (same content as the
file). The host captures it as the analysis-phase findings.
5. Return a ONE-LINE summary as your final message, e.g.
\`<count> comment smell(s) across <files> file(s).\` The full report lives
in findings.md — do NOT regenerate the report text in your final message
(regenerating a large report doubles the fragile output right after the
file write and can stall the run).
${RUBRIC}
@@ -110,7 +113,8 @@ ${RUBRIC}
\`\`\`
If no smells are found, write \`# comments — findings\n\n0 comment smell(s).\`
and return that text. Always create findings.md so the run has an artifact.
and return \`0 comment smell(s).\` as your final message. Always create
findings.md so the run has an artifact.
Write the report under \`${outDir}\` (create directories as needed).
`;
@@ -128,6 +132,7 @@ export function buildCommentsFixTask(
): string {
const outDir = commentsArtifactDir(scope);
const changesFile = changesPath(scope);
const findingsFile = findingsPath(scope);
return `# Task: comments hygiene fix
You are running the **comments** hygiene fix phase.
@@ -136,6 +141,9 @@ You are running the **comments** hygiene fix phase.
- Fix target: \`${scope.target}\`
## Input: scan findings
Read the detailed per-file findings from \`${findingsFile}\` (created by the
scan phase). If that file is missing or unreadable, fall back to the findings
text below:
${findings.trim().length > 0 ? findings : "(no findings text provided)"}
## What to do