Repository navigation
fix(pixel): count a passing inspection of the snapshot awaiting its byte check - #6726
Open
Lightheartdevs wants to merge 3 commits into
Open
Lightheartdevs wants to merge 3 commits into
Lightheartdevs wants to merge 3 commits into
Conversation
…te check Tower2 round 092 (main 04f0a83, qwen3-coder-next, session afc733bc): website-create published site-05f1589d, ran a read-only grep, then ran a transition inspection of that same snapshot that passed. The final answer was replaced by "The requested show/hide interaction has not passed browser inspection" although nothing on disk had changed. Root cause: a settled later call (here the grep) takes the preview out of currency until the end-of-turn host byte comparison. An inspection receipt bound only to the current preview, so while the comparison was pending the passing receipt bound to nothing and was discarded. The comparison then restored the unchanged snapshot as current, but the discarded pass was not recovered. Inspect-then-grep already passed; only grep-then-inspect failed. Now, while the preview is out of currency only pending that comparison, an inspection receipt binds to the pending snapshot of the same session, session key and workspace root. Nothing else changes: - The proof counts only when that exact snapshot is current again (the host digest matched at finalization, or identical bytes were republished). - A receipt for another snapshot, a failed receipt, a static plan or a foreign session still binds nothing; changed bytes leave the preview stale. - An ineligible call or explicit bundle invalidation drops the pending snapshot, so a later receipt binds nothing. - Static checks retain an earlier transition proof and page errors select the repair step for the pending snapshot, as they already did for the current one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…host code Adds the recorded round 092 calls (exact arguments, results as the tools returned them) to the post-publication replay. Writes, edits, execs and both publications run against a temporary workspace and host/workspace_preview.py, and each publication reproduces the recorded snapshot digest. Before the fix the replay ends "failed"; with it the model's answer is delivered as verified. The replay harness now follows OpenClaw 2026.6.33 more closely: - after_tool_call receives the before-hook params merged over the model's arguments (mergeParamsWithApprovalOverrides), so a recorded exec workdir survives the cancellation wrapper; - a bare /workspace path (cd /workspace && ...) maps to the temporary workspace like /workspace/...; - a recorded failed edit changes nothing and must still fail on the replayed bytes. Round 092 replays with the production exec wrapper only: its execs pass workdir "/workspace", which the guard models on that path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… its byte check Three probes from the #6726 review, next to the round 092 tests: - An inspection that settles after a parallel write binds to the snapshot awaiting its byte check. It counts when the write left the published bytes unchanged (the byte check restores the snapshot) and not when it changed them. The identical-write case fails on main aa0623f and passes here. - A late receipt for an earlier snapshot, settling after a newer publication and a grep, binds nothing; the newer snapshot stays unverified after its byte check. - A pass on the pending snapshot does not verify a later publication of different bytes. The last two pass on main as well; they guard the binding against widening. The late-receipt and changed-write cases fail if binding a receipt were to make the pending snapshot current without the byte check. awaitingByteCheck() gains publish(), which writes and publishes new bytes with both results settled, so the end-of-turn byte check can run afterwards. 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.
What went wrong
Tower2 round 092 (main
04f0a835, qwen3-coder-next, OpenClaw 2026.6.33, sessionafc733bc), website-create. After its last publication (site-05f1589d…) the model:cd /workspace && grep -n "midnight-concert-card" …/index.html(read-only),Nothing on disk changed, but the answer was replaced by "The requested show/hide interaction has not passed browser inspection."
Why
Any settled shell command after publishing takes the preview out of "current" until the host re-checks the published bytes at the end of the turn. The inspection receipt was matched only against the current preview. So in that window the passing receipt matched nothing and was dropped. At the end of the turn the byte check found no change and made the same snapshot current again, but the dropped pass was gone. The other order (inspect first, then grep) already worked, because the receipt arrived while the preview was still current.
Fix
While the preview is out of currency only because the end-of-turn byte check is pending, an inspection receipt now binds to that pending snapshot. The pending snapshot must belong to the same session, session key and workspace root. The final gate does not change: the proof counts only when that exact snapshot is current again (the host digest matched, or identical bytes were republished).
The fix is one helper,
inspectionPreview(state), intool-loop-guard.mjs. The proof, static-retention and page-error bindings now use it instead ofstate.workspacePreview.Still not verified:
sleep 1 &, or explicit bundle invalidation), or the session id, session key or workspace root changed. A receipt that arrives after that binds nothing, even if identical bytes are republished afterwards. (A receipt that bound before the drop is tied to that snapshot's hash, so it still counts if identical bytes are republished. That is correct: it tested those exact bytes.)For the pending snapshot, a static re-check keeps an earlier transition proof and page errors select the repair step. This is what already happens for the current snapshot.
Evidence
tests/post-publication-tower2-round092.jsonholds the 23 recorded calls (exact arguments; results as the tools returned them, with the guard's appended guidance and host cwd removed).post_publication_currency.test.mjsreplays them against a temporary workspace and the realhost/workspace_preview.py. Both publications reproduce the recorded snapshot digests. On main the replay endsfailed, the same outcome as the fleet run. On this branch it endspassedand delivers the model's answer with the preview link.R092:tests inpreview_interaction_assurance.test.mjs. On mainaa0623f4, 7 of them fail: every positive case (grep-then-inspect and inspect-across-grep, direct and Tool Search; static retention; page-error repair step; an inspection that settles after a parallel write of identical bytes). All 22 pass on this branch.4ae235f8): an inspection that settles after a parallel write counts only when the byte check finds the published bytes unchanged. A late receipt for an earlier snapshot, settling after a newer publication and a grep, binds nothing. A pass on the pending snapshot does not verify a later publication of different bytes. The last two also pass on main; they guard against widening the binding.bundle-invalidatedcase does not exercise the session checks, because invalidation drops the proof and the pending snapshot together; it catches the last-verified mutation.Tests
remote-js-test.sh): mainaa0623f4: 3112 tests, 0 failures. This branch4ae235f8: 3135 tests, 0 failures (23 new). This branch merged with current main47b36328: 3144 tests, 0 failures.tests/runtime_*.integration.mjs) against the pinned OpenClaw 2026.6.33 copies on tower2 (read-only), run ona5f93846: main and this branch give identical per-test results with both the upstream copy and the ODS-repaired copy (64 tests each). That includesruntime_preview_delivery("real harness recovers saved HTML without replaying work", 4/4 passing). The 12 failures are identical on main and the branch and come from this environment: the copied runtime lacksjson5(ERR_MODULE_NOT_FOUND).4ae235f8changes only a unit-test file.Scan for secretsfails, but not on anything in this PR. The scan also reads commits outside this branch. On4ae235f8its one finding is indashboard/src/lib/applicationPins.jsfrom commit5977cb1b, which belongs to feat(extensions): Open, How to use and Applications for installed extensions #6731 and is on neither main nor this branch. Ona5f93846its 4 findings were all indashboard-api/tests/test_host_agent_output_redaction.py, the same file mainaa0623f4failed on. This PR changes four pixel-agent files only, and the new fixturepost-publication-tower2-round092.jsonhas no host paths or personal data.Replay harness changes (
post_publication_currency.test.mjs)(
post_publication_currency.test.mjs)These bring the harness closer to OpenClaw 2026.6.33, so the round 092 calls replay as they ran:
after_tool_callnow receives the before-hook params merged over the model's arguments (mergeParamsWithApprovalOverridesin the pinned dist). This keeps the recorded execworkdirafter the cancellation wrapper./workspace(cd /workspace && …) maps to the temporary workspace, like/workspace/…already did.index.js). Its execs passworkdir: "/workspace", and the guard models that merge only on the wrapper path. Rounds 061 and 069 still replay both ways.Not in this PR
terminate:trueon a byte-identical write) needs its own fix. This PR changes no runtime-repair JSON, hashes, installer, foreign-restore list, CI wiring orODS-RUNTIME-REPAIRS.md, and it merges cleanly with fix(pixel): recover owner-turn context overflows by trimming tool output when that fits #6715's repair wiring.Merge note for #6723 (required, whichever lands second)
This branch has a textual conflict with #6723 on the page-errors line of the inspection-receipt block. Resolving that line alone is not enough. Every place in #6723 that picks the snapshot an inspection belongs to must use the snapshot this PR binds receipts to:
afterToolCall, useinspectedPreviewinstead ofstate.workspacePreviewin all three places:boundHiddenRequestedText(…),withHiddenRequestedText(…), and thesiteId/sha256ofstate.workspaceRequestedTextEvidence.requestedTextsForInspection(params), replaceruns.get(pending.runId)?.workspacePreviewwithinspectionPreview(…)of that run's state (skip it when the run is gone). This function merges without a conflict, so a resolver will not see it.Why it matters: after a read-only grep there is no current preview. If step 2 is skipped,
requestedTextsForInspectionreturns[], so the inspector is never asked whether the owner's requested text is visible at load, and this PR counts the pass. The turn is then delivered as passed although the requested footer isdisplay:noneat load. In fleet order, #6723 alone endsfailedfor that turn (the show/hide pass is lost after the grep, which is the defect this PR fixes), so the composition would turn a false negative into a false pass. If step 1 misses the evidence line, that line readsstate.workspacePreview.siteIdwhile the preview is undefined and throws aTypeErrorinafter_tool_call.Required test: whichever of #6723 or #6726 lands second adds the composed test below to
tests/requested_literals.test.mjs. It publishes, runs a read-only grep, and inspects with the requested footer hidden at load; the turn must fail for the hidden text, and the passing show/hide steps must still count. It runs in fleet order (create run first, so the show/hide duty applies) and with the edit run alone, each with the grep before the inspection and with an inspection that starts before the grep and settles after it. It also makes the test helper return norequestedTextblock when no texts were sent, as the broker does (preview_inspection_capsule.pyadds it only when the request has texts).Composed on a scratch branch (not pushed): main
47b36328+ this branch + #6723f424bdf8, with the composed test. Each row was run locally (node 24). "0 texts" means the inspector was not asked about any requested text.state.workspacePreviewTypeErrorTypeErrorTypeErrorTypeErrorThe composed test fails 4 of 4 cases on #6723 alone, 4 on "keep both sides", 2 on the old note and 4 on the
TypeErrorresolution. It passes all 4 with the full note, and that composition passes the whole plugin JS suite on tower2: 3155 tests, 0 failures.The "edit run only" row shows a gap in #6723 by itself: when a grep comes between publishing and inspecting, and the run has no show/hide duty, the hidden-text check does not run at all. With this PR and the full note, that case fails correctly.
The composition does not change tool-result coaching: while the snapshot awaits its byte check, the inspection result itself does not carry the hidden-text instruction (coaching reads the current preview). The gate still fails the turn, and finalization sends the one revision request.
Composed test (diff against #6723
f424bdf8)Other open PRs
#6715, #6716, #6722, #6724 and #6698 merge cleanly with this branch. #6645 already conflicts with main.
🤖 Generated with Claude Code