Skip to content

fix(pixel): plan within the output limit, continue a cut Portal turn once, report otherwise - #6743

Open
Lightheartdevs wants to merge 12 commits into
mainfrom
claude/pixel-output-limit-prevention
Open

Lightheartdevs wants to merge 12 commits into
mainfrom
claude/pixel-output-limit-prevention

Conversation

@Lightheartdevs

@Lightheartdevs Lightheartdevs commented Sep 25, 2026 •

Copy link
Copy Markdown
Collaborator

Supersedes #6742. This PR is the one output-limit change: it contains #6742's original commit e86cd4e4b and, in a285ba2a6, the substance of that PR's adversarial-review fixes (the WIP rework 56f724cc5 is 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 (stopReason length) 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 of a285ba2a6 (table below).
  • 924194884, 764210fc1 (tests only): the post-grant Stop case runs with nothing left to abort (it fails on a285ba2a6), and the after-reply value 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

  • strixy, 2026-09-25 (Qwen3.6-35B-A3B, 131072 context, 8192 output tokens): "make me a cool looking webpage with a forest theme ... Best you can do". The model wrote the whole page in one write call. The reply stopped at length after 8192 tokens and 220 s. The unfinished call never ran, nothing was saved, and the owner got nothing.
  • tower1 (Qwen3.5-27B, 65536 context, 8192 cap), fleet sweep runs/tower1-main-d4a61f33-sweep1/09-sales-dashboard/: "make a dashboard showing some fake sales data with charts" was graded NOTHING with stop cut-at-output-limit.
  • The same prompts delivered on other runs, so whether a turn is cut depends on how large the model's reply happens to be.

Behaviour

  1. Prevention. The prompt contract states the running model's per-reply output limit and asks for several smaller files or writes.
  2. Continuation first. When a Portal owner turn ends on a cut reply and its result is not already settled, the ingress gets one continuation turn from Pixel and submits a fixed message. The owner receives the continuation's result.
  3. Report otherwise. When no continuation ran, or the continuation was cut too, the owner receives a plain report in words that fit the request. A verified result is never reported over, and a written answer keeps whatever part of it OpenClaw delivered.

How OpenClaw 2026.6.33 ends a cut turn

  • Normally it skips before_agent_finalize and replaces the cut reply, text answers included, with its incomplete-turn text: "⚠️ Agent couldn't generate a response. Please try again.", or "… Note: some tool actions may have already been executed …" after tool work.
  • After a tool error (attempt.lastToolError: a failing last tool call, or a failed mutating call that was not retried identically), resolveIncompleteTurnPayloadText returns 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:
    • a positive params limit (defaults, then the model's defaults entry, then the agent; any maxTokens/max_completion_tokens/max_tokens alias), clamped to the model row's maxTokens; otherwise the row's own maxTokens;
    • for the model the run actually resolved (modelProviderId/modelId in the hook context);
    • undefined when that model has no configured row (for example a dynamic provider); then no line is added.
  • promptContractForAgent adds one line right after the conversation contract, before every per-request section. For strixy:

    One reply, including its thinking and tool-call arguments, can hold at most about 8192 output tokens; a longer reply is cut off and its unfinished tool call does not run. This limits each reply, not the size of your work: write large content as several files or several smaller writes (for a web page, separate index.html, styles.css and script.js), each well under 4096 tokens (about 12288 characters).

  • Cache stability: the line depends only on the host's model configuration, so its bytes are the same on every turn for a host.
  • No size ceiling: it does not bring back the creative-size ceiling that 52b4e81cb removed. 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-decision request. The request body is {runId, incompleteTurn}; incompleteTurn says 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-…, trigger user, 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, extending bf17d66d3): 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:

      • a latest test run that failed or is still running (bf17d66d3);
      • any preview attempt (failed, stale or verified);
      • a successful visual write whose preview the owner's words did not already require, or one that edited the page the chat had already published;
      • a stopped mixed-task lane or coding loop;
      • a spent unrequested-Operations or private-network denial fuse.

      Such a turn keeps its own receipt, with the report when that receipt is none or failed;

    • its delivery receipt is none or failed. A passed receipt (a verified preview, a passing test run, a host observation) means only the closing reply was cut; a pending one 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 toolUse reply, and any later reply clears outputLimitStop.

  • What the ingress sends: one new turn of the same chat with the request's trusted system messages and the fixed OUTPUT_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:

    • the owner request's system prompt, byte for byte;
    • its routing, including the Playground project: files the cut run already wrote into a new project stay in that project;
    • new: the files the cut run saved count as inspected, so the continuation can publish a page whose index.html the cut run already wrote, without writing it again. This applies when the owner's words asked for the page. A turn where only the written index.html made the preview required is refused (above);
    • its delivery checks: an unfounded "your page is ready" is reported as failed, exactly as in an uncut turn.

    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:

    • a cancelled run is refused;
    • the eligibility check and the continuation use the request's abort signal, so a disconnect before the grant starts nothing;
    • new: a Stop that reaches Pixel after the grant and before the continuation starts withdraws the grant. abortUserRun acknowledges 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;
    • during the continuation, the new run is the chat's active run for /pixel-ods/abort.

3. The report (only when no continuation ran, or the continuation was cut too)

  • Who gets it: owner chat turns whose delivery receipt is none or failed.
    • Heartbeat, cron and team-worker receipts are unchanged.
    • A pending receipt is unchanged.
    • New: a passed receipt is unchanged. continuationAllowed (a /goal plan) reads the receipt without the report.
  • Wording per request. A workspace task is a requested file change or one the run made. A written answer is everything else.
    • Workspace task: "Pixel's reply reached the model's output limit before it finished, so anything that reply was still writing, such as a large file, was not saved. Files saved by earlier steps are kept. Ask Pixel to continue in smaller steps, with large content split across several smaller files."
    • Written answer (reworded): "Pixel's answer reached the model's output limit before it finished and was cut off. Ask for a shorter answer, or ask Pixel to answer in parts." It never says a file write was cut, and it reads correctly after the delivered part of the answer.
    • A continuation cut again starts with: "Pixel already continued this request once automatically after an earlier reply was cut off."
  • Receipt: failed.
    • New: a written answer with no receipt of its own gets an after-reply receipt. 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_REPLY or an empty reply. The ingress accepts after-reply only on a failed receipt with text and no preview, recovery code or questions. It keeps suppressStaleExecWarning on that receipt so the kept reply loses a stale exec warning.
    • Otherwise the report leads, followed by the run's own receipt text (for example the website check) when both fit the ingress's 32 KiB receipt bound. If they do not fit, the report stands alone. The ingress's continuation-failure line keeps to the same bound.

Round-4 re-verification of 9fc646b98, and how 17029d780 resolves it

Findings (confirmed; the same class as the failed-test hole bf17d66d3 closed).

  1. Tool-derived obligations were lost. The grant carried only runId, ownerText, continueProject and savedPaths, and outputLimitContinuationEvent seeded only successfulReadPaths. The continuation run is classified only by the owner message, so it did not inherit what the cut run's own tools had established:

    • the preview requirement that a successful visual write sets (workspaceVisualArtifactProduced) when the owner's words did not ask for a preview;
    • the requirement and bound directory that an edit of the chat's published page sets;
    • a publication attempt (workspacePreviewAttempted) and its failure;
    • a verified publication that a later change made stale (workspaceLastVerifiedPreview).

    The continuation then delivered an unchecked "published", "ready" or "republished" claim with outcome none, where main and the uncut control deliver failed. On the real stack at 9fc646b98 (the new integration cases, red run):

    • a fox demo whose index.html was saved: "Your fox page is ready and published", none;
    • a failed publication: "your page is published and live", none;
    • a stale publication: the same claim, none;
    • an edit of the chat's published page: "the page is republished", none.
  2. A stopped lane was retried. A workspace-lane stop (progressBudget.exhaustedLanes, which forces the receipt to failed) leaves progressBudget.exhausted false. 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):

  • a latest test run that failed or is still running (the bf17d66d3 rule, moved here);
  • any preview attempt: a failed, stale or verified publication;
  • a successful visual write whose preview the owner's words did not already require, or one that edited the page the chat had already published;
  • a stopped mixed-task lane, or a stopped coding loop (codingExhausted);
  • a spent unrequested-Operations fuse or private-network denial fuse.

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:

  • a cut plain text or code answer (text);
  • a cut first write of a new site (site, unfounded ready claim);
  • a cut after non-visual writes (tools then cut);
  • a cut after a tool error (site after a tool error).

Only turns whose own tools already produced something the host must check are reported instead, which is what main does for them.

One exception, kept on purpose. A visual write does not refuse the grant when the owner's words already required the preview. observeRun now records this as workspacePreviewRequestedByOwner, AND-ed across the run's classifications; workspacePreviewRequired is 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 (the unfounded ready claim case). This keeps the R2-1 case index.html saved, then cut continued and passed. 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.

  • Real stack (pinned OpenClaw 2026.6.33): the four new integration cases fail on 9fc646b98 (11/15 pass; each fails with eligible:true). All 15 pass on 17029d780 and on 17029d780 merged with main 0a632581e.
  • The reviewer's real-stack probe file (zz_adv6743_r4_runtime.integration.mjs, unchanged) on 17029d780:
    • fox demo, failed publication and stale publication each get no continuation (2, 3 and 6 model requests);
    • the owner receives the workspace report followed by the uncut control's text, outcome failed;
    • the stale case keeps the control's "last published preview" link.
  • The reviewer's unit probe (zz_adv6743_r4.test.mjs, unchanged, locally) on 17029d780:
    • S1-EDIT, S1-SITE, S2-EDIT (own text and incomplete-turn text), S2-SITE, S3 ×3, S4, S5 and S5b: every grant is eligible:false, and no continuation run starts;
    • every cut delivers failed with the report followed by the control's receipt text (for S5, the lane-stop text).
  • Red checks: the 13 new unit tests fail on 9fc646b98 on Linux (68 other tests in the two files pass). Locally, each clause of outputLimitToolObligations was removed on its own, and each removal turns at least one new test red. Treating every visual write as an obligation instead turns a continuation publishes a page whose index.html the cut turn already saved red.

Re-verification finding on 764210fc1, and how bf17d66d3 resolves it

Finding. The continuation could replace a failed test result with an unchecked success claim. The reviewer's real-stack sequence:

  1. The owner asks for calc.py plus test_calc.py and asks to run the tests.
  2. The unittest run executes and fails.
  3. The next reply, a whole-file fix write, is cut at the output limit.
  4. The failing exec is a tool error, so OpenClaw delivers the cut reply's text. The ingress asks with incompleteTurn:false, and the plugin grants a continuation because the request is a workspace task.
  5. The continuation rewrites calc.py and replies "all tests pass now" without re-running the tests.
  6. The owner gets outcome none with a false success. On main this was delivered as failed.

Fix (the simplest rule that cannot report a false success). The grant is refused when the cut run's latestVerificationStatus is failed or pending. 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 own failed receipt with the report leading, exactly as a285ba2a6 delivered. Nothing else about the grant changed.

Evidence.

  • The reviewer's probe (zz_rr2_6743_runtime.integration.mjs, unchanged) on bf17d66d3: outcome failed, 447 characters: the workspace report followed by "Pixel could not complete this task successfully because the latest verification check failed. ...". calc.py is left as the failing version, because the cut fix never ran. Its uncut control is failed, as before. On 764210fc1 the same probe delivered outcome none with "Fixed add() in calc.py; all tests pass now."
  • New real-stack case "a failed test run is not continued into an unchecked claim": fails on 764210fc1 (the grant is eligible:true) and passes on bf17d66d3.
  • New ingress unit test over a real guard: the same sequence, and a website whose test run is still running when the reply is cut. Each half fails on its own without the fix.

Review findings on a285ba2a6, and how 9ab8e3469 resolves them

Finding Resolution
R1-1 (medium). A verified result was downgraded when only the closing reply was cut: a passed preview, test run or host-operation receipt became failed (for host-operation, extension and exact-download turns on every cut), or was continued into an unverified none. The report applies to the final delivery receipt only when it is none or failed, and the grant requires the same. Ingress tests over a real guard: a verified preview, a passing unittest run and a host.identity observation, 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 stays passed with the same preview.
R1-2 (medium). When OpenClaw delivered a cut answer's own text (after a tool error), the report replaced it; real stack: 18,499 characters on main, 163 on head. The after-reply receipt keeps the delivered text and adds the report after it (see the report). Real stack (failed read, 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.
R1-3 (low). A failed grant request ended the turn with a stream error (non-stream: 502). The PR body wrongly said this matched the other ingress continuations. outputLimitContinuationGranted catches 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.
R1-4 (low). The continuation never ran after a tool error, including the motivating website cut. The ingress asks after every completed owner turn, with incompleteTurn. A workspace task is continued either way. Real stack site after a tool error (write, failed read, cut) delivers what the uncut turn delivers.
R1-5 / R2-2 (low, UX). The Portal's live activity stayed on the cut run during the continuation. followNextRun() switches the poller to the continuation run. Ingress test: activity frames for both runs, in order. Real stack site (slowed model): activity frames for the cut run and then the continuation run.
R1-6 (low, plausible). (a) A Stop after the grant, before the continuation registered, aborted nothing. (b) continuationAllowed turned false for a cut run with a tool error, marking a /goal plan blocked. (a) The grant records its run. abortUserRun withdraws an unstarted grant and acknowledges the Stop, and a continuation run that still starts is cancelled. Guard test (with an abortRun that 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) continuationAllowed reads the receipt without the report, as on main (tested).
R2-1 (medium). When the cut turn had already saved index.html, the continuation was refused the preview, and an identical rewrite ends OpenClaw's run. The grant carries the cut run's saved paths, and the continuation counts them as inspected. Guard test, and real stack index.html saved, then cut with a stand-in preview host: passed, with the same text, preview and published files as the uncut turn.
R2-3 (info). The prevention line's effect on real models is not measured. Not addressed by code. It needs a live-model run on a fleet host (see "Not run").

#6742 review findings (resolved in a285ba2a6)

Finding on e86cd4e4b Resolution
1. A failed receipt that kept suppressStaleExecWarning: true was rejected by the ingress (HTTP 502). The lane-stop branch had the same pattern. Any receipt that ends failed or pending drops the key (ingressReceipt), except an after-reply receipt, which the ingress accepts with it.
2. The receipt override applied to heartbeat, cron and team-worker runs. The report requires an owner chat turn.
3. The prepended text could exceed the 32 KiB ingress bound. The run's receipt text is kept only when both fit.
4. The finalize revise was almost never reachable, and its instruction fit only file work. Removed.
5. Cut text answers do not keep partial text in the common case. Text answers are continued when OpenClaw replaced them. When OpenClaw delivered their text (after a tool error), 9ab8e3469 keeps it (R1-2).

maxOutputTokens: reported, not changed

  • ods/bin/ods-host-agent.py _pixel_max_tokens_for_context sets min(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.
  • Recommendation: for contexts of 128K and above, consider 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.
  • Not changed here: it is fleet-wide, it interacts with the compaction reserve, and qualification rounds are running. Raising it only moves the cliff; this PR addresses the failure itself.

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.
    • A cut answer whose own text OpenClaw delivered keeps it, without the stale exec warning, followed by the report (JSON and SSE). A lane stop after a stale exec warning: 200, not 502.
    • A written answer that OpenClaw replaced and that is not continued: the report alone.
    • Text answer and workspace task continued, then cut again: the report in their own words.
    • Continued workspace task, with and without a tool error first; continued text answer.
    • Verified results kept: a preview, a test run and a host observation, each with both kinds of OpenClaw text, compared with the uncut control.
    • A Stop right after the grant: acknowledged, one run only.
    • A later complete reply clears the report. Heartbeat, cron and team worker are unchanged, and so is a pending receipt.
    • The 32 KiB bound, and the continuation-failure line.
    • The after-reply receipt contract: kept after a visible reply, used alone in place of every incomplete-turn form, NO_REPLY or an empty reply; no duplicate when already appended. Rejected (502): on passed, pending or none, without text, with a preview, recovery code or questions, over the bound. A plain failed receipt with suppressStaleExecWarning is still rejected.
  • tests/output_limit_continuation.test.mjs:
    • grant rules and classification;
    • new: a saved index.html published by the continuation; the tool-error path continued only for a workspace task, with continuationAllowed true; a verified page kept with no grant; a Stop after the grant withdrawing it;
    • the ingress mechanics: grant bodies carry incompleteTurn;
    • new: broken grant routes never fail a turn; the live activity follows the continuation run.
  • 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.
    • Continued cases: site (also checks that the live activity follows the continuation run), tools then cut, unfounded ready claim, text, new index.html saved, then cut, new site after a tool error.
    • Report cases: cut again, text cut again.
    • New report-only cases: a cut answer OpenClaw delivered keeps its text; a verified page is kept when only the closing reply is cut.
    • New (bf17d66d3): "a failed test run is not continued into an unchecked claim". This is the re-verification sequence: write calc.py (buggy) and test_calc.py, run python3 -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, outcome failed, and calc.py unchanged. The uncut control (fix, then the same claim) is failed too.
      • This fixture's gateway host has no /workspace. The unittest run that Pixel binds to /workspace/Playground/calc therefore 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.
      • The fixture now copies host/cancellable-exec.sh into its disposable ~/.openclaw/.ods-exec-control and runs exec with security:"full", ask:"off". No other case calls exec.
  • tests/output_limit_recovery.test.mjs new (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 still running when the reply is cut (the website receipt is failed, so without the fix it would be continued into a published passed page).
  • New (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.
      • A publication that a later change made stale, for an edit request and for a website request.
      • A failed publication, for an edit request and for a website request, each with both kinds of OpenClaw text.
      • A visual write the owner did not ask a preview for (fox demo, something fun, styles change).
      • An edit of the chat's published page.
      • A stopped workspace lane of a mixed request.
    • 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.
      • An edit of the published page under a request that names it.
      • A stopped coding loop.
      • A private-network denial.
      • A spent unrequested-Operations fuse.
    • tests/runtime_output_limit_continuation.integration.mjs, "a cut after … is not continued": 4 real-stack cases, each with its uncut control.
      • A visual write the owner did not ask a preview for.
      • A failed publication.
      • A publication that a later change made stale.
      • An edit of the chat's published page, in a second owner turn. The fixture's second owner message can now be given.

Adversarial re-verification of bf17d66d3

The 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).

  • The false claim is closed on every test-status path. A failed native exec, a failed Tool Search tool_call exec, a failed background run completed by process poll, 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, the tool_call and native cases deliver the workspace report plus "the latest verification check failed". The background cases deliver the pending text.
  • Legitimate continuations still run. These cases are still continued, with the owner request's own checks: a tool error without a test run (the continuation keeps "owner-requested verification was not executed" when the request names the workspace, the same as the uncut control); a website after a failed ls, node --check or npx htmlhint; and a website with a background python3 -m http.server. A later passing test after a failure leaves the receipt passed, which is never continued (as before).
  • Lifecycle. The status belongs to its run, so a failed test in an earlier owner turn does not block the next turn's continuation. That next turn's continuation delivers what its uncut control delivers. A pending run that never finishes keeps the pending receipt (unchanged from main).
  • Red check (real stack, tower2): with the refusal line removed, the new integration case fails. The owner receives "Fixed add() in calc.py; all tests pass now." with outcome none. With the line in place it passes.
  • Scope note (not changed here): a turn stopped between a failed test and its re-run is refused, not continued. The owner gets the honest failed report, which is main's outcome plus the output-limit line.

9fc646b98 evidence: full Linux suite on tower2 at the head, 3193 tests / 3155 pass / 0 fail; merged with main 5afd46cf2 (clean, not pushed), 3275 / 3237 / 0. runtime_output_limit_continuation.integration.mjs passes 11/11 on 9fc646b98 (and 11/11 on bf17d66d3 merged with 5afd46cf2).

Test evidence

17029d780 (this push). It was tested against the current main, 0a632581e (5afd46cf2 plus #6750). The merge is clean; the merge commit 8fe239883 was made locally for testing only and not pushed.

  • Full Linux suite on tower2 (remote-js-test.sh):

    Tree tests pass fail
    head 17029d780 3206 3168 0
    merged with 0a632581e 3290 3252 0
  • Real 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.mjs passes 15/15 on the head and 15/15 merged with 0a632581e. That is every earlier case plus the four new ones.

  • Red checks:

    • the new integration file on 9fc646b98: 11/15. Only the four new cases fail, each with eligible:true;
    • the two unit files on 9fc646b98 (Linux): 68/81. Only the 13 new tests fail;
    • locally, each clause of the refusal fails at least one new test on its own.
  • Kept behaviour (all in the suites and real-stack runs above):

    • these are still continued, and deliver what their uncut controls deliver: a cut text answer, a cut first write of a site, a cut after non-visual writes, a cut after a tool error, and index.html saved, then cut;
    • verified results are kept, and a partial answer is kept;
    • a failed test run is still refused;
    • Stop handling and /goal plans work as before;
    • the live activity follows the continuation run;
    • there is at most one continuation.
  • Local Windows: the three output-limit files pass (84/85, 1 skipped by design). tool_loop_guard.test.mjs passes 686/687; the one failure ("exec cancellation control creates exact owner-private markers") fails the same way on 9fc646b98 on Windows and passes in the Linux suite.

bf17d66d3. It was tested against 321f82a30 and 5afd46cf2; both merges were clean and not pushed.

  • Full Linux suite on tower2 (remote-js-test.sh):

    Tree tests pass fail
    head bf17d66d3 3193 3155 0
    merged with 321f82a30 3264 3226 0
    merged with 5afd46cf2 3275 3237 0
  • Real 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.mjs passes 11/11 on the head, 11/11 merged with 321f82a30 and 11/11 merged with 5afd46cf2. That is every earlier case plus the new one.

  • Red checks:

    • the new integration file on 764210fc1: 10/11. Only the new case fails, with [{eligible:true, incompleteTurn:false}];
    • output_limit_recovery.test.mjs on 764210fc1 (Linux): 30/31. Only the new test fails;
    • locally, each half of the new unit test (failed, pending) fails on its own.
  • Kept behaviour (all in the suites and real-stack runs above):

    • verified results are kept;
    • a partial answer is kept;
    • a failed grant request gives 200 with the report;
    • a turn is still continued after a tool error when no test failed (site after a tool error, and the failed-read ingress case);
    • the live activity follows the continuation run;
    • Stop handling, /goal plans (continuationAllowed) and a saved index.html all work as before;
    • there is at most one continuation;
    • heartbeat, cron and team runs are unchanged.

Earlier rounds:

  • Full Linux suite on tower2 (remote-js-test.sh, all tests/*.test.mjs):
    • head 764210fc1: # tests 3192 / # pass 3154 / # fail 0;
    • head merged with current main d4a61f331 (clean merge, test-only commit 18b664f95, not pushed): # tests 3209 / # pass 3171 / # fail 0;
    • 9ab8e3469 gave the same counts, alone and merged; previous head a285ba2a6: 3173 / 3135 / 0.
  • Real stack on the tower2 test area (pinned OpenClaw 2026.6.33, the real plugin and ingress; temporary directories removed and checked): runtime_output_limit_continuation.integration.mjs passes 10/10 on 9ab8e3469 and 10/10 on it merged with main. The two later commits change only unit-test files.
    • A cut answer after a failed read: the owner receives the 194-character partial answer, then the report.
    • A verified page with a cut closing reply: 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.
  • Review 2's effectiveness replay (its zz_rv6743eff_replay.probe.mjs, unchanged, with exact tower1/strixy config shapes and a stand-in preview host), run on 9ab8e3469: 11/11 scenarios completed.
    • "split file cut in the first turn, continuation finishes" is now passed (on a285ba2a6: failed, preview refused). The two recovery variants and all continued cases are passed, and both cut-again cases are failed with one continuation.
    • In every continued scenario the owner's SSE stream carries activity for the cut run and then the continuation run (on a285ba2a6: the cut run only).
  • Red checks against a285ba2a6:
    • Real stack, the new integration test: the finding cases fail on their finding. 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 reported failed.
    • Unit and ingress tests (tower2): each finding case fails there. That covers the saved 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 the after-reply contract. Other cases fail only on the new incompleteTurn field or the reworded answer text.
  • Local Windows runs: the output-limit, live-activity and empty-completion test files pass (106/105/0, 1 skipped by design). tests/pixel_ingress.test.mjs fails 47 of 65 on Windows both on a285ba2a6 and on 9ab8e3469, with the same 47 failing cases (its harness serves the ingress on a Unix socket path); it passes on Linux.
  • GitHub CI on 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

  • Asking after every owner turn. Each completed Portal owner turn makes one more loopback request (/pixel-ods/output-limit-continuation), as it already does for /pixel-ods/unfinished-extension-decision. For an uncut run the answer is eligible:false.
  • A passed receipt after a cut closing reply is delivered exactly as on main. With OpenClaw's incomplete-turn text, that means the warning line followed by the verified evidence (for example "⚠️ Agent couldn't generate a response. Note: …" and then "Your preview is ready. Open preview"). Treating that warning like an empty reply for append receipts would be a separate change to main's delivery.
  • Continuation receipts cover the continuation's own work. The cut run's saved files count as inspected, so a preview the continuation publishes covers them. A test run, publication, host receipt or stop from the cut run is not carried over. For that reason, a turn whose own tools established one is not continued at all (bf17d66d3 for a failed or running test run, 17029d780 for the rest). It keeps its own receipt, with the report when that receipt is none or failed.

Not run

  • A live-model run on a fleet host (qualification rounds are running, and fleet hosts are read-only for this work). R2-3 is therefore open. Suggested check once one is free: the strixy prompt on strixy and the sales-dashboard prompt on tower1 through the Portal. Expect either a split-file publication in the first turn, or exactly one continuation turn in the gateway log.

🤖 Generated with Claude Code

Lightheartdevs and others added 3 commits September 25, 2026 18:33
…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>
@github-actions

Copy link
Copy Markdown

Sensitive Files Detected

Trigger: Security-sensitive files detected: .github/workflows/

Files flagged:

.github/workflows/portal-runtime.yml

Extra human review is recommended for this PR.


Claude Code Review | Sensitive File Detection | ~$1.50

@Lightheartdevs
Lightheartdevs marked this pull request as draft September 26, 2026 01:01
Lightheartdevs and others added 2 commits September 25, 2026 21:16
…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>
@github-actions

Copy link
Copy Markdown

Sensitive Files Detected

Trigger: Security-sensitive files detected: .github/workflows/

Files flagged:

.github/workflows/portal-runtime.yml

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>
@Lightheartdevs Lightheartdevs changed the title fix(pixel): plan writes within the output limit and continue a cut Portal turn once fix(pixel): plan within the output limit, continue a cut Portal turn once, report otherwise Sep 26, 2026
@Lightheartdevs
Lightheartdevs marked this pull request as ready for review September 26, 2026 02:04
@github-actions

Copy link
Copy Markdown

Sensitive Files Detected

Trigger: Security-sensitive files detected: .github/workflows/

Files flagged:

.github/workflows/portal-runtime.yml

Extra human review is recommended for this PR.


Claude Code Review | Sensitive File Detection | ~$1.50

1 similar comment
@github-actions

Copy link
Copy Markdown

Sensitive Files Detected

Trigger: Security-sensitive files detected: .github/workflows/

Files flagged:

.github/workflows/portal-runtime.yml

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>
@github-actions

Copy link
Copy Markdown

Sensitive Files Detected

Trigger: Security-sensitive files detected: .github/workflows/

Files flagged:

.github/workflows/portal-runtime.yml

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>
@github-actions

Copy link
Copy Markdown

Sensitive Files Detected

Trigger: Security-sensitive files detected: .github/workflows/

Files flagged:

.github/workflows/portal-runtime.yml

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>
@github-actions

Copy link
Copy Markdown

Sensitive Files Detected

Trigger: Security-sensitive files detected: .github/workflows/

Files flagged:

.github/workflows/portal-runtime.yml

Extra human review is recommended for this PR.


Claude Code Review | Sensitive File Detection | ~$1.50

Lightheartdevs added a commit that referenced this pull request Sep 26, 2026
… 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>
@github-actions

Copy link
Copy Markdown

Sensitive Files Detected

Trigger: Security-sensitive files detected: .github/workflows/

Files flagged:

.github/workflows/portal-runtime.yml

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>
@github-actions

Copy link
Copy Markdown

Sensitive Files Detected

Trigger: Security-sensitive files detected: .github/workflows/

Files flagged:

.github/workflows/portal-runtime.yml

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>
@github-actions

Copy link
Copy Markdown

Sensitive Files Detected

Trigger: Security-sensitive files detected: .github/workflows/

Files flagged:

.github/workflows/portal-runtime.yml

Extra human review is recommended for this PR.


Claude Code Review | Sensitive File Detection | ~$1.50

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant