Skip to content

revert(session): remove the empty-step guard that mis-flagged no-arg tool calls - #1782

Open
wqymi wants to merge 1 commit into
mainfrom
mimocode/revert-empty-step-guard-add-call-preamble-leak-retry-suppress
Open

revert(session): remove the empty-step guard that mis-flagged no-arg tool calls#1782
wqymi wants to merge 1 commit into
mainfrom
mimocode/revert-empty-step-guard-add-call-preamble-leak-retry-suppress

Conversation

@wqymi

@wqymi wqymi commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

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/opencode is back to its pre-guard behavior on this path.

The guard classified a tool call whose input was {} 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 is z.object({}). Real evidence: a computer-use_list_apps call with input: {} that COMPLETED with real output was flagged as "NO PROGRESS", got a spurious nudge, and — after EMPTY_STEP_MAX_RECOVERY — hard-halted the turn with a bogus Empty tool call loop detected terminal 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

File Change
src/session/prompt/empty-step-detection.ts Deleted (99 lines)
src/session/prompt.ts Removed the empty-step-detection import, handleEmptyStep + both branch call sites (main + fork), emptyStepStreak, hardHalt and its if (hardHalt) break in the outcome === "break" gate
src/flag/flag.ts Removed MIMOCODE_EMPTY_STEP_MAX_RECOVERY
test/session/empty-step-detection.test.ts Deleted
test/session/empty-step-guard-integration.test.ts Deleted
test/session/invalid-output-continuation.test.ts Removed the "repeated empty output … halts the turn" case + the now-unused Flag import

Net: 6 files changed, 520 deletions, 0 insertions.

Why the leaked-toolcall-marker detector was dropped

An earlier revision of this branch also added a second, unrelated mechanism: a leaked-toolcall-marker classify variant (matching a text part whose whole trimmed content is "call:", "code", or a bare invoked tool name), an autoRetryLeakedToolcallMarker retry ladder in prompt.ts, a LeakedToolcallMarkerError in message-v2.ts, the MIMOCODE_LEAKED_TOOLCALL_MARKER_RETRY_LIMIT flag, and their tests. All of it has been removed from this PR, for three reasons:

  1. The defect no longer occurs. The "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.
  2. It would be dead code. Shipping and maintaining a detector, a retry ladder, an error type, a flag and a test suite for a defect that no longer happens is pure carrying cost.
  3. The detection itself carries risk. A legitimate one-word code text part emitted alongside a same-named tool would be a false positive that discards an entire otherwise-valid step. And negatively priming the model about call: 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.

Note: the branch name still reads …-add-call-preamble-leak-retry-suppress for history/CI continuity, but the branch's scope is now revert only.

Verification

  • bun typecheckexit 0, 12/12 packages successful (no dangling references to any removed symbol).
  • Affected suites all green: 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.
  • Rebased onto latest origin/main (d0b4cc077).

@wqymi
wqymi force-pushed the mimocode/revert-empty-step-guard-add-call-preamble-leak-retry-suppress branch 4 times, most recently from ceac409 to b436c52 Compare July 20, 2026 10:23
@wqymi wqymi changed the title fix(session): replace empty-step guard with call-preamble-leak retry+suppress fix(session): replace empty-step guard with leaked-toolcall-marker retry+suppress Jul 20, 2026
@wqymi
wqymi force-pushed the mimocode/revert-empty-step-guard-add-call-preamble-leak-retry-suppress branch 2 times, most recently from 029b1b0 to 9060bda Compare July 27, 2026 07:54
…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
wqymi force-pushed the mimocode/revert-empty-step-guard-add-call-preamble-leak-retry-suppress branch from 9060bda to d709482 Compare July 27, 2026 13:32
@wqymi wqymi changed the title fix(session): replace empty-step guard with leaked-toolcall-marker retry+suppress revert(session): remove the empty-step guard that mis-flagged no-arg tool calls Jul 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant