Repository navigation
fix(pixel): plan within the output limit, continue a cut Portal turn once, report otherwise - #6743
Lightheartdevs wants to merge 12 commits into
Conversation
…silently On strixy the owner asked for "a cool looking webpage with a forest theme ... Best you can do". The model wrote the whole page in one call, the reply stopped at the 8192-token output limit after 220 s, the unfinished write never ran, and the owner saw one sentence and no page. Pixel now records the stop reason of each saved assistant reply (before_message_write). Delivery reports a final reply cut at the output limit plainly, since OpenClaw 2026.6.33 skips before_agent_finalize for such a turn and would otherwise show only a generic retry message. When the hook does run, one bounded revision pass asks for smaller steps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On strixy the model wrote a whole requested web page in one write call. The reply hit the 8192-token output limit, the unfinished call never ran, and nothing was saved. The model had no way to know how much one reply can hold. The prompt contract now states the running model's per-reply output limit and asks for large content as several smaller files or writes (for a web page: index.html, styles.css, script.js), each well under half of it. The limit is read from the host configuration the way OpenClaw 2026.6.33 applies it (params clamped to the model row), so the line is byte-stable per host. It sits right after the conversation contract, before every per-request section, so it adds no new prompt-cache break. It limits one reply, not the size of the work. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
OpenClaw 2026.6.33 skips before_agent_finalize when a reply cut at the output limit is the whole turn, so the finalize revision from the previous commit never ran for the strixy request and the owner had to ask again. After a Portal completion, the ingress now asks the plugin (/pixel-ods/output-limit-continuation) whether the run's final reply was cut. For an owner chat turn that was not cancelled, stopped, waiting, or a host-operation, extension or exact-download request, the plugin grants one continuation bound to the chat user and consumes it. The ingress then submits one new turn of the same chat containing only a fixed message that asks for smaller steps (plus the request's trusted system messages). It never replays the owner's message or a tool call, and OpenClaw never ran the cut call. The continuation's run is never granted another pass: a continuation cut again keeps the honest output-limit report. An owner turn gets at most one pass of any kind, and no output-limit continuation follows another ingress continuation. An older plugin without the route leaves the report unchanged. The fixed message selects no workspace, preview or visual-edit route (tested). A real-harness fixture replays the strixy reply shape through pinned OpenClaw and the real ingress, and runs in the Portal runtime workflow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sensitive Files DetectedTrigger: Security-sensitive files detected: .github/workflows/ Files flagged: Extra human review is recommended for this PR. Claude Code Review | Sensitive File Detection | ~$1.50 |
…st's checks Review of the output-limit continuation (PR #6743) found, on the real plugin and OpenClaw 2026.6.33: - No continuation when any tool ran earlier in the cut turn. Tool hooks call observeRun with OpenClaw's tool context, which has no trigger, and that reset the turn's chat user. The chat user now survives them. - The continuation run was classified by its fixed message, so it lost the owner request's contract and delivery checks: a "your page is ready" reply with no file was delivered as-is. The chat's next owner turn now takes the grant, and when it is the fixed message (alone or behind OpenClaw's timestamp envelope) Pixel classifies it as the owner message it continues. The model still receives only the fixed message. Files the cut run wrote into a new Playground project stay that project. - The ingress asked the plugin after every Portal turn, and a broken route broke good answers. It now asks only when the completion is OpenClaw's incomplete-turn text (either form). - A granted continuation the gateway answered with a non-200 status lost the honest report. The cut turn's report is delivered with a line that the automatic continuation did not complete; it is not retried. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The real-harness test used a stand-in plugin built from the tool guard, so it could not see index.js wiring or the prompt contract. It now loads the real pixel-ods plugin behind the real ingress on the Portal history path, with a fixture model, and compares each cut case with the same model behaviour uncut: a website request cut before or after a write, an unfounded "ready" reply, a long text answer with a follow-up turn, and a continuation cut again. The owner must receive what the uncut turn delivers, in the same files and Playground project, with the same system prompt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sensitive Files DetectedTrigger: Security-sensitive files detected: .github/workflows/ Files flagged: Extra human review is recommended for this PR. Claude Code Review | Sensitive File Detection | ~$1.50 |
…the request Brings the substance of #6742's adversarial-review fixes into the continuation change so the combined output-limit handling is correct on its own. The continuation comes first; the report is delivered only when no continuation ran or the continuation was cut too. - A receipt forced to failed (the output-limit report, a stopped mixed-task lane) or combined into a failed or pending mixed-task receipt drops suppressStaleExecWarning. The ingress accepts that key only on none/passed receipts and answered such a turn with HTTP 502. - The report covers owner chat turns only. Heartbeat, cron and team-worker receipts are unchanged, and a receipt still waiting on the host or the owner (pending) is not turned into a failure. - It covers written answers as well as workspace tasks: OpenClaw 2026.6.33 replaces a cut text-only reply with its generic "couldn't generate a response" too. A workspace task hears that what the reply was still writing was not saved; a written answer hears that it was cut off, never that a file write was. A continuation cut again says so. - The report and the run's own receipt text stay within the ingress's 32 KiB receipt bound (the report stands alone otherwise); the ingress's continuation-failure line does too, and no longer tells a written answer to check the workspace. - The before_agent_finalize revise is removed: OpenClaw skips the hook for a cut turn, and its instruction fit only file work. Only the continuation prompt's bytes remain (unchanged). tests/output_limit_recovery.test.mjs is rewritten at ingress level: the real ingress over loopback TCP in front of a gateway double whose Pixel routes are served by a real tool-loop guard (the former 502 cases now 200, non-owner and pending receipts unchanged, text vs workspace wording, the size bound, cut-then-continued turns). The real-harness fixture adds a written answer cut twice. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sensitive Files DetectedTrigger: Security-sensitive files detected: .github/workflows/ Files flagged: Extra human review is recommended for this PR. Claude Code Review | Sensitive File Detection | ~$1.50 |
1 similar comment
Sensitive Files DetectedTrigger: Security-sensitive files detected: .github/workflows/ Files flagged: Extra human review is recommended for this PR. Claude Code Review | Sensitive File Detection | ~$1.50 |
…put-limit cut Fixes the confirmed findings of the two adversarial reviews of a285ba2. - A verified (passed) receipt is kept when only the closing reply is cut, and such a turn gets no continuation. The output-limit report now applies to the run's final delivery receipt only when it is none or failed; a pending receipt is unchanged as before. On a285ba2 a verified preview, a passing test run or a host observation became failed (host-operation turns on every cut), or was continued into an unverified 'none'. - A written answer whose own text OpenClaw delivered (after a tool error, attempt.lastToolError) keeps that text: its report is a failed 'after-reply' receipt that the ingress (and the reply delivery hook) add after a visible reply, and use alone in place of OpenClaw's incomplete-turn text. The ingress accepts after-reply only on a failed receipt with text and no preview, code or questions, and keeps suppressStaleExecWarning on it so the kept reply loses its stale exec warning. The report no longer says the answer was not delivered. - The ingress asks /pixel-ods/output-limit-continuation after every completed owner turn it has not already continued, with incompleteTurn saying whether OpenClaw answered with its incomplete-turn text. A workspace task cut after a tool error is now continued too; a written answer is continued only when OpenClaw replaced it. - A failed grant request (reset, invalid or truncated JSON) is a refusal: the turn's own receipt is delivered instead of a stream error or 502. - The continuation run counts the cut run's saved files as inspected, so it can publish a page whose index.html the cut run already wrote. Before, the preview was refused, and an identical rewrite ends OpenClaw's run. - The Portal's live activity follows the continuation run (streamTaskActivity followNextRun); it stayed on the cut run. - A Stop that reaches Pixel after the grant and before the continuation starts withdraws it: abortUserRun acknowledges it, so the ingress closes the continuation request, and a continuation run that still starts is cancelled as it begins. - continuationAllowed (a /goal plan) reads the receipt without the report, as on main. Tests: new ingress-level cases over a real guard (verified preview, test run and host observation kept, each against the uncut control; delivered answer text kept; a workspace task continued after a tool error; broken grant routes; Stop after the grant; after-reply receipt contract), guard cases (saved index.html, withdrawal, continuationAllowed) and a live activity case. The real-harness fixture gains a stand-in preview host and cases for a saved index.html, a site after a tool error, a delivered answer and a verified page with a cut closing reply. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sensitive Files DetectedTrigger: Security-sensitive files detected: .github/workflows/ Files flagged: Extra human review is recommended for this PR. Claude Code Review | Sensitive File Detection | ~$1.50 |
The gateway double's runs end before their completion returns, so its abortRun now finds nothing, as OpenClaw does for the ended cut run. With abortRun always succeeding, the case also passed on a285ba2 and did not show the post-grant Stop race; it now fails there and passes on 9ab8e34. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sensitive Files DetectedTrigger: Security-sensitive files detected: .github/workflows/ Files flagged: Extra human review is recommended for this PR. Claude Code Review | Sensitive File Detection | ~$1.50 |
…gress Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sensitive Files DetectedTrigger: Security-sensitive files detected: .github/workflows/ Files flagged: Extra human review is recommended for this PR. Claude Code Review | Sensitive File Detection | ~$1.50 |
… is running Re-verification of #6743 found the output-limit continuation could replace a failed test result with an unchecked success claim: the owner's unittest run failed, the whole-file fix write was cut, OpenClaw delivered the cut reply's text (the failed exec is a tool error), and the workspace task was granted a continuation. The continuation run has no test result of its own, so it rewrote calc.py, said "all tests pass now" without running them, and the owner got outcome `none` where main delivered `failed`. The grant now refuses a turn whose latest verification status is failed or pending. Such a turn keeps the cut run's own receipt with the honest report, as a285ba2 delivered. Everything else about the grant is unchanged. Tests: an ingress-plus-real-guard unit test for the exact sequence (and a website whose test run is still running when cut), each half red without the fix; and a real-stack regression in tests/runtime_output_limit_continuation.integration.mjs with its uncut control. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sensitive Files DetectedTrigger: Security-sensitive files detected: .github/workflows/ Files flagged: Extra human review is recommended for this PR. Claude Code Review | Sensitive File Detection | ~$1.50 |
The output-limit docs, plugin comment and tests named the fleet host the 2026-09-25 run was recorded on. Shipped files and tests describe it as a recorded run instead (same date, model and output limit); the prompt constant and chat id follow. No behaviour change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sensitive Files DetectedTrigger: Security-sensitive files detected: .github/workflows/ Files flagged: Extra human review is recommended for this PR. Claude Code Review | Sensitive File Detection | ~$1.50 |
…ols set Re-verification of #6743 (round 4) found the output-limit continuation lost what the cut run's own tools had established. The grant carries only the owner text, the Playground project and the saved paths, and the continuation run is classified only by the owner message with a fresh budget. So: - a successful visual write (index.html) that made the run's preview required where the owner's words did not, an attempted publication that failed or that a later change made stale, and an edit of the page the chat had already published each made the cut run's receipt failed; the continuation replied "published" / "ready" / "republished" and the owner got outcome none (main and the uncut control deliver failed); - a stopped mixed-task lane (progressBudget.exhaustedLanes, which forces the receipt to failed) left progressBudget.exhausted false, so the grant was given and the continuation retried the stopped lane with a fresh budget. The grant is now refused when the cut run carries any such state (outputLimitToolObligations): a failed or still-running test run (the earlier rule), any preview attempt, a visual write whose preview the owner's words did not already require or that edited the chat's published page, a stopped lane or coding loop, or a spent unrequested-Operations or private-network denial fuse. Such a turn gets the report followed by its own receipt, which is main's outcome plus the output-limit line. The feature keeps its purpose: a cut plain answer, a cut first write of a new site, a cut after non-visual writes or a tool error, and a cut after index.html was saved under a request that itself asks for the page (the continuation derives the same preview requirement from the words) are still continued. observeRun now records whether the owner's words alone required the preview (workspacePreviewRequestedByOwner, AND-ed over the run's classifications); workspacePreviewRequired is computed exactly as before. Tests (each red without the fix; each clause red on its own): - output_limit_recovery.test.mjs: the reviewer's ingress reproducers with uncut controls (stale and failed publication for an edit and a website, a visual write the owner did not ask a preview for x3, an edit of the chat's published page, a stopped workspace lane); - output_limit_continuation.test.mjs: a published-page edit under a request that names it, a stopped coding loop, a private-network denial and a spent unrequested-Operations fuse; - runtime_output_limit_continuation.integration.mjs: the reviewer's real-stack reproducers plus the published-page edit, each with its uncut control (the fixture's second owner message can now be given). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sensitive Files DetectedTrigger: Security-sensitive files detected: .github/workflows/ Files flagged: Extra human review is recommended for this PR. Claude Code Review | Sensitive File Detection | ~$1.50 |
Supersedes #6742. This PR is the one output-limit change: it contains #6742's original commit
e86cd4e4band, ina285ba2a6, the substance of that PR's adversarial-review fixes (the WIP rework56f724cc5is not merged; its fixes are re-done here with tests). #6742 is closed without merging.Review, in order:
e86cd4e4b(from fix(pixel): report a reply cut at the output limit instead of ending silently #6742): Pixel records a final reply cut at the output limit (stopReasonlength) and reports it.f88b6fbaf: prevention (the model is told its per-reply output limit).8639d2994: automatic recovery (one continuation turn through the Portal ingress).7ab7754a1: fixes from the first adversarial review of this PR.293680e1b: the real-harness test loads the real plugin.a285ba2a6: fix(pixel): report a reply cut at the output limit instead of ending silently #6742's review fixes, the report scoped and worded per request, and the finalize revise removed.9ab8e3469: fixes for the confirmed findings of the two adversarial reviews ofa285ba2a6(table below).924194884,764210fc1(tests only): the post-grant Stop case runs with nothing left to abort (it fails ona285ba2a6), and theafter-replyvalue is pinned equal in plugin and ingress.bf17d66d3: no continuation for a turn whose latest test run failed or is still running (re-verification finding below).9fc646b98(no behaviour change): the shipped doc, the plugin comment and the tests describe the recorded 2026-09-25 run without naming the test host it ran on.17029d780(new): no continuation for a turn that carries what its own tools established: a preview attempt, a preview that only a visual write required, an edit of the chat's published page, a stopped lane or coding loop, or a spent denial fuse (round-4 findings below).The problem
writecall. The reply stopped atlengthafter 8192 tokens and 220 s. The unfinished call never ran, nothing was saved, and the owner got nothing.runs/tower1-main-d4a61f33-sweep1/09-sales-dashboard/: "make a dashboard showing some fake sales data with charts" was graded NOTHING with stopcut-at-output-limit.Behaviour
How OpenClaw 2026.6.33 ends a cut turn
before_agent_finalizeand replaces the cut reply, text answers included, with its incomplete-turn text: "attempt.lastToolError: a failing last tool call, or a failed mutating call that was not retried identically),resolveIncompleteTurnPayloadTextreturns nothing and OpenClaw delivers the cut reply's own text instead.Pixel cannot revise either inside the run, so the continuation is driven from the ingress, like
/pixel-ods/read-only-extension-continuation.1. Prevention
configuredMaxOutputTokens(config, agentId, context)(output-limit-recovery.mjs) reads the output limit the way OpenClaw 2026.6.33 applies it:maxTokens/max_completion_tokens/max_tokensalias), clamped to the model row'smaxTokens; otherwise the row's ownmaxTokens;modelProviderId/modelIdin the hook context);promptContractForAgentadds one line right after the conversation contract, before every per-request section. For strixy:52b4e81cbremoved. It limits one reply, not the result.2. Continuation (Portal ingress)
When the ingress asks: after every completed owner turn that no other ingress continuation already recovered. Only Pixel knows whether the run was cut, because after a tool error OpenClaw delivers ordinary-looking text. This matches the existing per-turn
/pixel-ods/unfinished-extension-decisionrequest. The request body is{runId, incompleteTurn};incompleteTurnsays whether OpenClaw answered with its incomplete-turn text.The request is optional. A 404 (older plugin), a non-200 status, a reset connection, or invalid or truncated JSON is a refusal. The turn's own receipt is then delivered.
Plugin grant (
POST /pixel-ods/output-limit-continuation,outputLimitContinuationForRun). It returns{schemaVersion, kind, eligible:true, user}only when all of these hold:the run's final saved reply was cut (
outputLimitStop), including after earlier tool calls in the same turn;no pass has been used yet;
it is an owner chat turn (
agent:pixel:openai-user:ods-…, triggeruser, not a team worker);the turn was not cancelled, and did not stop on the progress budget, a web loop or a recursive-delete denial;
the turn is not waiting on owner questions or operations;
it is not a host-operation, GitHub-extension or exact-download request (those have durable receipts and their own continuations);
new (
17029d780, extendingbf17d66d3): it carries nothing its own tools established that the continuation would not inherit (outputLimitToolObligations). The continuation run is classified only by the owner message and starts with a fresh budget, so without these its reply could claim a pass or a publication that nothing checked, or retry stopped work. Refused:bf17d66d3);Such a turn keeps its own receipt, with the report when that receipt is
noneorfailed;its delivery receipt is
noneorfailed. Apassedreceipt (a verified preview, a passing test run, a host observation) means only the closing reply was cut; apendingone waits on the host;new: OpenClaw answered with its incomplete-turn text, or the request is a workspace task. A written answer whose own text OpenClaw delivered keeps that text (see the report).
The grant is used up when it is given. No tool ran after the cut: OpenClaw runs tool calls only after a complete
toolUsereply, and any later reply clearsoutputLimitStop.What the ingress sends: one new turn of the same chat with the request's trusted
systemmessages and the fixedOUTPUT_LIMIT_CONTINUATION_PROMPT. It never replays the owner's message or a tool call. The ingress and the plugin hold the same bytes, pinned equal by a test.Live activity (new): from that point the Portal's activity stream follows the continuation run (
streamTaskActivity().followNextRun()), which does most of such a turn's work.How Pixel classifies the continuation run (
outputLimitContinuationEvent): the chat's next owner turn after a grant takes it. When that turn is the fixed message (alone or behind OpenClaw's timestamp envelope), the run is classified as the owner message it continues. So it gets:index.htmlthe cut run already wrote, without writing it again. This applies when the owner's words asked for the page. A turn where only the writtenindex.htmlmade the preview required is refused (above);The model still receives only the fixed message. Another chat, a non-owner run, any other next message, or a later copy of the text never uses the grant. Without a grant, the fixed message alone selects no workspace, preview, visual-edit, operations or extension route (tested).
Bounds: the continuation's run is never granted another pass. An owner turn gets at most one continuation of any kind. The continuation shares the owner turn's ingress timeout.
Failures: if the gateway answers the continuation with a non-200 status, the owner receives the cut turn's report plus: "Pixel's automatic continuation after the output limit did not complete (gateway HTTP N). It may have acted before it stopped; check what it changed before asking Pixel to continue." It is not retried. An invalid continuation response ends the turn with the ingress error, as the extension continuations do.
Cancellation:
abortUserRunacknowledges it, so the ingress closes the continuation request (OpenClaw aborts a run whose HTTP client disconnects). A continuation run that still starts is cancelled as it begins, so its tool calls are blocked;/pixel-ods/abort.3. The report (only when no continuation ran, or the continuation was cut too)
noneorfailed.pendingreceipt is unchanged.passedreceipt is unchanged.continuationAllowed(a/goalplan) reads the receipt without the report.failed.after-replyreceipt. The ingress (and OpenClaw's reply delivery hook) keep OpenClaw's delivered text and add the report after it. They use the report alone in place of OpenClaw's incomplete-turn text,NO_REPLYor an empty reply. The ingress acceptsafter-replyonly on afailedreceipt with text and no preview, recovery code or questions. It keepssuppressStaleExecWarningon that receipt so the kept reply loses a stale exec warning.Round-4 re-verification of
9fc646b98, and how17029d780resolves itFindings (confirmed; the same class as the failed-test hole
bf17d66d3closed).Tool-derived obligations were lost. The grant carried only
runId,ownerText,continueProjectandsavedPaths, andoutputLimitContinuationEventseeded onlysuccessfulReadPaths. The continuation run is classified only by the owner message, so it did not inherit what the cut run's own tools had established:workspaceVisualArtifactProduced) when the owner's words did not ask for a preview;workspacePreviewAttempted) and its failure;workspaceLastVerifiedPreview).The continuation then delivered an unchecked "published", "ready" or "republished" claim with outcome
none, wheremainand the uncut control deliverfailed. On the real stack at9fc646b98(the new integration cases, red run):index.htmlwas saved: "Your fox page is ready and published",none;none;none;none.A stopped lane was retried. A workspace-lane stop (
progressBudget.exhaustedLanes, which forces the receipt tofailed) leavesprogressBudget.exhaustedfalse. The grant was therefore given, and the continuation got a fresh budget and retried the stopped lane.Fix (the conservative rule). The grant is refused when the cut run carries anything its own tools established that the continuation would not inherit (
outputLimitToolObligations):bf17d66d3rule, moved here);codingExhausted);Such a turn keeps its own receipt with the report leading. That is
main's outcome plus the output-limit line.Why refuse rather than carry the obligations. Carrying them would mean copying each piece of per-run state into the continuation run: the preview requirement and its bound directory, the attempt and its failure code, the last verified preview, the lane and coding stops, and the denial fuses. That is the same kind of per-field state that the earlier rounds missed one piece at a time. Refusing leaves the feature's purpose intact. These are still continued, with their uncut controls unchanged on the real stack:
text);site,unfounded ready claim);tools then cut);site after a tool error).Only turns whose own tools already produced something the host must check are reported instead, which is what
maindoes for them.One exception, kept on purpose. A visual write does not refuse the grant when the owner's words already required the preview.
observeRunnow records this asworkspacePreviewRequestedByOwner, AND-ed across the run's classifications;workspacePreviewRequiredis computed exactly as before. The continuation is classified by the same words, so it derives the same requirement and mode, and an unfounded claim in it is reported as failed (theunfounded ready claimcase). This keeps the R2-1 caseindex.html saved, then cutcontinued andpassed. That is the shape the prevention line asks for. An edit of the chat's published page refuses even then, because that binding is the run's own.Evidence.
9fc646b98(11/15 pass; each fails witheligible:true). All 15 pass on17029d780and on17029d780merged withmain0a632581e.zz_adv6743_r4_runtime.integration.mjs, unchanged) on17029d780:failed;zz_adv6743_r4.test.mjs, unchanged, locally) on17029d780:eligible:false, and no continuation run starts;failedwith the report followed by the control's receipt text (for S5, the lane-stop text).9fc646b98on Linux (68 other tests in the two files pass). Locally, each clause ofoutputLimitToolObligationswas removed on its own, and each removal turns at least one new test red. Treating every visual write as an obligation instead turnsa continuation publishes a page whose index.html the cut turn already savedred.Re-verification finding on
764210fc1, and howbf17d66d3resolves itFinding. The continuation could replace a failed test result with an unchecked success claim. The reviewer's real-stack sequence:
calc.pyplustest_calc.pyand asks to run the tests.incompleteTurn:false, and the plugin grants a continuation because the request is a workspace task.calc.pyand replies "all tests pass now" without re-running the tests.nonewith a false success. On main this was delivered asfailed.Fix (the simplest rule that cannot report a false success). The grant is refused when the cut run's
latestVerificationStatusisfailedorpending. The continuation run starts with no test result of its own, so it is never given a turn in which a pass could be claimed over a failed or unfinished test run. Such a turn keeps today's honest report: the cut run's ownfailedreceipt with the report leading, exactly asa285ba2a6delivered. Nothing else about the grant changed.Evidence.
zz_rr2_6743_runtime.integration.mjs, unchanged) onbf17d66d3: outcomefailed, 447 characters: the workspace report followed by "Pixel could not complete this task successfully because the latest verification check failed. ...".calc.pyis left as the failing version, because the cut fix never ran. Its uncut control isfailed, as before. On764210fc1the same probe delivered outcomenonewith "Fixed add() in calc.py; all tests pass now."764210fc1(the grant iseligible:true) and passes onbf17d66d3.Review findings on
a285ba2a6, and how9ab8e3469resolves themfailed(for host-operation, extension and exact-download turns on every cut), or was continued into an unverifiednone.noneorfailed, and the grant requires the same. Ingress tests over a real guard: a verified preview, a passing unittest run and ahost.identityobservation, each with the incomplete-turn text and with the reply's own text after a tool error, serve the same receipt as the uncut control, with no continuation. Real stack: a verified page with a cut closing reply stayspassedwith the same preview.after-replyreceipt keeps the delivered text and adds the report after it (see the report). Real stack (failedread, then a cut answer): the owner receives the partial answer followed by the report, with no continuation. Ingress: the stale exec warning is removed from the kept text.outputLimitContinuationGrantedcatches every failure and returns a refusal. Tests: reset, non-JSON and truncated JSON for an ordinary answer and for a cut turn, JSON and SSE; the cut turn gets its report. The body no longer makes that claim.incompleteTurn. A workspace task is continued either way. Real stacksite after a tool error(write, failed read, cut) delivers what the uncut turn delivers.followNextRun()switches the poller to the continuation run. Ingress test: activity frames for both runs, in order. Real stacksite(slowed model): activity frames for the cut run and then the continuation run.continuationAllowedturnedfalsefor a cut run with a tool error, marking a/goalplan blocked.abortUserRunwithdraws an unstarted grant and acknowledges the Stop, and a continuation run that still starts is cancelled. Guard test (with anabortRunthat finds nothing): Stop acknowledged, the late continuation's write blocked, the next message its own turn. Ingress test over a real guard: a Stop right after the grant leaves one run only. (b)continuationAllowedreads the receipt without the report, as on main (tested).index.html, the continuation was refused the preview, and an identical rewrite ends OpenClaw's run.index.html saved, then cutwith a stand-in preview host:passed, with the same text, preview and published files as the uncut turn.#6742 review findings (resolved in
a285ba2a6)e86cd4e4bsuppressStaleExecWarning: truewas rejected by the ingress (HTTP 502). The lane-stop branch had the same pattern.failedorpendingdrops the key (ingressReceipt), except anafter-replyreceipt, which the ingress accepts with it.9ab8e3469keeps it (R1-2).maxOutputTokens: reported, not changed
ods/bin/ods-host-agent.py_pixel_max_tokens_for_contextsetsmin(8192, context // 4). At a 131072-token context that is 8192 (6.25% of the context). On reasoning models, thinking tokens count against it too.min(16384, context // 8). Costs: the compaction reserve goes from 32768 to 39322 at 131072, and a maximum-length reply on strixy would take about 440 s at ~37 tok/s.Tests
tests/output_limit_recovery.test.mjs(ingress level, loopback TCP, also runs on Windows): the real ingress in front of a gateway double whose Pixel routes (/verification,/output-limit-continuation,/abort) are served by a real tool-loop guard.after-replyreceipt contract: kept after a visible reply, used alone in place of every incomplete-turn form,NO_REPLYor an empty reply; no duplicate when already appended. Rejected (502): onpassed,pendingornone, without text, with a preview, recovery code or questions, over the bound. A plain failed receipt withsuppressStaleExecWarningis still rejected.tests/output_limit_continuation.test.mjs:index.htmlpublished by the continuation; the tool-error path continued only for a workspace task, withcontinuationAllowedtrue; a verified page kept with no grant; a Stop after the grant withdrawing it;incompleteTurn;tests/output_limit_prevention.test.mjs(unchanged).tests/runtime_output_limit_continuation.integration.mjs: pinned OpenClaw 2026.6.33, the real plugin and the real ingress on the Portal history path, a fixture model, and now a stand-in ODS preview host on a private socket. The gateway process reaches it through a preload that redirects only the plugin's preview socket path. Each continued case must deliver the same text, outcome, preview, files, published files and system prompt as the same model behaviour uncut.site(also checks that the live activity follows the continuation run),tools then cut,unfounded ready claim,text, newindex.html saved, then cut, newsite after a tool error.cut again,text cut again.bf17d66d3): "a failed test run is not continued into an unchecked claim". This is the re-verification sequence: writecalc.py(buggy) andtest_calc.py, runpython3 -m unittest -v, which fails, then cut the fix write. There is no grant (incompleteTurn:false) and no continuation run. The owner gets the report followed by the failed-verification text, outcomefailed, andcalc.pyunchanged. The uncut control (fix, then the same claim) isfailedtoo./workspace. The unittest run that Pixel binds to/workspace/Playground/calctherefore runs in the fallback directory and exits 5 with no tests run. Pixel treats that as a failed test run, just as it treats a failing assertion.host/cancellable-exec.shinto its disposable~/.openclaw/.ods-exec-controland runs exec withsecurity:"full", ask:"off". No other case calls exec.tests/output_limit_recovery.test.mjsnew (bf17d66d3): "a cut after a failed or still-running test run keeps the failed report and is not continued". It covers the same sequence at ingress level over a real guard, plus a website whose test run is stillrunningwhen the reply is cut (the website receipt isfailed, so without the fix it would be continued into a publishedpassedpage).17029d780):tests/output_limit_recovery.test.mjs, "a cut after … keeps the failed receipt and is not continued": 9 tests at ingress level over a real guard, each with its uncut control.tests/output_limit_continuation.test.mjs, "a cut run whose own tools stopped, denied or bound its work gets no continuation": 4 tests at guard level.tests/runtime_output_limit_continuation.integration.mjs, "a cut after … is not continued": 4 real-stack cases, each with its uncut control.Adversarial re-verification of
bf17d66d3The refusal was attacked on every transport and status source. Each case below was run through the real ingress in front of a real guard. The
tool_call, background and native cases were also run on the real stack (pinned OpenClaw 2026.6.33, the real plugin and ingress).exec, a failed Tool Searchtool_callexec, a failed background run completed byprocesspoll, a run still running at the cut, and a refused piped test command all refuse the grant. None of them delivers a pass. In the real stack, thetool_calland native cases deliver the workspace report plus "the latest verification check failed". The background cases deliver the pending text.ls,node --checkornpx htmlhint; and a website with a backgroundpython3 -m http.server. A later passing test after a failure leaves the receiptpassed, which is never continued (as before).main).none. With the line in place it passes.main's outcome plus the output-limit line.9fc646b98evidence: full Linux suite on tower2 at the head, 3193 tests / 3155 pass / 0 fail; merged withmain5afd46cf2(clean, not pushed), 3275 / 3237 / 0.runtime_output_limit_continuation.integration.mjspasses 11/11 on9fc646b98(and 11/11 onbf17d66d3merged with5afd46cf2).Test evidence
17029d780(this push). It was tested against the currentmain,0a632581e(5afd46cf2plus #6750). The merge is clean; the merge commit8fe239883was made locally for testing only and not pushed.Full Linux suite on tower2 (
remote-js-test.sh):17029d7800a632581eReal stack on tower2 (pinned OpenClaw 2026.6.33 at
~/.npm-global/lib/node_modules/openclaw, the real plugin and ingress; temporary directory under/mnt/bulk/claude-tests, removed and checked afterwards).runtime_output_limit_continuation.integration.mjspasses 15/15 on the head and 15/15 merged with0a632581e. That is every earlier case plus the four new ones.Red checks:
9fc646b98: 11/15. Only the four new cases fail, each witheligible:true;9fc646b98(Linux): 68/81. Only the 13 new tests fail;Kept behaviour (all in the suites and real-stack runs above):
index.html saved, then cut;/goalplans work as before;Local Windows: the three output-limit files pass (84/85, 1 skipped by design).
tool_loop_guard.test.mjspasses 686/687; the one failure ("exec cancellation control creates exact owner-private markers") fails the same way on9fc646b98on Windows and passes in the Linux suite.bf17d66d3. It was tested against321f82a30and5afd46cf2; both merges were clean and not pushed.Full Linux suite on tower2 (
remote-js-test.sh):bf17d66d3321f82a305afd46cf2Real stack on tower2 (pinned OpenClaw 2026.6.33 at
~/.npm-global/lib/node_modules/openclaw, the real plugin and ingress; temporary directory under/mnt/bulk/claude-tests, removed and checked afterwards).runtime_output_limit_continuation.integration.mjspasses 11/11 on the head, 11/11 merged with321f82a30and 11/11 merged with5afd46cf2. That is every earlier case plus the new one.Red checks:
764210fc1: 10/11. Only the new case fails, with[{eligible:true, incompleteTurn:false}];output_limit_recovery.test.mjson764210fc1(Linux): 30/31. Only the new test fails;Kept behaviour (all in the suites and real-stack runs above):
site after a tool error, and the failed-readingress case);/goalplans (continuationAllowed) and a savedindex.htmlall work as before;Earlier rounds:
remote-js-test.sh, alltests/*.test.mjs):764210fc1:# tests 3192 / # pass 3154 / # fail 0;maind4a61f331(clean merge, test-only commit18b664f95, not pushed):# tests 3209 / # pass 3171 / # fail 0;9ab8e3469gave the same counts, alone and merged; previous heada285ba2a6:3173 / 3135 / 0.runtime_output_limit_continuation.integration.mjspasses 10/10 on9ab8e3469and 10/10 on it merged withmain. The two later commits change only unit-test files.read: the owner receives the 194-character partial answer, then the report.passed, with the same preview as the uncut turn.index.html saved, then cut:passed, with the uncut turn's text, preview and published files.site(slowed model): live-activity frames for the cut run, then the continuation run.zz_rv6743eff_replay.probe.mjs, unchanged, with exact tower1/strixy config shapes and a stand-in preview host), run on9ab8e3469: 11/11 scenarios completed.passed(ona285ba2a6:failed, preview refused). The two recovery variants and all continued cases arepassed, and both cut-again cases arefailedwith one continuation.a285ba2a6: the cut run only).a285ba2a6:site: activity for the cut run only.index.html saved, then cut: "Pixel did not create or verify the requested website files".site after a tool error: no grant request. The delivered answer: the report only, partial lost. The verified page: continued, then reportedfailed.index.html, tool-error continuation, verified results (preview, test run, host observation), the Stop after the grant (guard and ingress level), broken grant routes, live activity, delivered answer text and theafter-replycontract. Other cases fail only on the newincompleteTurnfield or the reworded answer text.106/105/0, 1 skipped by design).tests/pixel_ingress.test.mjsfails 47 of 65 on Windows both ona285ba2a6and on9ab8e3469, with the same 47 failing cases (its harness serves the ingress on a Unix socket path); it passes on Linux.764210fc1: all 49 checks pass (3 skipped). The real-runtime step "Verify a reply cut at the output limit is continued once through the real runtime" (now with the stand-in preview host and the four new cases) passed on ubuntu-24.04 and macos-14; it is skipped on Windows by design.Known differences
/pixel-ods/output-limit-continuation), as it already does for/pixel-ods/unfinished-extension-decision. For an uncut run the answer iseligible:false.main. With OpenClaw's incomplete-turn text, that means the warning line followed by the verified evidence (for example "appendreceipts would be a separate change to main's delivery.bf17d66d3for a failed or running test run,17029d780for the rest). It keeps its own receipt, with the report when that receipt isnoneorfailed.Not run
🤖 Generated with Claude Code