Repository navigation
Defer a host-guided inspection failure that would stop the run for exactly one corrected attempt, with every hard bound kept - #6749
Open
Lightheartdevs wants to merge 2 commits into
Conversation
A failed preview inspection whose result already hands the model its correction was charged like any other failure, so it could be the result that ends the run before the correction was ever sent. On merged main d4a61f3 this failed the website journey on strixy (round 107: the second pre-execution rejection was the fourth consecutive failure) and mac-mini (round 106: four locators that matched nothing). The run-progress budget now lets the consecutive (or lane) charge of a correctable failure wait for exactly one next result. The next failure of any tool charges both; only a success that resets the same counter forgives it; discovery and free corrections do neither. At most one charge waits, at most two per run (RUN_PROGRESS_CORRECTED_ATTEMPTS), and every failure still counts toward the total cap of 12 at once. The four limits in RUN_PROGRESS_LIMITS are unchanged. correctableInspectionFailure accepts only inspection results that measured nothing: invalid_request, INCOMPLETE transition_untested, or a failed receipt with no top-level error, page errors or blocked requests whose last step is no_match, selector_not_unique or invalid_selector. The guard passes it only for an outer call bound as an inspection by before_tool_call. tool_result_persist classifies the same receipt the same way, including a Tool Search inspection whose failure is inside the envelope, so either hook order gives the same accounting. Replays of the recorded runs through the real guard: strixy stops at #12 (main: #11), mac-mini still at call 12, the laptop still at call 40 on the total cap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review fixes for the run-progress deferral: - A correctable inspection failure waits only when its consecutive (or lane) charge would stop the run or lane. Below the fuse it is charged at once and spends no allowance, so early correctable failures no longer use up both allowances before the failure main stops on. - Only a passing inspection (passedInspection: steps passed, no page errors) forgives the waiting charge. The next failure or any other non-discovery success charges it, so without a passing correction the run stops exactly one result after main would have. - The inspection tool's own result is recorded on its pending call when it returns (index.js wrapper, recordPreviewInspectionResult), and tool_result_persist classifies that result instead of a copy whose details OpenClaw capped at 8 KiB. Both hook orders now account alike. Replays: strixy r107 still stops at #12; mac-mini r106 now runs its next inspection (#13), which is INCOMPLETE and stops the run there; laptop r107 still stops at #40 on the total cap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Sep 26, 2026
Open
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.
Problem
The run-progress budget charges every failed inspection the same way. That includes results that measured nothing and already tell the model how to fix the check:
invalid_request), which ran nothing;transition_untestedresult from fix(pixel): report an untested requested show/hide change as incomplete at tool-result time #6740, which carries ready corrected steps;A website-only task has no progress lane, because
workspaceExtensionIsolatedmakestoolProgressLanereturn undefined. These failures therefore hit the global fuse of 4 consecutive failures. So the result that hands the model its fix can be the one that ends the run, before the model ever sends the fix.Evidence from fleet task
ods-main-qualification-20260924-r049on tower2.website_create_artifact_preview_interactionfailed on merged main d4a61f3 (round 107) on laptop, strixy and mac-mini, while all three towers passed:tool_call {id:"tool_describe"}twice and got "Unknown tool id".no_match,no_match,invalid_selector,no_match). Call 12 stopped the run with consecutive 4 and total 6, before the model's next inspection (Fix execute permissions on 6 shell scripts #13).Scope: this PR alone does not fix the r107 website regression
Replayed through the real guard hooks, this PR rescues none of the recorded failing sessions:
selectorkeys, so it gets the sameinvalid_request.In those runs, the failing results did not hand the model a usable fix. The
invalid_requesttext is generic and sends the model totool_describethroughtool_call, andno_matchnames no candidate locator. Those texts are changed in sibling PRs, not here:invalid_requestnames the step and key and carries lossless ready args. Failed locators get a ready plan, and measured failures get a diagnosis.This PR is the budget half. When such a result arrives at the fuse, the run stays alive for the one attempt that uses it. The classifier covers exactly the categories #6748 makes ready-to-send, plus #6740's INCOMPLETE. No model-facing string changes here.
Change
plugin/run-progress-budget.mjs,observeResult({..., correctable, corrected}). Both flags are optional and default tofalse, so every existing caller behaves exactly as before.A correctable failure waits only when its consecutive (or lane) charge would stop the run or the lane, i.e. that counter is at 3. Two more conditions apply:
RUN_PROGRESS_CORRECTED_ATTEMPTS(2) charges have waited in this run.Below the fuse, a correctable failure is charged at once and spends no allowance.
While a charge waits:
correctedis set: a passing inspection in the same lane (or globally) forgives it. So an unrelated read or a running-process receipt stops the run one result after main.The terminal expression is unchanged. It moved into a
settle()helper.plugin/preview-interaction-assurance.mjscorrectableInspectionFailure(result)requiresisError: trueanddetails.kind === INSPECTION_KIND, plus one of:invalid_requestwithstatus 'failed';status 'incomplete'withtransition_untested;status 'failed'with no top-levelerrorCode, nopageErrors, an emptyblockedRequests, and a last step ofno_match,selector_not_uniqueorinvalid_selector.passedInspection(result)is true only for an inspection receipt that is notisError, hasstatus 'passed'and has no page errors.plugin/tool-loop-guard.mjsafter_tool_call:correctableonly for a pending run bound as the inspection tool in this run.correctedfor a non-failed direct result or validated Tool Search envelope whose inspection passed.tool_search_code:IDs are never observed, as before.recordPreviewInspectionResult(toolCallId, params, result)keeps the tool's own result on the exact pending call: direct, or the pendingtool_callparent of a Tool Search child. It uses the same binding aspreviewInspectionTransition, now shared asboundPreviewInspectionRuns.tool_result_persistclassifies the recorded result when there is one, else the persisted copy. It also counts a Tool Search inspection whose failure is inside the envelope (persistedisError: false).plugin/index.jswraps the registered inspection tool'sexecuteto callrecordPreviewInspectionResultwith the result it returns. The tool itself is unchanged.Why the tool result is recorded
OpenClaw 2026.6.33 caps persisted
detailsabove 8 KiB (MAX_PERSISTED_TOOL_RESULT_DETAILS_BYTES) beforetool_result_persistruns.after_tool_callis dispatched fire-and-forget and can run after persistence.In that order, a capped receipt could not be classified and was charged at once, while the other order deferred it. The tool returns before either hook runs, so its recorded result makes both orders account the same way. Without a record, for example a tool not registered through
index.js, the old fail-closed behavior remains.Deviations from the fix spec
RUN_PROGRESS_CORRECTED_ATTEMPTS = 2, not a fifth key inRUN_PROGRESS_LIMITS. The existing test "an external stop is sticky and changes no limit" deep-equals that object to the four limits.inspection_transition_coverage.test.mjs, "the registered inspection tool asks the run guard about exactly its own call" runs theindex.jsregistration block against a fake guard. The fake now also hasrecordPreviewInspectionResult, and the test asserts that it receives exactly('call-1', args, result). Its existing assertions are unchanged.after_tool_callcould no longer bind the inspection and charged it normally, and strixy stopped at Rename to Android Framework — update all references #11. With persistence first, the transcript copy of a stopping Tool Search inspection now gets the stop text too. The after-first order already did this on main (strixy entry 27). The model's live context is unaffected.Safety invariants
RUN_PROGRESS_LIMITSis unchanged: consecutive 4, total 12, 8 rounds without progress, 2 identical successes. Every failure counts toward the total of 12 at once, and the total cap is never deferred.visibility_mismatch,click_failed,unstable, page errors, blocked requests,unavailable,timeout,cancelled,output_limit, blocked receipts and every non-inspection tool are never correctable. A pass with page errors never forgives.seenCallsdedupe and its cap are unchanged. Nestedtool_search_code:IDs and unbound calls get no allowance.Tests
tests/run_progress_budget.test.mjs: 12 new tests.correctableInspectionFailureandpassedInspection.C S C S F F F INCOMPLETEshape keeps the run going;correctableomitted (randomcorrected), the budget is identical at every step;tests/inspection_budget_replay.test.mjs(fixturetests/inspection-budget-replay-round106-107.json, unchanged). Realbefore_tool_call/after_tool_call/tool_result_persisthooks, both orders:invalid_request.transitionRequirementand a capsule that reproduces only plans whose steps passed on the same snapshot in a recorded receipt. Fix execute permissions on 6 shell scripts #13's four steps are the first four steps of Rename to Lighthouse AI + major content expansion #12, which passed.transition_untested, and the run stops there.passedInspection: 14 of 57 tests in the three changed files fail, and all of those failures are expected:recordPreviewInspectionResultAPI.index.jsrecorder call fails the registration test.remote-js-test.sh, pixel-agent JS, tower2):# tests 3165 # pass 3127 # fail 03159 / 3121 / 03138 / 3100 / 0🤖 Generated with Claude Code