Intent
Stop silently completing a run with empty output when a stuck-thinking model exhausts empty-response retries; fail the run instead, and stop burning step/Turn budget on retries.
Plan reference
Source finding
Empty-response retry exhaustion silently completes the run with empty output (runner_step_engine.go:598-648). When consecutiveEmptyResponses >= maxEmptyRetries, control falls through (no else) and calls r.completeRun(runID, ""). A model stuck thinking returns "completed" with empty output — silent data loss. Also, the continue for retries runs the for-loop's step++, so retries consume the step/Turn budget.
Scope
- Allowed files (test):
internal/harness/runner_empty_response_test.go (extend)
- Allowed files (impl):
internal/harness/runner_step_engine.go
- Dependencies: none
- Complexity: low — Risk: low
Acceptance criteria
Use slice T03 in the plan as detailed acceptance criteria. Summary:
- Failing tests written first, compile and fail on
main.
- After
maxEmptyRetries consecutive empty responses, run status is RunStatusFailed with reason "max_empty_responses" — not RunStatusCompleted. Today a test asserts RunStatusCompleted (the bug); flip the assertion and delete/retarget the broken-behavior test.
- Three consecutive empty responses consume step budget of 1, not 4 — prove retries run on a nested loop, not the outer
step++.
- Impl: replace the bare
continue on the empty-response branch with an inner for retry loop that does not advance step; when retries exhaust, call r.failRun(runID, errors.New("runner: max consecutive empty responses reached")) and return.
go test ./internal/harness/... -race green; ./scripts/test-regression.sh green.
Guardrails
- Strict TDD; failing regression test must compile and fail on
main first.
- Worktree flow; own branch, merge via
verify-and-merge.sh.
- Update
.context/harness-reliability/tracker.md on pick-up and merge.
- Deterministic tests only.
- Append engineering-log entry on merge.
Intent
Stop silently completing a run with empty output when a stuck-thinking model exhausts empty-response retries; fail the run instead, and stop burning step/Turn budget on retries.
Plan reference
docs/plans/2026-06-24-harness-reliability-plan.md— slice T03Source finding
Empty-response retry exhaustion silently completes the run with empty output (
runner_step_engine.go:598-648). WhenconsecutiveEmptyResponses >= maxEmptyRetries, control falls through (noelse) and callsr.completeRun(runID, ""). A model stuck thinking returns "completed" with empty output — silent data loss. Also, thecontinuefor retries runs the for-loop'sstep++, so retries consume the step/Turn budget.Scope
internal/harness/runner_empty_response_test.go(extend)internal/harness/runner_step_engine.goAcceptance criteria
Use slice T03 in the plan as detailed acceptance criteria. Summary:
main.maxEmptyRetriesconsecutive empty responses, run status isRunStatusFailedwith reason"max_empty_responses"— notRunStatusCompleted. Today a test assertsRunStatusCompleted(the bug); flip the assertion and delete/retarget the broken-behavior test.step++.continueon the empty-response branch with an innerforretry loop that does not advancestep; when retries exhaust, callr.failRun(runID, errors.New("runner: max consecutive empty responses reached"))andreturn.go test ./internal/harness/... -racegreen;./scripts/test-regression.shgreen.Guardrails
mainfirst.verify-and-merge.sh..context/harness-reliability/tracker.mdon pick-up and merge.