[GH-ISSUE #5847] [Bug] inspect_plist recommends host-memory changes for its fixed binary-plist decode allowance #3544

Closed
opened 2026-10-31 00:41:42 +00:00 by bsakaguchi · 1 comment
Owner

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

Originally assigned to: @kaoru0822-kitauji on GitHub.

Before submitting

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

Area

Native analysis or decompilation

Observed behavior

The public CLI rejected a valid, self-authored 23,068,738-byte binary plist with resource_constraint, details.resource: "memory", retryable: false, and maximum_decoded_bytes: 67108864. Its recovery action nevertheless said to review worker memory limits and available host memory and retry with sufficient memory. This refusal comes from a fixed decode estimate; adding host memory does not change that allowance.

Two consecutive executions in the final replay exited 1 with identical stdout and empty stderr. A 70-byte binary plist control exited 0 and returned the exact data leaf. These were real CLI runs with the macOS native provider, not mocked provider results.

Reproduction

Use the prepared source checkout with locked dependencies and its built CLI. Set REPRO_ROOT to an existing disposable directory outside the checkout. From the checkout root, run the following executed Python driver using python3 -I. It generates valid binary fixtures with standard-library plistlib, isolates home/configuration/state directories, and invokes node scripts/rea.mjs inspect-plist <generated-plist> --json. Its temporary workspace is removed on exit.

import base64, json, os, pathlib, plistlib, subprocess, tempfile

with tempfile.TemporaryDirectory(dir=os.environ['REPRO_ROOT']) as directory:
    root = pathlib.Path(directory)
    env = {k: v for k, v in os.environ.items()
           if not k.startswith(('REA_', 'HOPPER_', 'GHIDRA_')) and k != 'NODE_OPTIONS'}
    for key in ('HOME', 'XDG_CONFIG_HOME', 'XDG_DATA_HOME',
                'XDG_CACHE_HOME', 'XDG_STATE_HOME', 'TMPDIR'):
        selected = root / key.lower()
        selected.mkdir()
        env[key] = str(selected)
    for name, size in [('large', 22 * 1024 * 1024), ('small', 16)]:
        value = {'data': b'x' * size}
        blob = plistlib.dumps(value, fmt=plistlib.FMT_BINARY)
        assert plistlib.loads(blob) == value
        path = root / (name + '.plist')
        path.write_bytes(blob)
        runs = [subprocess.run(
            ['node', 'scripts/rea.mjs', 'inspect-plist', str(path), '--json'],
            env=env, capture_output=True, text=True, check=False
        ) for _ in range(2 if name == 'large' else 1)]
        print(f'{name}: input_bytes={len(blob)}, exits={[r.returncode for r in runs]}, '
              f'stderr_bytes={[len(r.stderr) for r in runs]}')
        result = json.loads(runs[0].stdout)
        if name == 'large':
            assert all(r.returncode == 1 and not r.stderr for r in runs)
            assert runs[0].stdout == runs[1].stdout
            assert result['code'] == 'resource_constraint'
            assert result['retryable'] is False
            assert result['details']['reported_limits']['maximum_decoded_bytes'] == 67108864
            print('repeated_stdout_identical=True')
            print(runs[0].stdout.strip())
        else:
            assert runs[0].returncode == 0 and not runs[0].stderr
            normalized = result['normalized_result']
            assert normalized['format'] == 'binary'
            leaf = normalized['value']['data']
            assert leaf['$plist_type'] == 'data'
            assert base64.b64decode(leaf['base64']) == value['data']
            print('format=binary; decoded_data_matches=True')

The large fixture contains one dictionary entry whose value is 22 MiB of data bytes. The plistlib.loads assertions independently check that the generated binary encoding represents the intended values before REA receives it. Both large executions reproduce the same recovery wording; the smaller control verifies successful binary decoding on the same runtime.

Expected behavior

Keep the fixed safety allowance, the intentional refusal, resource_constraint, the reported numeric limit, and retryable: false. The action should identify the native binary-plist decode allowance and explain that changing worker limits or available host memory cannot raise it. Repeating the unchanged request should be described as failing again. Selecting a different complete input within the allowance is appropriate only when it preserves the needed observation.

The native inspection documentation describes separate 64 MiB input and estimated decode budgets, and explicitly distinguishes the estimate from a hard process-memory bound. The shared fixed-allowance recovery policy supplies the applicable parser wording. The resource-constraint schema requires a remediation action; it does not itself prescribe the sentence.

This report does not require every input below the input ceiling to pass the separate decode budget, or ask to increase either allowance.

Impact and scope

P3: misleading recovery wording. The refusal, resource category, non-retryable flag, reason and numeric allowance are correct. No consequential behavior caused by the advice, automated retry loop, or incorrect successful analysis was observed. Callers can recognize the fixed decode budget from the message and reported limit and disregard the host-memory instruction.

The executed scope is the native macOS CLI with the synthetic binary fixtures above. The smaller control does not establish a workaround preserving complete inspection of the original large plist. Real-world frequency, other hosts and live MCP behavior were not tested. Shared CLI/MCP error presentation is source-derived; the report makes no live MCP reproduction claim.

REA version

rea-agents 6.7.0; prepared built source CLI at a5f982de8d.

Runtime and platform

Node.js v26.12.0; macOS 27.0.1 arm64; Python 3 standard-library plistlib; native macOS file classification; no external analysis engine.

Target and artifact context

Self-authored binary plists containing a dictionary with one data leaf: 22 MiB of repeated x bytes for the refusal, 16 bytes for the successful control; encoded file sizes 23,068,738 and 70 bytes.

Interface

CLI

Earliest failure stage

Transport or result projection

Relevant output or evidence

Actual trimmed output from the executed driver:

large: input_bytes=23068738, exits=[1, 1], stderr_bytes=[0, 0]
repeated_stdout_identical=True
{"error":"Analysis failed","code":"resource_constraint","category":"resource_constraint","message":"binary plist object table exceeds the native plist decode budget","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":{"operation":"inspect_plist","resource":"memory","reason":"binary plist object table exceeds the native plist decode budget","reported_limits":{"maximum_decoded_bytes":67108864}}}
small: input_bytes=70, exits=[0], stderr_bytes=[0]
format=binary; decoded_data_matches=True

The binary native-provider branch reads original source bytes and returns the parser's resource error unchanged. The decode estimator reserves three times the source length plus per-object metadata for number projection before walking objects. The reproduced fixture exceeds that fixed estimate while remaining below the separate source-size ceiling. This is source-derived explanation of the observed refusal, not a measurement of peak process memory.

Typed outcome or recovery expected

Preserve code: "resource_constraint", category: "resource_constraint", retryable: false, and details.reported_limits.maximum_decoded_bytes: 67108864. Supply allowance-specific remediation.action without implying that worker or host memory adjustments can admit this unchanged input. No change to the safety guard is needed to correct this wording.

Analysis or suspected cause

Source-derived: the parser's budget-failure mapping constructs AnalysisResourceConstraintError without remediationAction. The shared presentation fallback then selects generic worker/host-memory advice. Applying the existing fixed-allowance recovery helper at this producer is a proposed correction; no fix or restored behavior was tested.

Additional context

Related issue 5556 and merged PR 5633 cover other fixed-allowance producers, not this binary-plist decoder. Tracker 5808 proposes a broader remaining recovery audit without reporting this specific failure. Read-only open/closed issue and PR searches found no matching report or fix for this producer. Last known good version: NOT_ENOUGH_INFO; none established.

Originally created by @coygeek on GitHub (Oct 21, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/5847 Originally assigned to: @kaoru0822-kitauji on GitHub. ### 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 Native analysis or decompilation ### Observed behavior The public CLI rejected a valid, self-authored 23,068,738-byte binary plist with `resource_constraint`, `details.resource: "memory"`, `retryable: false`, and `maximum_decoded_bytes: 67108864`. Its recovery action nevertheless said to review worker memory limits and available host memory and retry with sufficient memory. This refusal comes from a fixed decode estimate; adding host memory does not change that allowance. Two consecutive executions in the final replay exited 1 with identical stdout and empty stderr. A 70-byte binary plist control exited 0 and returned the exact data leaf. These were real CLI runs with the macOS native provider, not mocked provider results. ### Reproduction Use the prepared source checkout with locked dependencies and its built CLI. Set `REPRO_ROOT` to an existing disposable directory outside the checkout. From the checkout root, run the following executed Python driver using `python3 -I`. It generates valid binary fixtures with standard-library `plistlib`, isolates home/configuration/state directories, and invokes `node scripts/rea.mjs inspect-plist <generated-plist> --json`. Its temporary workspace is removed on exit. ```python import base64, json, os, pathlib, plistlib, subprocess, tempfile with tempfile.TemporaryDirectory(dir=os.environ['REPRO_ROOT']) as directory: root = pathlib.Path(directory) env = {k: v for k, v in os.environ.items() if not k.startswith(('REA_', 'HOPPER_', 'GHIDRA_')) and k != 'NODE_OPTIONS'} for key in ('HOME', 'XDG_CONFIG_HOME', 'XDG_DATA_HOME', 'XDG_CACHE_HOME', 'XDG_STATE_HOME', 'TMPDIR'): selected = root / key.lower() selected.mkdir() env[key] = str(selected) for name, size in [('large', 22 * 1024 * 1024), ('small', 16)]: value = {'data': b'x' * size} blob = plistlib.dumps(value, fmt=plistlib.FMT_BINARY) assert plistlib.loads(blob) == value path = root / (name + '.plist') path.write_bytes(blob) runs = [subprocess.run( ['node', 'scripts/rea.mjs', 'inspect-plist', str(path), '--json'], env=env, capture_output=True, text=True, check=False ) for _ in range(2 if name == 'large' else 1)] print(f'{name}: input_bytes={len(blob)}, exits={[r.returncode for r in runs]}, ' f'stderr_bytes={[len(r.stderr) for r in runs]}') result = json.loads(runs[0].stdout) if name == 'large': assert all(r.returncode == 1 and not r.stderr for r in runs) assert runs[0].stdout == runs[1].stdout assert result['code'] == 'resource_constraint' assert result['retryable'] is False assert result['details']['reported_limits']['maximum_decoded_bytes'] == 67108864 print('repeated_stdout_identical=True') print(runs[0].stdout.strip()) else: assert runs[0].returncode == 0 and not runs[0].stderr normalized = result['normalized_result'] assert normalized['format'] == 'binary' leaf = normalized['value']['data'] assert leaf['$plist_type'] == 'data' assert base64.b64decode(leaf['base64']) == value['data'] print('format=binary; decoded_data_matches=True') ``` The large fixture contains one dictionary entry whose value is 22 MiB of data bytes. The `plistlib.loads` assertions independently check that the generated binary encoding represents the intended values before REA receives it. Both large executions reproduce the same recovery wording; the smaller control verifies successful binary decoding on the same runtime. ### Expected behavior Keep the fixed safety allowance, the intentional refusal, `resource_constraint`, the reported numeric limit, and `retryable: false`. The action should identify the native binary-plist decode allowance and explain that changing worker limits or available host memory cannot raise it. Repeating the unchanged request should be described as failing again. Selecting a different complete input within the allowance is appropriate only when it preserves the needed observation. The [native inspection documentation](https://github.com/morluto/rea/blob/a5f982de8dbe4b092cc20ab89d492b7e7028b758/docs/native-investigation.md#L301-L306) describes separate 64 MiB input and estimated decode budgets, and explicitly distinguishes the estimate from a hard process-memory bound. The [shared fixed-allowance recovery policy](https://github.com/morluto/rea/blob/a5f982de8dbe4b092cc20ab89d492b7e7028b758/src/domain/fixedAllowanceRemediation.ts#L17-L24) supplies the applicable parser wording. The [resource-constraint schema](https://github.com/morluto/rea/blob/a5f982de8dbe4b092cc20ab89d492b7e7028b758/docs/error-contract.schema.json#L509-L569) requires a remediation action; it does not itself prescribe the sentence. This report does not require every input below the input ceiling to pass the separate decode budget, or ask to increase either allowance. ### Impact and scope P3: misleading recovery wording. The refusal, resource category, non-retryable flag, reason and numeric allowance are correct. No consequential behavior caused by the advice, automated retry loop, or incorrect successful analysis was observed. Callers can recognize the fixed decode budget from the message and reported limit and disregard the host-memory instruction. The executed scope is the native macOS CLI with the synthetic binary fixtures above. The smaller control does not establish a workaround preserving complete inspection of the original large plist. Real-world frequency, other hosts and live MCP behavior were not tested. Shared CLI/MCP error presentation is source-derived; the report makes no live MCP reproduction claim. ### REA version rea-agents 6.7.0; prepared built source CLI at a5f982de8dbe4b092cc20ab89d492b7e7028b758. ### Runtime and platform Node.js v26.12.0; macOS 27.0.1 arm64; Python 3 standard-library plistlib; native macOS file classification; no external analysis engine. ### Target and artifact context Self-authored binary plists containing a dictionary with one data leaf: 22 MiB of repeated x bytes for the refusal, 16 bytes for the successful control; encoded file sizes 23,068,738 and 70 bytes. ### Interface CLI ### Earliest failure stage Transport or result projection ### Relevant output or evidence Actual trimmed output from the executed driver: ```text large: input_bytes=23068738, exits=[1, 1], stderr_bytes=[0, 0] repeated_stdout_identical=True {"error":"Analysis failed","code":"resource_constraint","category":"resource_constraint","message":"binary plist object table exceeds the native plist decode budget","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":{"operation":"inspect_plist","resource":"memory","reason":"binary plist object table exceeds the native plist decode budget","reported_limits":{"maximum_decoded_bytes":67108864}}} small: input_bytes=70, exits=[0], stderr_bytes=[0] format=binary; decoded_data_matches=True ``` The [binary native-provider branch](https://github.com/morluto/rea/blob/a5f982de8dbe4b092cc20ab89d492b7e7028b758/src/native/NativeMacOSProvider.ts#L930-L947) reads original source bytes and returns the parser's resource error unchanged. The [decode estimator](https://github.com/morluto/rea/blob/a5f982de8dbe4b092cc20ab89d492b7e7028b758/src/artifacts/apple/PropertyListDecodeBudget.ts#L269-L279) reserves three times the source length plus per-object metadata for number projection before walking objects. The reproduced fixture exceeds that fixed estimate while remaining below the separate source-size ceiling. This is source-derived explanation of the observed refusal, not a measurement of peak process memory. ### Typed outcome or recovery expected Preserve `code: "resource_constraint"`, `category: "resource_constraint"`, `retryable: false`, and `details.reported_limits.maximum_decoded_bytes: 67108864`. Supply allowance-specific `remediation.action` without implying that worker or host memory adjustments can admit this unchanged input. No change to the safety guard is needed to correct this wording. ### Analysis or suspected cause Source-derived: the [parser's budget-failure mapping](https://github.com/morluto/rea/blob/a5f982de8dbe4b092cc20ab89d492b7e7028b758/src/native/parsers/plist.ts#L283-L293) constructs `AnalysisResourceConstraintError` without `remediationAction`. The [shared presentation fallback](https://github.com/morluto/rea/blob/a5f982de8dbe4b092cc20ab89d492b7e7028b758/src/domain/analysisErrorPresentation.ts#L67-L79) then selects generic worker/host-memory advice. Applying the existing fixed-allowance recovery helper at this producer is a proposed correction; no fix or restored behavior was tested. ### Additional context Related [issue 5556](https://github.com/morluto/rea/issues/5556) and merged [PR 5633](https://github.com/morluto/rea/pull/5633) cover other fixed-allowance producers, not this binary-plist decoder. [Tracker 5808](https://github.com/morluto/rea/issues/5808) proposes a broader remaining recovery audit without reporting this specific failure. Read-only open/closed issue and PR searches found no matching report or fix for this producer. Last known good version: NOT_ENOUGH_INFO; none established.
Author
Owner

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

Agreed — recommending host-memory changes for a fixed decode-estimate allowance is actively misleading: raising memory will never change maximum_decoded_bytes, so users will retry with "sufficient memory" and hit the same refusal. The recovery text should state the fixed allowance (and the documented bound) instead of pointing at host limits, and ideally distinguish this fixed-budget refusal from genuinely host-dependent resource constraints in the same schema.

<!-- gh-comment-id:6311813258 --> @XinwangTao commented on GitHub (Oct 21, 2026): Agreed — recommending host-memory changes for a fixed decode-estimate allowance is actively misleading: raising memory will never change maximum_decoded_bytes, so users will retry with "sufficient memory" and hit the same refusal. The recovery text should state the fixed allowance (and the documented bound) instead of pointing at host limits, and ideally distinguish this fixed-budget refusal from genuinely host-dependent resource constraints in the same schema.
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#3544
No description provided.