Skip to content

[Bug]: /goal verifier aborts the whole run on a single denied web_fetch/web_search call (paused(verifier_capability)) #419

Open

Description

@Hylouis233

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:

  1. renderGoalVerifierReminder describes the read-only boundary in detail but never says the two network tools are unavailable. The model has no way to know in advance.
  2. web_fetch/web_search are 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_violation as 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.ts and goal-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

  1. Hylouis233 commented on Oct 1, 2026

    @Hylouis233
    Author

    The fix branch is pushed: Hylouis233:work (commit 69810ab).

    git fetch https://github.com/Hylouis233/minimax-code.git work
    git diff main...work
    

    It contains:

    • goal-subagent-coordinator.ts — bounded recovery window before latching capability_violation
    • goal-verifier-reminder.ts — states the run is offline and routes an evidence gap to PARTIAL
    • goal-verifier-readonly-guard.test.ts — 6 new cases, registered in test/vitest-suites.json

    The 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 createPullRequest on this repo), so the branch is on the fork for a maintainer to pull from, or I can transfer it wherever preferred.

  2. hetaoBackend commented on Oct 1, 2026

    @hetaoBackend
    Collaborator

    This was generated by AI during triage.

    @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 — beforeTool blocks the call and latches the issue:
      if (!GOAL_VERIFIER_DENIED_TOOLS.has(toolName)) return undefined;
      // Latched, not aborted here: the blocked result goes back to the model and
      // the next `beforeLlm` stops the run on the latched issue.
      this.latchIssue(state, { code: 'capability_violation', message: reason });
      return { block: true, reason };
    • :113 — the next beforeLlm turns 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 leaves web_fetch/web_search as 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 for web_search once 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, and route_unavailable / child_budget_exhausted stay untouched with maxTurns/maxTokens still 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

    • Category: bug — a single denied tool call aborts the run; the user gets a hard error instead of a verdict.
    • State: needs-triage — root cause confirmed, fix prepared and locally verified. It needs a maintainer to bring the commit in with attribution. (This tracker has no ready-for-human label, so needs-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 applicable pnpm verify against current main, since a few toolchain/test issues have landed since (test:sandbox and test:byok in 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingneeds-triageAwaiting maintainer assessment

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions