fix(llmloop): propagate grace-round task_done completion - #1019
Open
AllenMuu wants to merge 1 commit into
Open
Conversation
Contributor
|
🔍 OpenCodeReview found 2 issue(s) in this PR.
|
AllenMuu
force-pushed
the
feat/grace-round-completion
branch
from
August 20, 2026 09:13
bb4b8bb to
30089ab
Compare
The grace round that runs after the tool-request budget is exhausted dropped the result of its task_done call: a model that spent all its rounds on context tools and then called task_done(DONE) in the grace round was still recorded as incomplete, so callers kept the misleading "main_task did not complete before stopping" failure instead of a reusable checkpoint. The grace round now mirrors the main loop's terminal semantics: - task_done(DONE) completes the run; comments issued alongside it in the same round still land. - task_done(FAILED) surfaces as a task failure error and returns immediately without executing the rest of the round. - Transient LLM errors are logged and swallowed so budget exhaustion stays the recorded stop cause. - Cancellation wins over completion at every window and surfaces as ctx.Err() so callers classify the item as FailureCancelled instead of FailureBudget: before the grace call, inside the LLM call, after the response arrives, between the round's tool calls, and before returning completion. Regression coverage includes the completion propagation, same-round ordering for DONE and FAILED, the swallowed LLM error, all five cancellation windows, and cross-protocol propagation over the OpenAI chat-completions, Anthropic messages, and OpenAI responses formats.
AllenMuu
force-pushed
the
feat/grace-round-completion
branch
from
August 20, 2026 09:46
30089ab to
38d7987
Compare
AllenMuu
marked this pull request as ready for review
August 20, 2026 10:35
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
When the tool-request budget is exhausted,
RunPerFileruns one grace round, giving the model a final chance to submit findings.Previously, the grace round discarded the result of
task_done: a model that spent its normal rounds on context tools and then correctly calledtask_done(DONE)in the grace round was still recorded as incomplete. Callers therefore kept the misleadingmain_task did not complete before stoppingresult instead of recording a reusable checkpoint.The grace round now mirrors the main loop's terminal semantics:
task_done(DONE)completes the run.RunPerFilereturnscompleted=truewithStopNone; remaining calls in the same round still execute, so accompanyingcode_commentcalls land.task_done(FAILED)returns a task-failure error. The round stops immediately and does not execute subsequent calls, intentionally matching the main loop's DONE/FAILED asymmetry.ctx.Err()at every grace-round window. Callers classify it asFailureCancelled, consistently with the main loop and run-level cancellation handling.ErrTaskFailed, allowing callers to identify them witherrors.Iswhile preserving the existing error text.Type of Change
How Has This Been Tested?
make testpasses locallyUnit tests in
internal/llmloop:TestRunPerFile_GraceRoundTaskDoneCompletes— the core regression:task_done(DONE)in the grace round completes the run.TestRunPerFile_GraceRoundCommentAfterTaskDoneStillLands/TestRunPerFile_GraceRoundTaskDoneFailedSurfaces— same-round ordering semantics for DONE and FAILED, including theErrTaskFailedsentinel.TestRunPerFile_GraceRoundLLMErrorSwallowed— a transient LLM outage keeps budget exhaustion as the stop cause.TestRunPerFile_GraceRoundCancelled{DuringLLMCall,AfterResponseDiscarded,BetweenToolCalls,InFinalCallDiscardsCompletion}andTestRunPerFile_GraceRoundSkippedWhenContextCancelled— each grace-round cancellation window surfacescontext.Canceled.grace_round_protocol_test.go— propagation verified over the OpenAI chat-completions, Anthropic messages, and OpenAI responses wire formats, with normal-round control groups.Manual verification used real LLM runs against a scratch Git repository with a staged Go diff containing a swallowed-error bug:
v1.9.8baseline and this branch. For a deterministic grace-round trigger, each binary used a local test-only template change settingMAX_TOOL_REQUEST_TIMES=1; that change is not part of this PR. Both binaries ranocr review --audience agent --no-filteragainst the same repository.Review failed: 0 finding(s); 1 of 1 selected item(s) failed— the grace round'stask_done(DONE)was dropped, and the misleading error told the user to "check your LLM configuration and API key".Review complete: 1 finding(s) across 1 selected item(s)— thecode_commentsubmitted in the grace round landed, correctly flagging the swallowed error with a suggested fix; completion propagated instead of failing the item.Review failed (cancelled)and the per-item retry report recordedserver.go / main_task #1: cancelled; item-level and run-level attribution agree rather than classifying the item as budget exhaustion. The precise grace-round cancellation windows are covered by unit tests because LLM latency jitter makes them hard to trigger deterministically in a live run.Downstream coverage: the new scan integration test verifies checkpoint persistence and resume reuse. Existing
internal/agentandinternal/scansuites also pass.Additional local verification:
go test -raceoninternal/llmlooppasses.make checkpasses locally.make coveragepasses, meeting the repository's 90% threshold.Checklist
go fmt,go vet)Related Issues
Closes #1018