Skip to content

fix(wasm): report WABT output past its budget as a resource constraint - #2117

Merged
morluto merged 10 commits into
morluto:mainfrom
JayOfTheKeyboard:fix/wasm-output-limit
Oct 13, 2026
Merged

morluto merged 10 commits into
morluto:mainfrom
JayOfTheKeyboard:fix/wasm-output-limit

Conversation

@JayOfTheKeyboard

@JayOfTheKeyboard JayOfTheKeyboard commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Summary

inspect-wasm-artifact reports the wrong kind of failure when a module's WABT output is larger than the 32 MiB output budget. It returns unreadable_output, tells the caller to run rea doctor, and puts the first 33,554,432 bytes of WAT into details.captured_output.stdout, so the error is 33.6 MB of JSON. No issue is linked. I found this by running the tool over the real .wasm files on one machine, and the fix is small enough to explain here.

Problem and expected behavior

runOwnedCommand stops a WABT command when its output passes WABT_LIMITS.outputBytes (32 MiB) and throws OwnedCommandFailure with reason output-limit. wabtFailure in src/wasm/WabtFailures.ts has branches for cancelled, timeout and process, and none for output-limit. The failure falls through to AnalysisOutputError, which the CLI shows as an unreadable result, with all of the retained stdout attached.

The budget works as documented. The caller is told the result was unreadable when that documented limit was hit, and gets back the partial WAT that the limit exists to refuse.

3 of the 28 valid modules I ran hit this. All three are tree-sitter grammars. Sizes from WABT 1.0.42 run directly:

Module Input bytes wasm2wat --enable-all bytes wasm-objdump -h -x bytes
tree-sitter-cpp.wasm (VS Code Copilot) 3,434,931 43,505,653 14,665,080
tree-sitter-bash.wasm (@vscode/tree-sitter-wasm) 1,380,769 65,803,935 5,322,102
tree-sitter-powershell.wasm (@vscode/tree-sitter-wasm) 989,619 285,398,328 2,706,929
$ REA_WABT_BIN_DIRECTORY=.../wabt-1.0.42/bin rea inspect-wasm-artifact tree-sitter-cpp.wasm --format json
{"code":"unreadable_output","message":"Analysis returned an unreadable result. Run `rea doctor` and review the reported details; repeating the unchanged request is not expected to succeed.", ...
 "details":{"operation":"inspect_wasm_artifact","reason":"Command diagnostic output exceeded its complete-output budget.","captured_output":{"stdout":"(module\n  (type ...   <- 33,554,432 bytes

Expected: an error that says which limit was hit and does not carry 32 MiB of partial output. The apktool provider (src/apktool/ApktoolProvider.ts) and the native macOS provider (src/native/NativeMacOSProvider.ts) already map output-limit to AnalysisResourceConstraintError with resource memory and max_output_bytes.

Change and scope

  • During inspection, wabtFailure maps output-limit to AnalysisResourceConstraintError, as apktool and native macOS do. The message gives the budget in bytes, and reported_limits carries max_output_bytes.
  • The remediation says the budget is fixed, so retrying with the same input fails the same way. The default text for a memory constraint says to retry with more memory, and no memory setting changes this budget. The text does not say the module is too large, because any of the three tools can fill the budget, including wasm-validate printing diagnostics for a malformed module.
  • For an output-limit failure, captured_output keeps up to the first 4,096 bytes that stdout and stderr each retained. The two streams share one budget, so either can be the one that filled it, and a stream that starts after the other has filled it retains nothing. The cut uses the existing boundedUtf8Text helper, so it never splits a character. truncated is always true here, because the command raises output-limit only after it truncated what it retained. It also records stdout_bytes, stderr_bytes, exit_code and signal, as apktool does. The byte counts are retained bytes, the meaning apktool and the firmware command give them.
  • If cleanup also fails, the error is still cleanup_incomplete. Its diagnostics now record the output limit and the budget as previous_error, and carry the same bounded excerpt.
  • capturedEvmOutput and capturedPwntoolsOutput already build captured output from a snapshot. They copy the full text and have no excerpt step, and importing them would make WABT depend on EVM or pwntools code, so I did not reuse them.
  • An oversized --version probe in the configuration phase keeps its current mapping. A misconfigured tool should not be reported as an oversized module.
  • wasm-validate, wasm-objdump and wasm2wat all run through the same execute helper, so all three reach this mapping. REA does not run wasm-decompile or any other WABT tool.
  • docs/wasm-artifacts.md said "truncation fails". It now gives the error code and the excerpt size.

Not changed, and yours to decide:

  • The resource is memory, as in apktool and native macOS for the same output budget. No sibling uses a closer value, so I kept it for consistency.
  • Timeout, process failures and the provider's fallback in WabtArtifactProvider.ts (around line 385) still attach full captured output. Only the output-limit path is bounded here.
  • The error's cause still holds the full snapshot in memory. It is not serialized.
  • EVMole, pwntools and browser capture still map output-limit to AnalysisOutputError, so they show the same rea doctor advice for a size limit. Apktool keeps its capture unbounded. One excerpt step at the owning boundary in src/process/ could cover all of them. That touches more providers, so I left it out of this PR.
  • stdout_bytes and stderr_bytes keep their shared meaning of retained bytes. The snapshot also knows how many bytes each stream produced before it stopped. That could go into the message or a new field if you want it.
  • When output passes the budget, execute() throws before it re-checks the executable digest and the module snapshot. So a swapped binary that floods output is reported as over budget, and its changed digest goes unnoticed. That control flow predates this change.
  • An oversized --version probe still maps to unreadable_output. capability_unavailable may fit a wrong tool better, as it does for a wrong banner.
  • A module this large still gets no result. Returning sections, imports and exports without inline WAT would be a separate feature.
  • PR fix(wasm): stop appending WABT remediation to unrelated runtime-root failures #2084 also edits src/wasm/WabtFailures.ts and its test file. The two branches merge without conflicts.

Contract and boundary impact

  • Semantic owner and earliest changed stage: WABT failure mapping in wabtFailure (src/wasm/WabtFailures.ts).
  • CLI and MCP/tool-catalog contract: when WABT output passes the budget during inspection, inspect_wasm_artifact returns the existing resource_constraint code instead of unreadable_output. No schemas change.
  • Provider, bridge, target-format, or platform compatibility: none. Modules under the budget are unchanged.
  • Evidence, artifact, provenance, or reconstruction contract: for an output-limit failure, captured_output keeps up to 4,096 bytes of what each stream retained, the retained byte counts, exit_code, signal and truncated.
  • Process execution, authorization, cleanup, or containment impact: none. The budget and the process handling are unchanged.
  • Generated metadata, package, or installation impact: none.

Evidence and regression coverage

  • Tests added in src/wasm/WabtArtifactProvider.test.ts:
    • Through inspect, the real child processes run for the version probes and wasm-objdump, and the launcher raises an output-limit failure for wasm2wat. The test checks the error tag, resource, max_output_bytes, the projected resource_constraint code, that the message names the budget, the remediation, and the full captured_output with the excerpt, byte counts, exit code and signal. No test writes 32 MiB.
    • An oversized stderr is cut to the same excerpt and marks the output truncated.
    • An emoji that straddles the 4,096-byte cut is left out whole.
    • With a cleanup failure on top of the output limit, the diagnostics carry the excerpt and record the output limit as previous_error.
    • An output limit during the --version probe stays AnalysisOutputError.
  • Base reproduction: on main the first four tests fail. The fifth passes there, since it guards the configuration-phase behaviour this change keeps. Each part of the fix is needed. Keeping the full text fails the four excerpt tests. Cutting by UTF-16 units fails the emoji test. Setting truncated to false, reporting observed instead of retained byte counts, or dropping exit_code and signal each fails the inspect and stderr tests. Recording the generic command message as previous_error fails the cleanup test. Dropping the phase condition fails the --version test. Changing the resource to cpu, dropping max_output_bytes, dropping the byte count from the message, or using the old module-size remediation each fails the inspect test.
  • User-visible CLI output, with the fix, on tree-sitter-cpp.wasm (stdout excerpt shortened here):
{"code":"resource_constraint","category":"resource_constraint","message":"WABT output exceeded the 33554432-byte output budget","retryable":false,"remediation":{"action":"The WABT output budget is fixed, so retrying with the same input will fail the same way."},"details":{"operation":"inspect_wasm_artifact","resource":"memory","reason":"WABT output exceeded the 33554432-byte output budget","reported_limits":{"max_output_bytes":33554432},"captured_output":{"stdout":"(module\n  (type (;0;) (func (param i32))...","stderr":"","truncated":true,"stdout_bytes":33554432,"stderr_bytes":0,"exit_code":0,"signal":null}}}

For evidence-bearing changes:

  • Observed, derived, and inferred claims remain distinguishable.
  • Artifact identity, source provenance, and failed attempts remain preserved.
  • Unsupported, incomplete, unavailable, or uncertain outcomes remain visible.

Validation performed

  • vitest over src/wasm, tests/boundary/mcp/wasmArtifactMcp.test.ts, tests/acceptance/tools/localToolContracts.test.ts, src/contracts/toolEffects.test.ts and tests/boundary/filesystem/targetDiagnostics.test.ts: 6 files, 56 tests passed.
  • npm run check:fast: passed.
  • npm run docs:check: passed.
  • npm run verify:test-discovery: "Discovered all 915 test files exactly once."
  • Real modules, built CLI, WABT 1.0.42, before and after. After the fix each oversized module reports stdout_bytes 33,554,432. exit_code and signal depend on whether WABT finished before it was stopped: the same module gave exit 0 on one run and SIGKILL on another.
Module Before After
tree-sitter-cpp.wasm unreadable_output, 33,606,368-byte error resource_constraint, 4,932-byte error
tree-sitter-bash.wasm unreadable_output, 33,584,062-byte error resource_constraint, 4,931-byte error
tree-sitter-powershell.wasm unreadable_output, 33,568,405-byte error resource_constraint, 4,926-byte error
tree-sitter-c-sharp.wasm, onig.wasm, crc32.wasm ok ok, same output size
vsda_bg.wasm (not WebAssembly) invalid_request invalid_request

Costs, measured 2026-10-13 with the built CLI on this branch and on main at 4276aaf, WABT 1.0.42, Linux x64, 12 cores. Each run had its own cgroup, so CPU and peak memory cover the whole process family, WABT children included. Per module and tree: one first run and three warm runs, with main and the branch alternating. The machine was shared, with a load average of 8 to 14 during the runs, so differences of a few hundred milliseconds are noise. "First" is not truly cold: the page cache was already warm from the build, and I could not drop it.

Module Tree First run: elapsed / CPU / peak Warm median: elapsed / CPU / peak Output
tree-sitter-cpp.wasm main 4.2 s / 5.5 s / 535 MiB 3.2 s / 5.5 s / 500 MiB 33,606,368 B
branch 3.2 s / 5.4 s / 485 MiB 3.1 s / 5.1 s / 513 MiB 4,924 B
tree-sitter-bash.wasm main 3.0 s / 4.9 s / 470 MiB 3.4 s / 5.1 s / 508 MiB 33,584,062 B
branch 2.8 s / 4.5 s / 517 MiB 3.6 s / 5.0 s / 502 MiB 4,931 B
tree-sitter-powershell.wasm main 3.4 s / 5.1 s / 572 MiB 3.2 s / 4.9 s / 598 MiB 33,568,405 B
branch 3.3 s / 4.9 s / 522 MiB 3.0 s / 4.7 s / 535 MiB 4,926 B
tree-sitter-c-sharp.wasm (control, succeeds) main 4.8 s / 7.5 s / 631 MiB 5.4 s / 7.7 s / 588 MiB 64,132,114 B
branch 4.8 s / 7.5 s / 588 MiB 5.2 s / 7.8 s / 587 MiB 64,132,114 B

Elapsed time, CPU time and peak memory show no change outside that noise. The process still reads the full 32 MiB before stopping, and cause still holds the snapshot, so I did not expect one. The change is in what the caller receives: about 4.9 KB instead of about 33.6 MB per failure. Token usage was not measured. I had no tokenizer for the agent's model on this machine, and fewer bytes alone do not prove fewer tokens.

Compatibility, safety, and release

  • Breaking changes or migration steps: a caller that matched unreadable_output for oversized modules now gets resource_constraint. Neither code is retryable.
  • Real Hopper/Ghidra, browser, or OS coverage: Linux x64 (Fedora 44) only, with the WABT 1.0.42 release binaries. No other provider runs.
  • Package or release metadata impact: none.
  • Security, privacy, process, or containment review: the output budget and process handling are unchanged. The error carries at most 8,192 bytes of producer output, down from up to 32 MiB.

Review checklist

  • This PR addresses a concrete problem or an agreed enhancement.
  • The PR has one focused outcome and the title follows type(scope): outcome.
  • Related issue is linked, or the reason for not linking one is stated above.
  • Tests cover changed observable behavior and meaningful failure paths.
  • Owning docs, contracts, and generated metadata are updated where needed.
  • User-visible CLI/MCP changes include representative output.
  • I checked the final diff for secrets, unrelated cleanup, and unsupported claims.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-13T18:44:38.818883Z 15a783f New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@JayOfTheKeyboard

Copy link
Copy Markdown
Contributor Author

The red checks come from main, not this change. tests/boundary/mcp/promptCompletion.test.ts ("reads live documents and complete procedures without ambiguous names") and the packaged prompt-completion smoke step in the darwin package baselines fail the same way in main's CI at c22111ec. This PR only touches WABT failure mapping. Same note on #2116.

@morluto

morluto commented Oct 13, 2026

Copy link
Copy Markdown
Owner

@greptileai review

@greptile-apps

greptile-apps Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

[Medium impact] This PR appears safe to merge; no blocking issue was found.

Summary

Inspection commands that exceed WABT's output budget now return resource_constraint, with bounded diagnostic excerpts instead of the full retained output.

  • Oversized WABT output now returns a bounded resource error.

Reviews (7) · Last reviewed commit: "Merge branch 'main' into fix/wasm-output..." · Reviewed by Greptile

Comment thread docs/wasm-artifacts.md Resolved
A module whose WAT exceeds the 32 MiB output budget failed as
unreadable_output, advised `rea doctor`, and echoed the full 32 MiB
truncated WAT prefix in details.captured_output. Map the owned-command
output-limit outcome during inspection to AnalysisResourceConstraintError
naming the budget, as the apktool and native macOS providers do, with a
remediation that says the fixed budget will not change on retry.

Captured output for an output-limit failure keeps only the first 4,096
characters of each stream, including on the cleanup-failure path. An
oversized --version probe keeps its configuration-phase mapping.
…excerpts

Cut each stream's output-limit excerpt with boundedUtf8Text at 4,096
UTF-8 bytes, so an astral character at the cut no longer leaves a lone
surrogate. Mark the output truncated when either excerpt was cut, and
report each stream's observed byte count.

Word the remediation as a fact about the fixed budget. A flood of
diagnostics from any WABT tool reaches this path, so it no longer says
the module is too large.

Replace the 32 MiB end-to-end test with a launcher seam that raises a
small output-limit failure.
Report retained byte counts in stdout_bytes and stderr_bytes, as apktool
and the firmware command do, and record exit_code and signal. Mark the
excerpt truncated outright, since the command raises output-limit only
after retention was truncated.

When cleanup also fails, record the output limit and its budget as the
previous error instead of the generic command message. Document that
the two streams share the budget, so the excerpt holds up to 4,096 of
the bytes each stream retained.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 463a9176de

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/wasm/WabtFailures.ts Resolved

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 913cd9c7a1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread docs/wasm-artifacts.md Resolved
@greptile-apps

greptile-apps Bot commented Oct 13, 2026

Copy link
Copy Markdown
Contributor

Want your agent to iterate on Greptile's feedback? Try greploops.

@morluto

morluto commented Oct 13, 2026

Copy link
Copy Markdown
Owner

Review findings addressed at their producing boundaries and consumers. Latest changes were reviewed from source; tests and provider runs were skipped at the maintainer's request. Findings requiring separate decisions or verification remain open.

  1. thread 1

The guide now distinguishes the contributor-reported warm Linux x64 WABT 1.0.42 CLI elapsed/CPU/process-family-memory measurements from the unmeasured token usage and truly cold workflows. It links the PR evidence and explicitly retains the full underlying cause snapshot, so excerpting establishes no memory or speed improvement.

Current source: c5beb60. Validation: source inspection and the existing PR evidence only; tests/checks were skipped at the user's explicit request.

  1. thread 1

The note now attributes the existing warm Linux x64 WABT 1.0.42 elapsed/CPU/process-family-memory measurements to PR #2117, and limits the unmeasured gaps to agent tokens and truly cold runs. It preserves the full-snapshot caveat and makes no unsupported memory or speed improvement claim.

Current source: c5beb60. Validation: source inspection only; tests and checks were skipped at the user's explicit request.

@JayOfTheKeyboard

Copy link
Copy Markdown
Contributor Author

Thanks for tightening the guide. I ran the checks you skipped against c5beb60: the src/wasm tests pass (30), check:fast passes (typecheck and lint), and docs:check passes (6 tasks, readme consistency 2/2). The guide's wording matches the measurements in the body.

@morluto morluto added bug Something isn't working performance Performance regressions, scaling limits, and bounded-resource work area: native Native analysis and decompilation, including Ghidra, IDA, and Hopper analysis behavior area: providers Provider and capability discovery, including provider proposal triage P2: medium Medium priority issue labels Oct 13, 2026
@morluto
morluto merged commit 42667fc into morluto:main Oct 13, 2026
6 checks passed
@JayOfTheKeyboard

Copy link
Copy Markdown
Contributor Author

Thanks for merging, and for tightening the guide's wording on the measurements.

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

Labels

area: native Native analysis and decompilation, including Ghidra, IDA, and Hopper analysis behavior area: providers Provider and capability discovery, including provider proposal triage bug Something isn't working P2: medium Medium priority issue performance Performance regressions, scaling limits, and bounded-resource work

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants