revert(session): remove the empty-step guard that mis-flagged no-arg tool calls - #1782
Open
wqymi wants to merge 1 commit into
Open
Conversation
wqymi
force-pushed
the
mimocode/revert-empty-step-guard-add-call-preamble-leak-retry-suppress
branch
4 times, most recently
from
July 20, 2026 10:23
ceac409 to
b436c52
Compare
wqymi
force-pushed
the
mimocode/revert-empty-step-guard-add-call-preamble-leak-retry-suppress
branch
2 times, most recently
from
July 27, 2026 07:54
029b1b0 to
9060bda
Compare
…tool calls
The empty/no-op tool-call loop guard (isEmptyStep + handleEmptyStep) treated a
tool call with `input: {}` as an "empty step" with NO PROGRESS. That is wrong:
plenty of legitimate tools take no arguments (e.g. `list_apps`), so a perfectly
valid step got soft-nudged and, after EMPTY_STEP_MAX_RECOVERY, hard-halted the
turn with a bogus "Empty tool call loop detected" terminal error.
The design cannot be narrowed into correctness — "the model called a tool with
no arguments" is indistinguishable from "the model made progress" without
per-tool schema knowledge that the guard does not reliably have. Remove it:
- delete src/session/prompt/empty-step-detection.ts and its two suites
- drop isEmptyStep / handleEmptyStep / emptyStepStreak / hardHalt wiring and
both branch call sites from src/session/prompt.ts
- drop MIMOCODE_EMPTY_STEP_MAX_RECOVERY from src/flag/flag.ts
- drop the invalid-output-continuation case that asserted the guard's halt
This is a pure revert. An earlier revision of this branch also added a
`leaked-toolcall-marker` detector (matching a text part whose whole trimmed
content is "call:", "code", or a bare invoked tool name) plus a retry ladder.
That is intentionally NOT included: the marker leak was a quirk of Claude
Opus 4.8, which is no longer in use, so the detector would be dead code for a
defect that no longer occurs. It also carried real downside — a legitimate
one-word `code` text part next to a same-named tool would discard the whole
step, and priming the model about `call:` plausibly makes the leak more likely,
not less.
wqymi
force-pushed
the
mimocode/revert-empty-step-guard-add-call-preamble-leak-retry-suppress
branch
from
July 27, 2026 13:32
9060bda to
d709482
Compare
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.
Summary
A pure revert of the empty-step guard (commit
0c73f7440, PR #1730, later narrowed by #1776). Nothing new is added — after this PR,packages/opencodeis back to its pre-guard behavior on this path.The guard classified a tool call whose
inputwas{}as an "empty step" with NO PROGRESS. That is wrong:input: {}is completely legitimate for no-arg tools (list_apps,plan_exit,plan_enter, …) whose schema isz.object({}). Real evidence: acomputer-use_list_appscall withinput: {}that COMPLETED with real output was flagged as "NO PROGRESS", got a spurious nudge, and — afterEMPTY_STEP_MAX_RECOVERY— hard-halted the turn with a bogusEmpty tool call loop detectedterminal error. A tool call that executed successfully IS progress.The design cannot be narrowed into correctness: "the model called a tool with no arguments" is not distinguishable from "the model made progress" without per-tool schema knowledge the guard does not reliably have. So it is removed rather than patched again.
What was reverted
src/session/prompt/empty-step-detection.tssrc/session/prompt.tsempty-step-detectionimport,handleEmptyStep+ both branch call sites (main + fork),emptyStepStreak,hardHaltand itsif (hardHalt) breakin theoutcome === "break"gatesrc/flag/flag.tsMIMOCODE_EMPTY_STEP_MAX_RECOVERYtest/session/empty-step-detection.test.tstest/session/empty-step-guard-integration.test.tstest/session/invalid-output-continuation.test.tsFlagimportNet: 6 files changed, 520 deletions, 0 insertions.
Why the
leaked-toolcall-markerdetector was droppedAn earlier revision of this branch also added a second, unrelated mechanism: a
leaked-toolcall-markerclassify variant (matching a text part whose whole trimmed content is"call:","code", or a bare invoked tool name), anautoRetryLeakedToolcallMarkerretry ladder inprompt.ts, aLeakedToolcallMarkerErrorinmessage-v2.ts, theMIMOCODE_LEAKED_TOOLCALL_MARKER_RETRY_LIMITflag, and their tests. All of it has been removed from this PR, for three reasons:"call:"preamble leak was a quirk of Claude Opus 4.8. That model is no longer in use (Opus 5 now), and the leak is not observed in normal operation.codetext part emitted alongside a same-named tool would be a false positive that discards an entire otherwise-valid step. And negatively priming the model aboutcall:in a system reminder plausibly makes it more likely to emit the marker, not less.If the leak ever reappears on a model we actually ship, it should be addressed then, with evidence from that model — not pre-emptively here, coupled to an unrelated revert.
Verification
bun typecheck— exit 0, 12/12 packages successful (no dangling references to any removed symbol).test/session/classify.test.ts+test/session/invalid-output-continuation.test.ts(37 pass / 0 fail),test/session/prompt.test.ts(11 pass / 0 fail),test/session/prompt-effect.test.ts(47 pass / 2 skip / 0 fail).grep -rniE 'leaked.toolcall|LeakedToolcallMarker|LEAKED_TOOLCALL_MARKER|leakedToolcallMarker' packages/→ 0 matches.grep -rniE 'isEmptyStep|handleEmptyStep|emptyStepStreak|EMPTY_STEP_|hardHalt|empty-step-detection' packages/opencode/→ 0 matches.origin/main(d0b4cc077).