[GH-ISSUE #7652] [Bug] Dispatch metadata reports malformed section sizes as provider execution failures #4578

Closed
opened 2026-10-31 00:50:56 +00:00 by bsakaguchi · 0 comments
Owner

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

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

On the baseline below, node scripts/rea.mjs inspect-native-dispatch-metadata "$WORK/objc12" --format json rejects a selected Mach-O with a 12-byte Objective-C class-list section, but identifies the problem as execution_failure / execution_failure. Its message and remediation both advise running rea doctor. The response itself identifies the actual byte-format problem as Misaligned Objective-C class-list size in details.diagnostics.reason.

The same command reports that provider-failure category and recovery for a 6-byte Swift type section and a synthetic big-endian arm64 header. A size-8 Objective-C class-list control returns successful Evidence and an empty class list. The defect is the attribution and recovery for refused bytes; refusing malformed or unsupported input is appropriate.

Reproduction

  1. Use the built baseline below with its locked dependencies and Node.js v24.22.1. From the checkout root, run this script. It creates inert fixtures, isolates product state, and puts fixture npm/npx/bun executables first on PATH. It invokes only version reporting and the static dispatch-metadata inspection command.
WORK="$(mktemp -d)"
export WORK
python3 -I - <<'PYFIXTURE'
import json, os, pathlib, shutil, struct, subprocess

work = pathlib.Path(os.environ['WORK']).resolve()
for part in ('home', 'config', 'data', 'cache', 'state', 'temp', 'bin'):
    (work / part).mkdir(parents=True, exist_ok=True)
for tool in ('npm', 'npx', 'bun', 'Hopper', 'hopper'):
    stub = work / 'bin' / tool
    stub.write_text('#!/bin/sh\nexit 97\n')
    stub.chmod(0o755)
node_dir = str(pathlib.Path(shutil.which('node')).parent)
env = {
    'PATH': str(work / 'bin') + ':' + node_dir + ':/usr/bin:/bin:/usr/sbin:/sbin',
    'HOME': str(work / 'home'),
    'XDG_CONFIG_HOME': str(work / 'config'),
    'XDG_DATA_HOME': str(work / 'data'),
    'XDG_CACHE_HOME': str(work / 'cache'),
    'XDG_STATE_HOME': str(work / 'state'),
    'TMPDIR': str(work / 'temp'),
    'REA_LOG_LEVEL': 'silent',
    'NO_COLOR': '1',
}
for args in (['node', '--version'], ['node', 'scripts/rea.mjs', '--version']):
    r = subprocess.run(args, env=env, capture_output=True, text=True, timeout=60)
    assert r.returncode == 0, r.stderr
    print(' '.join(args) + ': ' + r.stdout.strip())

cases = [
    ('objc12', '__objc_classlist', 12, '<'),
    ('be-arm64', '__objc_classlist', 8, '>'),
    ('swift6', '__swift5_types', 6, '<'),
    ('objc8', '__objc_classlist', 8, '<'),
]
for name, section, size, order in cases:
    data = bytearray(4096)
    struct.pack_into(order + 'IIIIIIII', data, 0,
                     0xfeedfacf, 0x0100000c, 0, 2, 1, 152, 0, 0)
    struct.pack_into(order + 'II16sQQQQiiII', data, 32,
                     0x19, 152, b'__DATA', 0x100000000, 4096, 0, 4096, 7, 3, 1, 0)
    struct.pack_into(order + '16s16sQQIIIIIIII', data, 104,
                     section.encode(), b'__DATA', 0x100000200, size,
                     512, 0, 0, 0, 0, 0, 0, 0)
    (work / name).write_bytes(data)

for attempt in range(1, 3):
    for name, _, _, _ in cases:
        args = ['node', 'scripts/rea.mjs', 'inspect-native-dispatch-metadata',
                str(work / name), '--format', 'json']
        r = subprocess.run(args, env=env, capture_output=True, text=True, timeout=60)
        result = json.loads(r.stdout)
        summary = {'attempt': attempt, 'input': name, 'exit': r.returncode}
        if name == 'objc8':
            assert r.returncode == 0, result
            summary['objc_classes'] = result['normalized_result']['result']['objc_classes']
        else:
            summary.update({key: result[key] for key in
                            ('code', 'category', 'message', 'retryable', 'remediation')})
            summary['reason'] = result['details']['diagnostics']['reason']
        assert r.stderr == '', r.stderr
        print(json.dumps(summary))
PYFIXTURE
rm -rf "$WORK"
  1. Read the printed error fields and control result. The initial independent run and replay of this report's script each ran two passes: all three rejection cases reproduced four times each; the size-8 control succeeded four times. Every inspection's stderr was empty.

Expected behavior

Return an input- or target-specific typed refusal for a malformed section size or unsupported byte-order/CPU declaration, retain the precise reason, and direct recovery toward a well-formed or supported artifact. Provider-readiness advice cannot change these selected bytes. This does not require accepting corrupt metadata, adding big-endian arm64 support, or suppressing real provider failures.

The repository's boundary contract requires expected failures to use its tagged error algebra and preserve meaningful reasons. Its error types distinguish invalid caller input, readable unsupported targets and the generic provider-adapter fallback. Closed #5708, now reflected in the native-inspection documentation, establishes related target-specific refusal and recovery for recognized dSYM/CORE native-tool limitations. Exact locators are in Evidence below.

Impact and scope

P2, supported: bounded malformed or unsupported inputs return an incorrect machine-readable failure class and irrelevant repair action. The size-8 control works, and refusal itself remains correct. No data loss, target execution or security-boundary crossing occurred. A diagnostic workaround is to read details.diagnostics.reason, which retained the concrete reason in every failure. Downstream automated recovery effects and real-world frequency were not measured. MCP, other platforms and a repaired implementation were not tested.

REA version

Exact node scripts/rea.mjs --version stdout:

6.8.0

Source baseline: 65f1c447344a423fa2785847dbcea1e9a9e2454a.

Runtime and platform

Node.js v24.22.1; macOS 27.0.1; arm64. Successful control provider: native-macos, version apple-metadata-reader-2. HOME/XDG/TMPDIR were isolated; no deep-analysis provider was requested.

Target and artifact context

Each synthetic thin arm64 Mach-O is 4096 bytes, with one 152-byte LC_SEGMENT_64 and one section header. The section starts at file offset 512 / virtual address 0x100000200, beyond the headers, and contains zero-filled bytes. The control differs from the malformed Objective-C fixture only in declared section size, 8 instead of 12. The big-endian header and its command fields are consistently encoded as big-endian; this is an intentionally unsupported synthetic input.

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

Observed in both independent runs, with path-bearing fields omitted:

input       exit  code/category                       retained reason or control result
objc12      1     execution_failure/execution_failure  Misaligned Objective-C class-list size
be-arm64    1     execution_failure/execution_failure  Unsupported big-endian Mach-O CPU type
swift6      1     execution_failure/execution_failure  Misaligned Swift type section
objc8       0     successful Evidence                  objc_classes: []

For each failure, retryable is false, details.provider_id is native-macos, and details.operation is inspect_native_dispatch_metadata. Both message and remediation.action contain:

Analysis could not complete. Run `rea doctor` and review the reported details; repeating the unchanged request is not expected to succeed.

Contract locators at the baseline:

  • AGENTS.md:33: tagged expected failures and meaningful reasons.
  • docs/native-investigation.md:729-740: supported public command and little-endian 64-bit byte reader; lines 520-530 document related target-specific refusals after #5708.
  • src/domain/analysisErrorCore.ts:7-23 and 107-125: unsupported-target and input error semantics; src/domain/providerAdapterError.ts:6-23: generic fallback.
  • src/domain/analysisErrorProjection.ts:276-291: separate codes for input, target and provider failures.

I also checked docs/cli.md:442-449, 975-978, and docs/error-contract.schema.json. The reason is preserved, so this is not a lost-diagnostic claim. The schema defines error-envelope structure and separate code branches, rather than assigning these specific malformed inputs a mandatory code; the semantic expectation comes from the boundary/type contracts above. The malformed-plist documentation applies to another reader and is not used as the contract for this report.

Typed outcome or recovery expected

Replay the same fixtures and require a precise artifact-related refusal with the retained reason and useful target-selection guidance; preserve successful size-8 inspection. No fix or restoration was verified.

Analysis or suspected cause

Source-derived: src/native/NativeMacOSProvider.ts:294-320 calls the static reader and translates its thrown failure. src/native/AppleObjcDispatchFacets.ts:405-409 and src/native/AppleDispatchMetadata.ts:228-233 raise expected size-alignment RangeErrors. src/native/AppleMachoSelection.ts:184-188 raises a TypeError for the unsupported CPU/byte-order combination. The translator at src/native/AppleDispatchMetadata.ts:442-507 recognizes other typed failures but falls back to ProviderAdapterError for these format refusals. That variant produces the observed execution-failure projection and doctor advice. Direct-decoder rejection tests establish refusal, not the correctness of this public error classification.

Additional context

Related, different closure signals: closed #6154 covers unwalkable load-command framing at admission; closed #5997 covers bound-target filesystem changes and permissions; closed #3336 covers preserving sibling properties after a malformed individual record. These fixtures have walkable command framing, stable readable files and section-wide size/format refusals. No known-good revision or regression of those fixes is claimed. Searches covered open/closed issues, issue comments, and open/closed/merged PRs; no matching section-size/byte-order classification report was found.

Originally created by @coygeek on GitHub (Oct 28, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/7652 ### 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 On the baseline below, `node scripts/rea.mjs inspect-native-dispatch-metadata "$WORK/objc12" --format json` rejects a selected Mach-O with a 12-byte Objective-C class-list section, but identifies the problem as `execution_failure` / `execution_failure`. Its message and remediation both advise running `rea doctor`. The response itself identifies the actual byte-format problem as `Misaligned Objective-C class-list size` in `details.diagnostics.reason`. The same command reports that provider-failure category and recovery for a 6-byte Swift type section and a synthetic big-endian arm64 header. A size-8 Objective-C class-list control returns successful Evidence and an empty class list. The defect is the attribution and recovery for refused bytes; refusing malformed or unsupported input is appropriate. ### Reproduction 1. Use the built baseline below with its locked dependencies and Node.js v24.22.1. From the checkout root, run this script. It creates inert fixtures, isolates product state, and puts fixture npm/npx/bun executables first on PATH. It invokes only version reporting and the static dispatch-metadata inspection command. ```sh WORK="$(mktemp -d)" export WORK python3 -I - <<'PYFIXTURE' import json, os, pathlib, shutil, struct, subprocess work = pathlib.Path(os.environ['WORK']).resolve() for part in ('home', 'config', 'data', 'cache', 'state', 'temp', 'bin'): (work / part).mkdir(parents=True, exist_ok=True) for tool in ('npm', 'npx', 'bun', 'Hopper', 'hopper'): stub = work / 'bin' / tool stub.write_text('#!/bin/sh\nexit 97\n') stub.chmod(0o755) node_dir = str(pathlib.Path(shutil.which('node')).parent) env = { 'PATH': str(work / 'bin') + ':' + node_dir + ':/usr/bin:/bin:/usr/sbin:/sbin', 'HOME': str(work / 'home'), 'XDG_CONFIG_HOME': str(work / 'config'), 'XDG_DATA_HOME': str(work / 'data'), 'XDG_CACHE_HOME': str(work / 'cache'), 'XDG_STATE_HOME': str(work / 'state'), 'TMPDIR': str(work / 'temp'), 'REA_LOG_LEVEL': 'silent', 'NO_COLOR': '1', } for args in (['node', '--version'], ['node', 'scripts/rea.mjs', '--version']): r = subprocess.run(args, env=env, capture_output=True, text=True, timeout=60) assert r.returncode == 0, r.stderr print(' '.join(args) + ': ' + r.stdout.strip()) cases = [ ('objc12', '__objc_classlist', 12, '<'), ('be-arm64', '__objc_classlist', 8, '>'), ('swift6', '__swift5_types', 6, '<'), ('objc8', '__objc_classlist', 8, '<'), ] for name, section, size, order in cases: data = bytearray(4096) struct.pack_into(order + 'IIIIIIII', data, 0, 0xfeedfacf, 0x0100000c, 0, 2, 1, 152, 0, 0) struct.pack_into(order + 'II16sQQQQiiII', data, 32, 0x19, 152, b'__DATA', 0x100000000, 4096, 0, 4096, 7, 3, 1, 0) struct.pack_into(order + '16s16sQQIIIIIIII', data, 104, section.encode(), b'__DATA', 0x100000200, size, 512, 0, 0, 0, 0, 0, 0, 0) (work / name).write_bytes(data) for attempt in range(1, 3): for name, _, _, _ in cases: args = ['node', 'scripts/rea.mjs', 'inspect-native-dispatch-metadata', str(work / name), '--format', 'json'] r = subprocess.run(args, env=env, capture_output=True, text=True, timeout=60) result = json.loads(r.stdout) summary = {'attempt': attempt, 'input': name, 'exit': r.returncode} if name == 'objc8': assert r.returncode == 0, result summary['objc_classes'] = result['normalized_result']['result']['objc_classes'] else: summary.update({key: result[key] for key in ('code', 'category', 'message', 'retryable', 'remediation')}) summary['reason'] = result['details']['diagnostics']['reason'] assert r.stderr == '', r.stderr print(json.dumps(summary)) PYFIXTURE rm -rf "$WORK" ``` 2. Read the printed error fields and control result. The initial independent run and replay of this report's script each ran two passes: all three rejection cases reproduced four times each; the size-8 control succeeded four times. Every inspection's stderr was empty. ### Expected behavior Return an input- or target-specific typed refusal for a malformed section size or unsupported byte-order/CPU declaration, retain the precise reason, and direct recovery toward a well-formed or supported artifact. Provider-readiness advice cannot change these selected bytes. This does not require accepting corrupt metadata, adding big-endian arm64 support, or suppressing real provider failures. The repository's boundary contract requires expected failures to use its tagged error algebra and preserve meaningful reasons. Its error types distinguish invalid caller input, readable unsupported targets and the generic provider-adapter fallback. Closed [#5708](https://github.com/morluto/rea/issues/5708), now reflected in the native-inspection documentation, establishes related target-specific refusal and recovery for recognized dSYM/CORE native-tool limitations. Exact locators are in Evidence below. ### Impact and scope P2, supported: bounded malformed or unsupported inputs return an incorrect machine-readable failure class and irrelevant repair action. The size-8 control works, and refusal itself remains correct. No data loss, target execution or security-boundary crossing occurred. A diagnostic workaround is to read `details.diagnostics.reason`, which retained the concrete reason in every failure. Downstream automated recovery effects and real-world frequency were not measured. MCP, other platforms and a repaired implementation were not tested. ### REA version Exact `node scripts/rea.mjs --version` stdout: ```text 6.8.0 ``` Source baseline: `65f1c447344a423fa2785847dbcea1e9a9e2454a`. ### Runtime and platform Node.js `v24.22.1`; macOS `27.0.1`; `arm64`. Successful control provider: `native-macos`, version `apple-metadata-reader-2`. HOME/XDG/TMPDIR were isolated; no deep-analysis provider was requested. ### Target and artifact context Each synthetic thin arm64 Mach-O is 4096 bytes, with one 152-byte `LC_SEGMENT_64` and one section header. The section starts at file offset 512 / virtual address `0x100000200`, beyond the headers, and contains zero-filled bytes. The control differs from the malformed Objective-C fixture only in declared section size, 8 instead of 12. The big-endian header and its command fields are consistently encoded as big-endian; this is an intentionally unsupported synthetic input. ### Interface CLI ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence Observed in both independent runs, with path-bearing fields omitted: ```text input exit code/category retained reason or control result objc12 1 execution_failure/execution_failure Misaligned Objective-C class-list size be-arm64 1 execution_failure/execution_failure Unsupported big-endian Mach-O CPU type swift6 1 execution_failure/execution_failure Misaligned Swift type section objc8 0 successful Evidence objc_classes: [] ``` For each failure, `retryable` is false, `details.provider_id` is `native-macos`, and `details.operation` is `inspect_native_dispatch_metadata`. Both `message` and `remediation.action` contain: ```text Analysis could not complete. Run `rea doctor` and review the reported details; repeating the unchanged request is not expected to succeed. ``` Contract locators at the baseline: - `AGENTS.md:33`: tagged expected failures and meaningful reasons. - `docs/native-investigation.md:729-740`: supported public command and little-endian 64-bit byte reader; lines 520-530 document related target-specific refusals after #5708. - `src/domain/analysisErrorCore.ts:7-23` and `107-125`: unsupported-target and input error semantics; `src/domain/providerAdapterError.ts:6-23`: generic fallback. - `src/domain/analysisErrorProjection.ts:276-291`: separate codes for input, target and provider failures. I also checked `docs/cli.md:442-449`, `975-978`, and `docs/error-contract.schema.json`. The reason is preserved, so this is not a lost-diagnostic claim. The schema defines error-envelope structure and separate code branches, rather than assigning these specific malformed inputs a mandatory code; the semantic expectation comes from the boundary/type contracts above. The malformed-plist documentation applies to another reader and is not used as the contract for this report. ### Typed outcome or recovery expected Replay the same fixtures and require a precise artifact-related refusal with the retained reason and useful target-selection guidance; preserve successful size-8 inspection. No fix or restoration was verified. ### Analysis or suspected cause Source-derived: `src/native/NativeMacOSProvider.ts:294-320` calls the static reader and translates its thrown failure. `src/native/AppleObjcDispatchFacets.ts:405-409` and `src/native/AppleDispatchMetadata.ts:228-233` raise expected size-alignment `RangeError`s. `src/native/AppleMachoSelection.ts:184-188` raises a `TypeError` for the unsupported CPU/byte-order combination. The translator at `src/native/AppleDispatchMetadata.ts:442-507` recognizes other typed failures but falls back to `ProviderAdapterError` for these format refusals. That variant produces the observed execution-failure projection and doctor advice. Direct-decoder rejection tests establish refusal, not the correctness of this public error classification. ### Additional context Related, different closure signals: closed [#6154](https://github.com/morluto/rea/issues/6154) covers unwalkable load-command framing at admission; closed [#5997](https://github.com/morluto/rea/issues/5997) covers bound-target filesystem changes and permissions; closed [#3336](https://github.com/morluto/rea/issues/3336) covers preserving sibling properties after a malformed individual record. These fixtures have walkable command framing, stable readable files and section-wide size/format refusals. No known-good revision or regression of those fixes is claimed. Searches covered open/closed issues, issue comments, and open/closed/merged PRs; no matching section-size/byte-order classification report was found.
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#4578
No description provided.