[GH-ISSUE #5460] [Bug] IL2CPP report-budget refusals recommend worker and host memory changes #3319

Closed
opened 2026-10-31 00:39:08 +00:00 by bsakaguchi · 1 comment
Owner

Originally created by @coygeek on GitHub (Oct 20, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/5460

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

CLI

Observed behavior

node scripts/rea.mjs inspect-il2cpp-metadata <artifact> --json correctly refuses a complete inventory above its fixed 64 MiB encoded-report allowance, but returns details.resource: "memory" and recommends reviewing worker memory limits or adding host memory. Those changes cannot raise the fixed report allowance. The message and maximum_report_bytes correctly identify the report budget; the defect is the contradictory resource classification and recovery advice.

Reproduction

From the baseline source checkout with dependencies and the built CLI prepared, run the following. It generates version 29 metadata with one shared 65,536-byte ASCII method name, first with one method as a successful control, then with 1,100 methods to exceed the encoded-report allowance. The independent verifier observed each case twice, including replay of this exact script.

python3 - <<'PYTHON'
import json, os, pathlib, struct, subprocess, tempfile
work = pathlib.Path(tempfile.mkdtemp(prefix="rea-report-check-"))
env = {k: v for k, v in os.environ.items() if not k.startswith("REA_")}
for key, name in [("HOME", "home"), ("XDG_CONFIG_HOME", "config"),
                  ("XDG_DATA_HOME", "data"), ("XDG_CACHE_HOME", "cache"),
                  ("XDG_STATE_HOME", "state")]:
    folder = work / name
    folder.mkdir()
    env[key] = str(folder)
for count in [1, 1100]:
    name = b"A" * 65536 + b"\0"
    records = bytearray(count * 32)
    for index in range(count):
        struct.pack_into("<i", records, index * 32 + 4, -1)
    header = bytearray(256)
    struct.pack_into("<Ii", header, 0, 0xfab11baf, 29)
    cursor = 256
    for section in range(31):
        content = name if section == 2 else records if section == 5 else b""
        struct.pack_into("<ii", header, 8 + section * 8, cursor, len(content))
        cursor += len(content)
    artifact = work / (str(count) + ".dat")
    artifact.write_bytes(header + name + records)
    result = subprocess.run(
        ["node", "scripts/rea.mjs", "inspect-il2cpp-metadata", str(artifact), "--json"],
        env=env, capture_output=True, text=True, timeout=90)
    (work / (str(count) + ".json")).write_text(result.stdout)
    (work / (str(count) + ".stderr")).write_text(result.stderr)
    value = json.loads(result.stdout)
    if result.returncode == 0:
        report = value["normalized_result"]
        print(json.dumps({"methods": count, "exit": result.returncode,
                          "input_bytes": artifact.stat().st_size,
                          "metadata_version": report["metadata_version"],
                          "returned_methods": len(report["methods"])}))
    else:
        details = value["details"]
        print(json.dumps({"methods": count, "exit": result.returncode,
                          "input_bytes": artifact.stat().st_size,
                          "code": value["code"], "retryable": value["retryable"],
                          "resource": details["resource"],
                          "limits": details["reported_limits"],
                          "action": value["remediation"]["action"],
                          "partial": details["partial_observation"]["normalized_result"]}))
PYTHON

Expected behavior

Keep refusing complete inventories above the fixed report allowance, but identify that allowance and provide recovery guidance consistent with it. Preserve the bound and partial source facts without implying that worker or host memory changes admit the unchanged report.

docs/il2cpp-metadata.md:179-182 documents separate 256 MiB selected-input and 64 MiB encoded complete-report guards, including JSON/UTF-8 expansion checks. Successful analysis beyond the report allowance is not promised. The expectation of report-specific recovery advice follows from this source contract.

Impact and scope

P3 diagnostic/recovery wording defect. Both version 29 cases gave consistent results on two verifier runs. The correct report budget remains visible; no complete inventory is falsely returned and partial source facts survive. The documented refusal itself is intentional. No data loss or consequential behavior change caused by the advice was established.

Callers can identify the fixed allowance from the message and maximum_report_bytes, ignore the inapplicable memory advice, and select an input whose complete report fits. Above-budget inventories remain unsupported. MCP was not exercised.

REA version

6.7.0; source commit 074768901f3cdc8205b61773fb1faf6f6c7b3ca8.

Runtime and platform

Node v26.12.0; Darwin arm64. This is in-process static analysis; no Unity runtime, Hopper, Ghidra, or worker is required.

Target and artifact context

Synthetic version 29 IL2CPP metadata following the layout in tests/boundary/il2cpp/il2cppMetadataProvider.test.ts:18-48. The one-method control is 65,825 bytes; the 1,100-method case is 100,993 bytes. Both pass the 256 MiB input admission guard. Repeated names expand in the complete encoded report.

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

The control exited 0 and returned metadata version 29 with one method and its full 65,536-character name. The 1,100-method case exited 1 with empty stderr and these observed fields:

{
  "code": "resource_constraint",
  "category": "resource_constraint",
  "retryable": false,
  "remediation": {
    "action": "Review the reported worker memory limits and available host memory. Retry with sufficient memory or a smaller artifact; REA retains tighter inherited limits."
  },
  "details": {
    "resource": "memory",
    "reported_limits": { "maximum_report_bytes": 67108864 }
  }
}

The message correctly says the complete metadata exceeds the retained report budget and no complete inventory is retained. Partial evidence includes inventory_status: "not-returned-as-complete" and counts of 0 images, 0 types and 1,100 methods. No worker-memory allowance is reported.

The verifier also observed the same diagnostic through the early row-count guard using a generated version 39 input with 900,000 filler types (64,801,032 input bytes). Retained counts were 2 images, 900,004 types and 6 methods. That guard checks header/layout counts before decoding row names or relationships, so this does not establish a fully validated inventory. A default version 39 control succeeded. These are additional observations; the smaller version 29 script above is the reduced reproduction.

Typed outcome or recovery expected

Retain the non-retryable report-budget refusal, numeric allowance, and available partial source facts, with resource semantics and recovery advice appropriate to the fixed encoded-report allowance. Genuine worker-memory failures should retain memory-appropriate advice. Replaying both report guards and the successful control after a correction is a proposed restoration check; no fix was tested here.

Analysis or suspected cause

Source-derived at the tested commit: src/composition/il2cpp.ts:6-7 uses the in-process provider's default allowance. src/il2cpp/Il2cppMetadataProvider.ts:165-171 constructs the report-budget failure as a memory constraint without specific remediation. Its row-count guard at lines 303-308 and encoded-report-size guard at lines 327-328 use that producer. src/domain/analysisErrorPresentation.ts:67-78 therefore selects generic worker/host-memory advice. The version 29 reproduction exercises the encoded-report-size guard.

Additional context

Prepared with AI assistance and independently reproduced through the supported built CLI. The reproduction isolated HOME and XDG state and made no external API/network calls. Submission checks fetched upstream main at 052956af3416fa01d93837e54fdec3ad2739f6d1; no commits after the baseline changed the cited provider, composition, CLI registration, error presentation, or documentation files. Applicability to current main is source-inspected, not a new runtime replay.

Related but separate: #5405 covers the earlier input-size admission guard (maximum_input_bytes, resource file-size); these inputs pass admission and fail the distinct report-budget producer (maximum_report_bytes, resource memory). #5403 covers missing-version/magic classification for truncated inputs. Neither reports this behavior.

NOT_ENOUGH_INFO: no last-known-good version or regression boundary was established. Installed-package and MCP behavior were not replayed.

Originally created by @coygeek on GitHub (Oct 20, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/5460 ### Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area CLI ### Observed behavior `node scripts/rea.mjs inspect-il2cpp-metadata <artifact> --json` correctly refuses a complete inventory above its fixed 64 MiB encoded-report allowance, but returns `details.resource: "memory"` and recommends reviewing worker memory limits or adding host memory. Those changes cannot raise the fixed report allowance. The message and `maximum_report_bytes` correctly identify the report budget; the defect is the contradictory resource classification and recovery advice. ### Reproduction From the baseline source checkout with dependencies and the built CLI prepared, run the following. It generates version 29 metadata with one shared 65,536-byte ASCII method name, first with one method as a successful control, then with 1,100 methods to exceed the encoded-report allowance. The independent verifier observed each case twice, including replay of this exact script. ```sh python3 - <<'PYTHON' import json, os, pathlib, struct, subprocess, tempfile work = pathlib.Path(tempfile.mkdtemp(prefix="rea-report-check-")) env = {k: v for k, v in os.environ.items() if not k.startswith("REA_")} for key, name in [("HOME", "home"), ("XDG_CONFIG_HOME", "config"), ("XDG_DATA_HOME", "data"), ("XDG_CACHE_HOME", "cache"), ("XDG_STATE_HOME", "state")]: folder = work / name folder.mkdir() env[key] = str(folder) for count in [1, 1100]: name = b"A" * 65536 + b"\0" records = bytearray(count * 32) for index in range(count): struct.pack_into("<i", records, index * 32 + 4, -1) header = bytearray(256) struct.pack_into("<Ii", header, 0, 0xfab11baf, 29) cursor = 256 for section in range(31): content = name if section == 2 else records if section == 5 else b"" struct.pack_into("<ii", header, 8 + section * 8, cursor, len(content)) cursor += len(content) artifact = work / (str(count) + ".dat") artifact.write_bytes(header + name + records) result = subprocess.run( ["node", "scripts/rea.mjs", "inspect-il2cpp-metadata", str(artifact), "--json"], env=env, capture_output=True, text=True, timeout=90) (work / (str(count) + ".json")).write_text(result.stdout) (work / (str(count) + ".stderr")).write_text(result.stderr) value = json.loads(result.stdout) if result.returncode == 0: report = value["normalized_result"] print(json.dumps({"methods": count, "exit": result.returncode, "input_bytes": artifact.stat().st_size, "metadata_version": report["metadata_version"], "returned_methods": len(report["methods"])})) else: details = value["details"] print(json.dumps({"methods": count, "exit": result.returncode, "input_bytes": artifact.stat().st_size, "code": value["code"], "retryable": value["retryable"], "resource": details["resource"], "limits": details["reported_limits"], "action": value["remediation"]["action"], "partial": details["partial_observation"]["normalized_result"]})) PYTHON ``` ### Expected behavior Keep refusing complete inventories above the fixed report allowance, but identify that allowance and provide recovery guidance consistent with it. Preserve the bound and partial source facts without implying that worker or host memory changes admit the unchanged report. `docs/il2cpp-metadata.md:179-182` documents separate 256 MiB selected-input and 64 MiB encoded complete-report guards, including JSON/UTF-8 expansion checks. Successful analysis beyond the report allowance is not promised. The expectation of report-specific recovery advice follows from this source contract. ### Impact and scope P3 diagnostic/recovery wording defect. Both version 29 cases gave consistent results on two verifier runs. The correct report budget remains visible; no complete inventory is falsely returned and partial source facts survive. The documented refusal itself is intentional. No data loss or consequential behavior change caused by the advice was established. Callers can identify the fixed allowance from the message and `maximum_report_bytes`, ignore the inapplicable memory advice, and select an input whose complete report fits. Above-budget inventories remain unsupported. MCP was not exercised. ### REA version 6.7.0; source commit `074768901f3cdc8205b61773fb1faf6f6c7b3ca8`. ### Runtime and platform Node v26.12.0; Darwin arm64. This is in-process static analysis; no Unity runtime, Hopper, Ghidra, or worker is required. ### Target and artifact context Synthetic version 29 IL2CPP metadata following the layout in `tests/boundary/il2cpp/il2cppMetadataProvider.test.ts:18-48`. The one-method control is 65,825 bytes; the 1,100-method case is 100,993 bytes. Both pass the 256 MiB input admission guard. Repeated names expand in the complete encoded report. ### Interface CLI ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence The control exited 0 and returned metadata version 29 with one method and its full 65,536-character name. The 1,100-method case exited 1 with empty stderr and these observed fields: ```json { "code": "resource_constraint", "category": "resource_constraint", "retryable": false, "remediation": { "action": "Review the reported worker memory limits and available host memory. Retry with sufficient memory or a smaller artifact; REA retains tighter inherited limits." }, "details": { "resource": "memory", "reported_limits": { "maximum_report_bytes": 67108864 } } } ``` The message correctly says the complete metadata exceeds the retained report budget and no complete inventory is retained. Partial evidence includes `inventory_status: "not-returned-as-complete"` and counts of 0 images, 0 types and 1,100 methods. No worker-memory allowance is reported. The verifier also observed the same diagnostic through the early row-count guard using a generated version 39 input with 900,000 filler types (64,801,032 input bytes). Retained counts were 2 images, 900,004 types and 6 methods. That guard checks header/layout counts before decoding row names or relationships, so this does not establish a fully validated inventory. A default version 39 control succeeded. These are additional observations; the smaller version 29 script above is the reduced reproduction. ### Typed outcome or recovery expected Retain the non-retryable report-budget refusal, numeric allowance, and available partial source facts, with resource semantics and recovery advice appropriate to the fixed encoded-report allowance. Genuine worker-memory failures should retain memory-appropriate advice. Replaying both report guards and the successful control after a correction is a proposed restoration check; no fix was tested here. ### Analysis or suspected cause Source-derived at the tested commit: `src/composition/il2cpp.ts:6-7` uses the in-process provider's default allowance. `src/il2cpp/Il2cppMetadataProvider.ts:165-171` constructs the report-budget failure as a memory constraint without specific remediation. Its row-count guard at lines 303-308 and encoded-report-size guard at lines 327-328 use that producer. `src/domain/analysisErrorPresentation.ts:67-78` therefore selects generic worker/host-memory advice. The version 29 reproduction exercises the encoded-report-size guard. ### Additional context Prepared with AI assistance and independently reproduced through the supported built CLI. The reproduction isolated HOME and XDG state and made no external API/network calls. Submission checks fetched upstream main at `052956af3416fa01d93837e54fdec3ad2739f6d1`; no commits after the baseline changed the cited provider, composition, CLI registration, error presentation, or documentation files. Applicability to current main is source-inspected, not a new runtime replay. Related but separate: #5405 covers the earlier input-size admission guard (`maximum_input_bytes`, resource `file-size`); these inputs pass admission and fail the distinct report-budget producer (`maximum_report_bytes`, resource `memory`). #5403 covers missing-version/magic classification for truncated inputs. Neither reports this behavior. NOT_ENOUGH_INFO: no last-known-good version or regression boundary was established. Installed-package and MCP behavior were not replayed.
Author
Owner

@morluto commented on GitHub (Oct 21, 2026):

Closing as fixed on main by github.com/morluto/rea@ef59174c1e, retained at 67a6477f01.

IL2CPP refusal recovery now identifies the input, fixed parser/count and complete-report allowance separately. Complete-report advice preserves already-observed source facts without claiming a complete inventory exists. Provider regressions check report guidance and partial facts; no new near-limit release-path replay was performed.

This audit inspected the current producer, regression source, issue discussion and main ancestry. Tests/runtime reproductions were not rerun, and no additional real-provider or platform coverage is implied.

<!-- gh-comment-id:6324334432 --> @morluto commented on GitHub (Oct 21, 2026): Closing as fixed on main by https://github.com/morluto/rea/commit/ef59174c1edd6ed9adb0722b86a5b9a57217e310, retained at 67a6477f011ae358fc333e5a6e26c86d932654ed. IL2CPP refusal recovery now identifies the input, fixed parser/count and complete-report allowance separately. Complete-report advice preserves already-observed source facts without claiming a complete inventory exists. Provider regressions check report guidance and partial facts; no new near-limit release-path replay was performed. This audit inspected the current producer, regression source, issue discussion and main ancestry. Tests/runtime reproductions were not rerun, and no additional real-provider or platform coverage is implied.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#3319
No description provided.