Repository navigation
[Bug]: /goal verifier aborts the whole run on a single denied web_fetch/web_search call (paused(verifier_capability)) #419
Description
Summary
In /goal mode, a Goal verifier child that calls web_fetch or web_search once has its entire run aborted, and the user gets a hard error instead of a verdict:
GOAL_VERIFIER_CAPABILITY_VIOLATION: readonly Goal verification blocked tool "web_fetch".
# → 出了点小问题,请稍后重试
The Goal settles as paused(verifier_capability).
The objective of a Goal is always about the local workspace, and the child still holds read, grep and bash, so a single denied network call has a straightforward local answer. Stopping the run for it leaves the user with no way forward.
Root cause
GoalVerifierChildCoordinator.beforeTool blocks the call and latches a capability_violation issue. The next beforeLlm converts any latched issue into an abort:
// beforeTool — the latch happens here
this.latchIssue(state, { code: 'capability_violation', message: reason });
return { block: true, reason };
// beforeLlm — and the run dies on the very next turn
if (state.issue) return { type: 'abort', reason: state.issue.message };beforeTool's own comment states "the blocked result goes back to the model and the next beforeLlm stops the run on the latched issue" — the recovery is described but never reachable. The child cannot act on the rejection, because it is stopped before it gets a turn in which to do so.
Two things make this reachable in practice:
renderGoalVerifierReminderdescribes the read-only boundary in detail but never says the two network tools are unavailable. The model has no way to know in advance.web_fetch/web_searchare precisely the two tools that survive the verifier role's five-tool grant minus the canonical read-only ceiling, so they are the only calls that ever reach this guard.
Suggested direction
Count denied calls per run and allow a bounded number of them to be spent on recovery:
- First N denials (N≈2): still
block: true, but the reason states the run is offline and names the local alternative, and no issue is latched — the child continues and can reach the same evidence. - Past N: the child is looping rather than detouring; latch
capability_violationas today.
route_unavailable and child_budget_exhausted would be untouched, and maxTurns/maxTokens stay as the outer bound on a child that keeps retrying.
Independently, adding one clause to the reminder — that the run is offline and that a missing-evidence situation is a PARTIAL rather than a reason to reach for the network — would stop the common case from spending a turn on a denied call at all.
On the current design intent
FAILURE_REASONS.capability_violation → paused(verifier_capability) is documented as owned by the readonly guard / tool catalog layer. That framing is right for a genuine catalog defect, but a model reaching for web_search once on a local objective is not one, and pausing the whole Goal surfaces as an unexplained failure to the user. Keeping the latch for the repeat case preserves the intent; it only stops firing on a single recoverable mistake.
Environment
- App: MiniMax Code
3.0.74-inside.104(macOS, arm64) - Reproduced on the released build; the same code path is in
packages/local-runtime-v2/src/application/agent/goal-subagent-coordinator.tsandgoal-verifier-reminder.ts
A fix with tests and a verified fail-before/pass-after regression is ready in a fork branch (Hylouis233:work) — happy to open a PR if that is preferred over a maintainer-side patch.
Activity
Hylouis233 commentedon Oct 1, 2026
The fix branch is pushed:
Hylouis233:work(commit69810ab).It contains:
goal-subagent-coordinator.ts— bounded recovery window before latchingcapability_violationgoal-verifier-reminder.ts— states the run is offline and routes an evidence gap toPARTIALgoal-verifier-readonly-guard.test.ts— 6 new cases, registered intest/vitest-suites.jsonThe two central cases were confirmed to fail against the unpatched coordinator (
2 failed | 4 passed) and pass with the fix, so they pin the behaviour change rather than the shape of the new code. Also green: 145 goal-suite tests,tsc -p tsconfig.standalone.json --noEmit, and eslint on all touched files.I could not open a PR directly (the account lacks
createPullRequeston this repo), so the branch is on the fork for a maintainer to pull from, or I can transfer it wherever preferred.hetaoBackend commentedon Oct 1, 2026
@Hylouis233 — I verified every step of this chain against
main, and it's correct line for line. This is a real bug with a real fix already in progress on your side.Verification (against
main)In
packages/local-runtime-v2/src/application/agent/goal-subagent-coordinator.ts::17—GOAL_VERIFIER_DENIED_TOOLS = new Set(['web_fetch', 'web_search'])— exactly the two tools you name, and they are the only ones that reach this guard.:162-167—beforeToolblocks the call and latches the issue::113— the nextbeforeLlmturns any latched issue into an abort:if (state.issue) return { type: 'abort', reason: state.issue.message };So your central observation is exact: the comment describes a recovery path that is never reachable, because the child is stopped before it gets the turn in which it could act on the rejection. And your two reachability arguments check out too — the reminder (
goal-verifier-reminder.ts) describes the read-only boundary in detail but never states that the two network tools are unavailable, and the verifier role's five-tool grant minus the read-only ceiling leavesweb_fetch/web_searchas precisely the calls that can trip this.Assessment
Your diagnosis of why this is a defect rather than intended behavior is the part I agree with most:
FAILURE_REASONS.capability_violation → paused(verifier_capability)is the right framing for a genuine catalog defect, but a model reaching forweb_searchonce on a local-workspace objective is a recoverable detour, not a catalog defect. Pausing the whole Goal on it surfaces to the user as an unexplained failure, which is the real harm.Your proposed fix shape — a bounded recovery window (N≈2) where the first denials block without latching, and only a repeat offender latches
capability_violation— is a proportionate fix: it preserves the guard's intent for the genuine loop case, androute_unavailable/child_budget_exhaustedstay untouched withmaxTurns/maxTokensstill bounding the outer loop. The independent reminder clause (offline run + "evidence gap →PARTIAL, don't reach for the network") is a good complement because it stops the common case from spending a turn on a denied call at all.I also like that your test cases were confirmed to fail against the unpatched coordinator (2 failed | 4 passed) and pass with the fix — that pins the behavior change rather than the shape of the new code, which is the right way to review it.
Classification
bug— a single denied tool call aborts the run; the user gets a hard error instead of a verdict.needs-triage— root cause confirmed, fix prepared and locally verified. It needs a maintainer to bring the commit in with attribution. (This tracker has noready-for-humanlabel, soneeds-triage+ this note is the routing.)Process
As you note, you can't open a PR directly, so use the same path as #149 / #364 / #391: the branch is the deliverable — just post the commit link here (or ping a maintainer) so it can be landed with attribution. When you do, please make sure the new test file is registered in
test/vitest-suites.json(you noted you did — good, that's the gate that actually runs it) and re-run the full applicablepnpm verifyagainst currentmain, since a few toolchain/test issues have landed since (test:sandboxandtest:byokin particular were both fixed this week).Strong report — the fact that you quoted the latching comment back at us as evidence that the documented recovery is unreachable is exactly the right way to make the case.
No credentials or sensitive data echoed.