[GH-ISSUE #2317] [Bug] Artifact inventory labels JVM class files as universal Mach-O executables #766

Closed
opened 2026-10-15 18:28:10 +00:00 by bsakaguchi · 2 comments
Owner

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

Before submitting

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

Area

Evidence, artifacts, or reconstruction

Observed behavior

inspect-artifact on a JAR or ZIP reports each JVM .class entry as artifact_kind: executable, artifact_format: mach-o-universal, the same labels a real universal binary in the same archive gets. The occurrences carry no limitation, and normalized_result.coverage.status is complete-within-substeps. This happened for both a 57-byte class and a 4,060-byte class; file identifies both as "compiled Java class data, version 65.0".

In source, classifyArtifactBytes in src/artifacts/inventory/ArtifactGraphConstruction.ts labels every entry that starts with CA FE BA BE as a universal (FAT) Mach-O. JVM class files start with the same 4 bytes.

Reproduction

  1. Generate two valid class files (both class Hello, class-file version 65.0): Hello.class (57 bytes) and Big.class (4,060 bytes, the same class plus one unused 4,000-character UTF-8 constant). Put both in a JAR, and put both plus a real universal binary in a ZIP as a control:
mkdir -p /tmp/class-check && cd /tmp/class-check
python3 - <<'EOF'
import struct, zipfile, shutil
def utf8(s): b = s.encode(); return b'\x01' + struct.pack('>H', len(b)) + b
def make_class(extra):
    cp = [b'\x07' + struct.pack('>H', 2), utf8('Hello'), b'\x07' + struct.pack('>H', 4), utf8('java/lang/Object')] + extra
    return b'\xca\xfe\xba\xbe' + struct.pack('>HH', 0, 65) + struct.pack('>H', len(cp) + 1) + b''.join(cp) + struct.pack('>HHHHHHH', 0x21, 1, 3, 0, 0, 0, 0)
small = make_class([])
big = make_class([utf8('x' * 4000)])
open('Hello.class', 'wb').write(small)
open('Big.class', 'wb').write(big)
print(len(small), len(big))
with zipfile.ZipFile('hello.jar', 'w') as z:
    z.writestr('META-INF/MANIFEST.MF', 'Manifest-Version: 1.0\r\n\r\n')
    z.writestr('Hello.class', small)
    z.writestr('Big.class', big)
shutil.copy('/bin/ls', 'ls')
with zipfile.ZipFile('mixed.zip', 'w') as z:
    z.write('Hello.class'); z.write('Big.class'); z.write('ls')
EOF
file Hello.class Big.class
  1. Run npx -y rea-agents@6.4.0 inspect-artifact /tmp/class-check/mixed.zip --format json and read normalized_result.substeps[0].evidence.normalized_result.occurrences.
  2. Run npx -y rea-agents@6.4.0 inspect-artifact /tmp/class-check/hello.jar --format json and read the same occurrences.

Both commands exit 0 with empty stderr. The step 2 control relies on /bin/ls being a universal binary, which is true on macOS. Step 3 does not depend on the host, but it was only run on macOS.

Expected behavior

docs/native-investigation.md defines the Evidence subject format as "mach-o-universal for FAT files". A class file is not a FAT file: its bytes 4-7 hold the class-file minor_version and major_version. docs/tool-design.md asks tools to "Keep observed, derived, and inferred relationships distinct" and to return "relevant confidence and limitations". The same classifier already avoids guessing from a short signature for MZ files: it returns pe or dos-mz only after checking the header and unknown otherwise.

Each .class occurrence should therefore get a non-Mach-O format (for example file, unknown, or a dedicated class-file format) and should not get artifact_kind: executable. Real universal binaries, with the 32-bit and 64-bit FAT magics in either byte order, should keep mach-o-universal.

Impact and scope

For any JAR, WAR or ZIP with class files, inventory Evidence reports a content format and kind the bytes do not support, with no occurrence limitation and complete coverage. This affects evidence integrity: the wrong label is indistinguishable from a real universal binary's label in the same archive. A caller that trusts it would likely treat Java bytecode as a native Mach-O executable; that downstream effect is inferred, not observed.

Workaround: a caller can extract the entry (for example with extract-artifact or unzip) and check bytes 4-7, the .class suffix, or file before trusting mach-o-universal. REA's output does not prompt this check at the occurrence level.

Not affected: the real universal binary in the control (/bin/ls) is labeled correctly. Opening a standalone class file is refused (see Additional context), so this report concerns inventory labels, not analysis of class files as Mach-O.

REA version

6.4.0 (npm rea-agents); classifier unchanged on main bb26622

Runtime and platform

Node v26.10.0; macOS 27.0.1 arm64

Target and artifact context

A synthetic class file and a JAR/ZIP generated by the included script

Interface

CLI

Earliest failure stage

Artifact or evidence registration

Relevant output or evidence

mixed.zip occurrences (logical_path, artifact_kind, artifact_format, limitations):
.            container   zip               []
Big.class    executable  mach-o-universal  []
Hello.class  executable  mach-o-universal  []
ls           executable  mach-o-universal  []

hello.jar occurrences (logical_path, artifact_kind, artifact_format, limitations):
.                     container   zip               []
Big.class             executable  mach-o-universal  []
Hello.class           executable  mach-o-universal  []
META-INF/MANIFEST.MF  resource    file              []

Both runs: normalized_result.coverage.status = "complete-within-substeps"
Content nodes for the 57-byte and 4,060-byte entries: "format": "mach-o-universal"
Only hedge (top-level limitation, does not reach the occurrences): "Inspection derives bounded static hypotheses and next probes; it does not execute the artifact or claim format-specific semantics."

$ file Hello.class Big.class
Hello.class: compiled Java class data, version 65.0
Big.class:   compiled Java class data, version 65.0

$ lipo -info /bin/ls
Architectures in the fat file: /bin/ls are: x86_64 arm64e arm64e.x1

Typed outcome or recovery expected

Failing result: with the fixtures above, inspect-artifact on hello.jar and on mixed.zip reports both Hello.class and Big.class as executable / mach-o-universal.

Passing result: in both archives, both class occurrences get a format other than mach-o-universal or mach-o, and a kind other than executable. Big.class must pass as well as Hello.class: read as a FAT header, its bytes 4-7 give an architecture count of 65, and that table ends at byte 1,308, inside the 4,060-byte file, so rejecting only tables that run past end-of-file would leave it mislabeled.

Control: in mixed.zip, ls still reports executable / mach-o-universal with an empty occurrence limitations list, and universal binaries with each FAT magic (0xcafebabe, 0xcafebabf, and the byte-swapped 0xbebafeca and 0xbfbafeca) still classify as mach-o-universal with no new ambiguity limitation.

Analysis or suspected cause

Source-derived, not a prescribed fix: on main at bb26622670, classifyArtifactBytes (src/artifacts/inventory/ArtifactGraphConstruction.ts:439-440) returns mach-o-universal for 0xcafebabe, 0xcafebabf and their byte-swapped forms without reading the rest of the header. The published 6.4.0 package has the same check in dist/artifacts/inventory/ArtifactGraphConstruction.js.

Additional context

A related signal follows a different path and is not part of the restoration check above. Opening a standalone class file goes through BinaryTargetResolver and parseExecutableHeader in src/domain/binaryTarget.ts, which sends the same magic to the FAT parser. With 6.4.0, rea inspect refuses the 57-byte fixture with "truncated or invalid FAT architecture table" and the 4,060-byte fixture with "FAT binary has no host-compatible arm64 architecture". Both refusals fail closed, but they explain the refusal with Mach-O reasons.

Statically, main keeps both reason strings in parseFatMachO (src/domain/binaryTarget.ts), and #1987 (the fix for #1979) changed only how format refusals are presented; runtime on main was not run.

Originally created by @coygeek on GitHub (Oct 12, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/2317 ### 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 Evidence, artifacts, or reconstruction ### Observed behavior `inspect-artifact` on a JAR or ZIP reports each JVM `.class` entry as `artifact_kind: executable`, `artifact_format: mach-o-universal`, the same labels a real universal binary in the same archive gets. The occurrences carry no limitation, and `normalized_result.coverage.status` is `complete-within-substeps`. This happened for both a 57-byte class and a 4,060-byte class; `file` identifies both as "compiled Java class data, version 65.0". In source, `classifyArtifactBytes` in `src/artifacts/inventory/ArtifactGraphConstruction.ts` labels every entry that starts with `CA FE BA BE` as a universal (FAT) Mach-O. JVM class files start with the same 4 bytes. ### Reproduction 1. Generate two valid class files (both class `Hello`, class-file version 65.0): `Hello.class` (57 bytes) and `Big.class` (4,060 bytes, the same class plus one unused 4,000-character UTF-8 constant). Put both in a JAR, and put both plus a real universal binary in a ZIP as a control: ```sh mkdir -p /tmp/class-check && cd /tmp/class-check python3 - <<'EOF' import struct, zipfile, shutil def utf8(s): b = s.encode(); return b'\x01' + struct.pack('>H', len(b)) + b def make_class(extra): cp = [b'\x07' + struct.pack('>H', 2), utf8('Hello'), b'\x07' + struct.pack('>H', 4), utf8('java/lang/Object')] + extra return b'\xca\xfe\xba\xbe' + struct.pack('>HH', 0, 65) + struct.pack('>H', len(cp) + 1) + b''.join(cp) + struct.pack('>HHHHHHH', 0x21, 1, 3, 0, 0, 0, 0) small = make_class([]) big = make_class([utf8('x' * 4000)]) open('Hello.class', 'wb').write(small) open('Big.class', 'wb').write(big) print(len(small), len(big)) with zipfile.ZipFile('hello.jar', 'w') as z: z.writestr('META-INF/MANIFEST.MF', 'Manifest-Version: 1.0\r\n\r\n') z.writestr('Hello.class', small) z.writestr('Big.class', big) shutil.copy('/bin/ls', 'ls') with zipfile.ZipFile('mixed.zip', 'w') as z: z.write('Hello.class'); z.write('Big.class'); z.write('ls') EOF file Hello.class Big.class ``` 2. Run `npx -y rea-agents@6.4.0 inspect-artifact /tmp/class-check/mixed.zip --format json` and read `normalized_result.substeps[0].evidence.normalized_result.occurrences`. 3. Run `npx -y rea-agents@6.4.0 inspect-artifact /tmp/class-check/hello.jar --format json` and read the same occurrences. Both commands exit 0 with empty stderr. The step 2 control relies on `/bin/ls` being a universal binary, which is true on macOS. Step 3 does not depend on the host, but it was only run on macOS. ### Expected behavior `docs/native-investigation.md` defines the Evidence subject format as "`mach-o-universal` for FAT files". A class file is not a FAT file: its bytes 4-7 hold the class-file `minor_version` and `major_version`. `docs/tool-design.md` asks tools to "Keep observed, derived, and inferred relationships distinct" and to return "relevant confidence and limitations". The same classifier already avoids guessing from a short signature for MZ files: it returns `pe` or `dos-mz` only after checking the header and `unknown` otherwise. Each `.class` occurrence should therefore get a non-Mach-O format (for example `file`, `unknown`, or a dedicated class-file format) and should not get `artifact_kind: executable`. Real universal binaries, with the 32-bit and 64-bit FAT magics in either byte order, should keep `mach-o-universal`. ### Impact and scope For any JAR, WAR or ZIP with class files, inventory Evidence reports a content format and kind the bytes do not support, with no occurrence limitation and complete coverage. This affects evidence integrity: the wrong label is indistinguishable from a real universal binary's label in the same archive. A caller that trusts it would likely treat Java bytecode as a native Mach-O executable; that downstream effect is inferred, not observed. Workaround: a caller can extract the entry (for example with `extract-artifact` or `unzip`) and check bytes 4-7, the `.class` suffix, or `file` before trusting `mach-o-universal`. REA's output does not prompt this check at the occurrence level. Not affected: the real universal binary in the control (`/bin/ls`) is labeled correctly. Opening a standalone class file is refused (see Additional context), so this report concerns inventory labels, not analysis of class files as Mach-O. ### REA version 6.4.0 (npm rea-agents); classifier unchanged on main bb26622 ### Runtime and platform Node v26.10.0; macOS 27.0.1 arm64 ### Target and artifact context A synthetic class file and a JAR/ZIP generated by the included script ### Interface CLI ### Earliest failure stage Artifact or evidence registration ### Relevant output or evidence ```text mixed.zip occurrences (logical_path, artifact_kind, artifact_format, limitations): . container zip [] Big.class executable mach-o-universal [] Hello.class executable mach-o-universal [] ls executable mach-o-universal [] hello.jar occurrences (logical_path, artifact_kind, artifact_format, limitations): . container zip [] Big.class executable mach-o-universal [] Hello.class executable mach-o-universal [] META-INF/MANIFEST.MF resource file [] Both runs: normalized_result.coverage.status = "complete-within-substeps" Content nodes for the 57-byte and 4,060-byte entries: "format": "mach-o-universal" Only hedge (top-level limitation, does not reach the occurrences): "Inspection derives bounded static hypotheses and next probes; it does not execute the artifact or claim format-specific semantics." $ file Hello.class Big.class Hello.class: compiled Java class data, version 65.0 Big.class: compiled Java class data, version 65.0 $ lipo -info /bin/ls Architectures in the fat file: /bin/ls are: x86_64 arm64e arm64e.x1 ``` ### Typed outcome or recovery expected Failing result: with the fixtures above, `inspect-artifact` on `hello.jar` and on `mixed.zip` reports both `Hello.class` and `Big.class` as `executable` / `mach-o-universal`. Passing result: in both archives, both class occurrences get a format other than `mach-o-universal` or `mach-o`, and a kind other than `executable`. `Big.class` must pass as well as `Hello.class`: read as a FAT header, its bytes 4-7 give an architecture count of 65, and that table ends at byte 1,308, inside the 4,060-byte file, so rejecting only tables that run past end-of-file would leave it mislabeled. Control: in `mixed.zip`, `ls` still reports `executable` / `mach-o-universal` with an empty occurrence `limitations` list, and universal binaries with each FAT magic (`0xcafebabe`, `0xcafebabf`, and the byte-swapped `0xbebafeca` and `0xbfbafeca`) still classify as `mach-o-universal` with no new ambiguity limitation. ### Analysis or suspected cause Source-derived, not a prescribed fix: on main at bb266226707135ad9f181e6c3afd6af0ef62fb22, `classifyArtifactBytes` (`src/artifacts/inventory/ArtifactGraphConstruction.ts:439-440`) returns `mach-o-universal` for `0xcafebabe`, `0xcafebabf` and their byte-swapped forms without reading the rest of the header. The published 6.4.0 package has the same check in `dist/artifacts/inventory/ArtifactGraphConstruction.js`. ### Additional context A related signal follows a different path and is not part of the restoration check above. Opening a standalone class file goes through `BinaryTargetResolver` and `parseExecutableHeader` in `src/domain/binaryTarget.ts`, which sends the same magic to the FAT parser. With 6.4.0, `rea inspect` refuses the 57-byte fixture with "truncated or invalid FAT architecture table" and the 4,060-byte fixture with "FAT binary has no host-compatible arm64 architecture". Both refusals fail closed, but they explain the refusal with Mach-O reasons. Statically, `main` keeps both reason strings in `parseFatMachO` (`src/domain/binaryTarget.ts`), and #1987 (the fix for #1979) changed only how format refusals are presented; runtime on `main` was not run.
bsakaguchi 2026-10-15 18:28:10 +00:00
Author
Owner

@jeonjw85 commented on GitHub (Oct 12, 2026):

I'll take this. I reproduced both the 57-byte and 4,060-byte class files being reported as executable / mach-o-universal through inspect-artifact on JAR and ZIP inputs; the real universal /bin/ls control remains correctly identified. The inventory classifier is unchanged on current main. I'll add the regression first and open an early draft PR, then validate FAT structure while preserving FAT32/FAT64 and both byte orders. The larger class fixture will be covered so a table-length-only check cannot hide the bug.

<!-- gh-comment-id:6131439674 --> @jeonjw85 commented on GitHub (Oct 12, 2026): I'll take this. I reproduced both the 57-byte and 4,060-byte class files being reported as executable / mach-o-universal through inspect-artifact on JAR and ZIP inputs; the real universal /bin/ls control remains correctly identified. The inventory classifier is unchanged on current main. I'll add the regression first and open an early draft PR, then validate FAT structure while preserving FAT32/FAT64 and both byte orders. The larger class fixture will be covered so a table-length-only check cannot hide the bug.
Author
Owner

@CassieFox23 commented on GitHub (Oct 12, 2026):

TL;DR: This issue is closed, fixed on main by 921bb9575. I re-ran the report on Linux: before that commit, class files get the universal Mach-O label. On main they do not, and four hand-built universal files keep theirs.

Results

The universal label is executable / mach-o-universal. It is the artifact kind, then the format. Each class went into a JAR and a ZIP. Every entry had an empty limitations list.

Entry Before the fix (c339f9ebe) Main at 81cca2e0e (#2083)
409-byte class, version 65.0 (javac, JDK 21) universal label resource / file
4,060-byte class, version 65.0 (javac) universal label unknown / unknown
Both again as version 52.0 (javac --release 8) universal label small: resource / file. Large: unknown / unknown
The 57-byte and 4,060-byte classes from the issue script universal label small: resource / file. Large: unknown / unknown
The four universal files that I built by hand universal label universal label

Coverage read complete-within-substeps in every run, before and after.

Not run

  • A real universal binary such as /bin/ls (no macOS here). The four controls are 32-bit and 64-bit headers in both byte orders, with two tiny thin Mach-O stubs each. They are not real macOS programs.
  • macOS and Windows
  • Node 22 and Node 26
  • npm run check, lint and the full test suite
  • Main after 81cca2e0e. It moved to 8fc2043eb while I worked. The classifier files did not change in that range. Commit c59fbf5e9 (#2144) changed how ZIP files are read, and I did not re-run the tests after that change.

Pull request #2327 also names this issue and is still open. I ran its head (8ccb9af18) on the same inputs. It does not label the class files as Mach-O either.

Thanks, coygeek, for the clear report and the repro script. The 4,060-byte case was a useful test.

Why the 4,060-byte class mattered, and bare class files
  • A class file stores its version in bytes 4 to 7. A universal file stores its architecture count there. Version 65.0 reads as 65 architectures.
  • For the 4,060-byte javac class, that table ends at byte 1,308, inside the file. Its first record points to byte 265,216 with size 83,887,617, far past the end. Main checks the records, so it rejects the file.
  • A bare .class file never reaches the inventory. inspect-artifact and rea inspect refuse it with exit 1, before and after the fix. On main, the 4,060-byte ones still say "FAT table does not identify a Mach-O image".
The code that decided this

Before the fix, the check was two lines long:

// src/artifacts/inventory/ArtifactGraphConstruction.ts, classifyArtifactBytes, c339f9ebe lines 442-443
if ([0xcafebabe, 0xbebafeca, 0xcafebabf, 0xbfbafeca].includes(magic))
  return "mach-o-universal";

On main, the same function calls a new helper at line 444. The helper reads the header, then asks whether the table is real:

// src/artifacts/inventory/ArtifactGraphConstruction.ts, 81cca2e0e lines 478-492
const classifyFatArtifact = (
  prefix: Buffer,
  fileSize: number,
): ArtifactNode["format"] | null => {
  const fatHeader = readFatContainerHeader(prefix, fileSize);
  if (fatHeader.state === "needs-data" || fatHeader.state === "malformed")
    return "unknown";
  if (fatHeader.state === "not-fat") return null;
  const identity = classifyFatContainerIdentity(fatHeader, (offset) => {
    if (offset >= prefix.length) return undefined;
    if (offset + 4 > prefix.length) return null;
    return prefix.readUInt32BE(offset);
  });
  return identity.state === "mach-o" ? "mach-o-universal" : "unknown";
};

The header reader is in src/domain/apple/machoContainer.ts. A table that runs past the end of the file, or counts more than 128 architectures, comes back as not-fat. Then the generic path labels the file.

Commands

All ran with HOME set to an empty folder and Node 24.18.0.

git -C <mirror> archive <sha> | tar -x -C /tmp/i2317/<tree>
npm ci && npm run build        # before, fix and head: exit 0
node scripts/rea.mjs inspect-artifact <jar-or-zip> --format json
node scripts/rea.mjs inspect <class-file> --format json
  • The builds for c339f9ebe and 921bb9575 exited 0. So did javac, jar and the fixture script.
  • inspect-artifact on the five archives exited 0 in every tree.
  • inspect-artifact and inspect on bare class files exited 1 in every tree.
  • Bare hand-built universal files with real CPU types exited 0 in every tree.
  • On 81cca2e0e (#2083), npm run build stopped at a type error in src/server/contractToolHandler.ts. It was an argument count. I kept the files tsc wrote and ran the bundle step by hand. The results match a clean build of 921bb9575 on all 78 result rows.
  • Main fixed that error in 8fc2043eb.
Test setup
  • Box: Nobara 44 (Fedora 44 base), Linux 7.2.9
  • CPU: AMD Ryzen 9 5900X, 12 cores / 24 threads
  • RAM: 32 GB DDR4-3600
  • Node: 24.18.0
  • Fix: 921bb9575. Before it: c339f9ebe.
  • Main tested: 81cca2e0e (#2083)
  • Other: Eclipse Temurin JDK 21.0.12.1, license GPL-2.0-only WITH Classpath-exception-2.0, used for javac and jar
  • The box was shared with another test agent.

🤖 Generated with Claude Code (Sonnet 5.5, verified by Opus 5.5) 🦊

<!-- gh-comment-id:6133058067 --> @CassieFox23 commented on GitHub (Oct 12, 2026): **TL;DR:** This issue is closed, fixed on main by 921bb9575. I re-ran the report on Linux: before that commit, class files get the universal Mach-O label. On main they do not, and four hand-built universal files keep theirs. ### Results The universal label is `executable / mach-o-universal`. It is the artifact kind, then the format. Each class went into a JAR and a ZIP. Every entry had an empty limitations list. | Entry | Before the fix (c339f9ebe) | Main at 81cca2e0e (#2083) | | --- | --- | --- | | 409-byte class, version 65.0 (javac, JDK 21) | universal label | resource / file | | 4,060-byte class, version 65.0 (javac) | universal label | unknown / unknown | | Both again as version 52.0 (javac --release 8) | universal label | small: resource / file. Large: unknown / unknown | | The 57-byte and 4,060-byte classes from the issue script | universal label | small: resource / file. Large: unknown / unknown | | The four universal files that I built by hand | universal label | universal label | Coverage read `complete-within-substeps` in every run, before and after. ### Not run - A real universal binary such as /bin/ls (no macOS here). The four controls are 32-bit and 64-bit headers in both byte orders, with two tiny thin Mach-O stubs each. They are not real macOS programs. - macOS and Windows - Node 22 and Node 26 - `npm run check`, lint and the full test suite - Main after 81cca2e0e. It moved to 8fc2043eb while I worked. The classifier files did not change in that range. Commit c59fbf5e9 (#2144) changed how ZIP files are read, and I did not re-run the tests after that change. Pull request #2327 also names this issue and is still open. I ran its head (8ccb9af18) on the same inputs. It does not label the class files as Mach-O either. Thanks, coygeek, for the clear report and the repro script. The 4,060-byte case was a useful test. <details> <summary>Why the 4,060-byte class mattered, and bare class files</summary> - A class file stores its version in bytes 4 to 7. A universal file stores its architecture count there. Version 65.0 reads as 65 architectures. - For the 4,060-byte javac class, that table ends at byte 1,308, inside the file. Its first record points to byte 265,216 with size 83,887,617, far past the end. Main checks the records, so it rejects the file. - A bare `.class` file never reaches the inventory. `inspect-artifact` and `rea inspect` refuse it with exit 1, before and after the fix. On main, the 4,060-byte ones still say "FAT table does not identify a Mach-O image". </details> <details> <summary>The code that decided this</summary> Before the fix, the check was two lines long: ```ts // src/artifacts/inventory/ArtifactGraphConstruction.ts, classifyArtifactBytes, c339f9ebe lines 442-443 if ([0xcafebabe, 0xbebafeca, 0xcafebabf, 0xbfbafeca].includes(magic)) return "mach-o-universal"; ``` On main, the same function calls a new helper at line 444. The helper reads the header, then asks whether the table is real: ```ts // src/artifacts/inventory/ArtifactGraphConstruction.ts, 81cca2e0e lines 478-492 const classifyFatArtifact = ( prefix: Buffer, fileSize: number, ): ArtifactNode["format"] | null => { const fatHeader = readFatContainerHeader(prefix, fileSize); if (fatHeader.state === "needs-data" || fatHeader.state === "malformed") return "unknown"; if (fatHeader.state === "not-fat") return null; const identity = classifyFatContainerIdentity(fatHeader, (offset) => { if (offset >= prefix.length) return undefined; if (offset + 4 > prefix.length) return null; return prefix.readUInt32BE(offset); }); return identity.state === "mach-o" ? "mach-o-universal" : "unknown"; }; ``` The header reader is in `src/domain/apple/machoContainer.ts`. A table that runs past the end of the file, or counts more than 128 architectures, comes back as not-fat. Then the generic path labels the file. </details> <details> <summary>Commands</summary> All ran with HOME set to an empty folder and Node 24.18.0. ```sh git -C <mirror> archive <sha> | tar -x -C /tmp/i2317/<tree> npm ci && npm run build # before, fix and head: exit 0 node scripts/rea.mjs inspect-artifact <jar-or-zip> --format json node scripts/rea.mjs inspect <class-file> --format json ``` - The builds for c339f9ebe and 921bb9575 exited 0. So did javac, jar and the fixture script. - `inspect-artifact` on the five archives exited 0 in every tree. - `inspect-artifact` and `inspect` on bare class files exited 1 in every tree. - Bare hand-built universal files with real CPU types exited 0 in every tree. - On 81cca2e0e (#2083), `npm run build` stopped at a type error in `src/server/contractToolHandler.ts`. It was an argument count. I kept the files tsc wrote and ran the bundle step by hand. The results match a clean build of 921bb9575 on all 78 result rows. - Main fixed that error in 8fc2043eb. </details> <details> <summary>Test setup</summary> - Box: Nobara 44 (Fedora 44 base), Linux 7.2.9 - CPU: AMD Ryzen 9 5900X, 12 cores / 24 threads - RAM: 32 GB DDR4-3600 - Node: 24.18.0 - Fix: 921bb9575. Before it: c339f9ebe. - Main tested: 81cca2e0e (#2083) - Other: Eclipse Temurin JDK 21.0.12.1, license `GPL-2.0-only WITH Classpath-exception-2.0`, used for javac and jar - The box was shared with another test agent. </details> 🤖 Generated with Claude Code (Sonnet 5.5, verified by Opus 5.5) 🦊
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#766
No description provided.