[GH-ISSUE #4556] [Bug] Ghidra java check fails with Homebrew keg-only openjdk on macOS #2368

Closed
opened 2026-10-17 21:03:49 +00:00 by bsakaguchi · 1 comment
Owner

Originally created by @DanielCvz1 on GitHub (Oct 16, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/4556

Before submitting

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

Area

Setup, packaging, or upgrade

Observed behavior

With Ghidra installed via Homebrew and openjdk also installed via Homebrew, rea doctor --provider ghidra fails only the ghidra-java check. Seven of the eight ghidra checks pass:

ok     ghidra                     /opt/homebrew/Cellar/ghidra/12.1.4/libexec
ok     ghidra-platform            darwin
ok     ghidra-architecture        arm64
ok     ghidra-installation        .../Ghidra/application.properties
ok     ghidra-version             12.1.4
ok     ghidra-headless            .../support/analyzeHeadless
ok     ghidra-native_decompiler   reports mach-o arm64, which matches darwin/arm64
false  ghidra-java                detail: java    classification: missing_dependency

Homebrew's openjdk is keg-only: it is neither on PATH nor resolved by /usr/libexec/java_home. A working JDK 27 is present at /opt/homebrew/opt/openjdk/libexec/openjdk.jdk/Contents/Home, and setting JAVA_HOME to it makes every check pass, so the installation itself is fine.

Reproduction

brew install ghidra openjdk       # openjdk is keg-only

env -u JAVA_HOME \
  GHIDRA_INSTALL_DIR=/opt/homebrew/Cellar/ghidra/12.1.4/libexec \
  PATH="/usr/bin:/bin:/opt/homebrew/bin" \
  rea doctor --provider ghidra
# ghidra-java: ok: false, detail: java

Expected behavior

With a usable JDK present in a standard Homebrew location, the ghidra-java check should find it, or the remediation should name the keg-only case so the user knows to set JAVA_HOME to the keg path.

Impact and scope

Affects the default macOS setup path (brew install ghidra openjdk). The provider is otherwise fully healthy, so the failure reads as a missing JDK when one is installed. Workaround is to export JAVA_HOME manually, which is easy once you know the cause and not obvious before.

REA version

6.5.0

Runtime and platform

Node 26.11.1, darwin/arm64 (macOS), Ghidra 12.1.4 via Homebrew, OpenJDK 27 via Homebrew (keg-only)

Target and artifact context

Not target-specific: the check fails during provider admission, before any artifact is opened.

Interface

CLI

Earliest failure stage

Setup or provider admission

Relevant output or evidence

detail: java is the bare command name, which matches the javaCommand value built in src/ghidra/GhidraInstallation.ts when options.javaHome is undefined.

Typed outcome or recovery expected

ghidra-java reports ok: true when a usable JDK exists in a standard Homebrew location, or its remediation names the keg-only case explicitly.

Analysis or suspected cause

src/ghidra/GhidraInstallation.ts, lines 463-472:

const javaCommand =
  options.javaHome === undefined
    ? platform === "win32"
      ? "java.exe"
      : "java"
    : path.join(
        options.javaHome,
        "bin",
        platform === "win32" ? "java.exe" : "java",
      );

With javaHome undefined the command is bare "java", so a keg-only JDK is never found.

A candidate list would follow the idiom the same file already uses for the native decompiler at line 458 (].find((candidate) => host.executable(candidate)) ?? null):

const javaCandidates =
  options.javaHome === undefined
    ? platform === "win32"
      ? ["java.exe"]
      : [
          "java",
          "/opt/homebrew/opt/openjdk/libexec/openjdk.jdk/Contents/Home/bin/java",
          "/usr/local/opt/openjdk/libexec/openjdk.jdk/Contents/Home/bin/java",
        ]
    : [path.join(options.javaHome, "bin", platform === "win32" ? "java.exe" : "java")];

const javaCommand =
  javaCandidates.find((candidate) => host.executable(candidate)) ?? javaCandidates[0];

This respects the constraint your own comment states a few lines below:

The configuration check rejects a relative JAVA_HOME. Probing it would run whatever bin/java the current directory holds.

All added candidates are absolute, so no cwd-relative probing is introduced. "java" stays first, preserving current behavior when it resolves.

Worth noting: the keg path contains a JDK 27, but a found JDK below Ghidra's minimum should probably report "found but insufficient" rather than "missing", since those need different remediations.

Additional context

Declaring the state of our own verification, so you can weigh this accordingly. We are a small team building an evidence layer on top of REA, and our own system is still under sanity checks — we are actively finding and fixing defects in it, including several of our own today. So treat our verification as careful but not yet hardened:

  • The reproduction above was run on this machine and is what we observed.
  • The source lines and the existing idiom were read directly in src/ghidra/GhidraInstallation.ts at v6.5.0.
  • We did not run your test suite, so the proposed patch is reasoned, not CI-verified.
  • We do run the equivalent candidate-list logic in our own adapter and it resolves the keg-only JDK, but that is our code, not yours, and it is not evidence about your CI.

Separately, and not a request: inspect-macho returning exhaustive: true|false per collection is excellent. Our evidence contract stated that a universal negation needs a closed-universe certificate "which does not exist yet", and that certificate turned out to be a boolean in your output. Thanks for that one.

Originally created by @DanielCvz1 on GitHub (Oct 16, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/4556 ### 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 Setup, packaging, or upgrade ### Observed behavior With Ghidra installed via Homebrew and `openjdk` also installed via Homebrew, `rea doctor --provider ghidra` fails only the `ghidra-java` check. Seven of the eight ghidra checks pass: ``` ok ghidra /opt/homebrew/Cellar/ghidra/12.1.4/libexec ok ghidra-platform darwin ok ghidra-architecture arm64 ok ghidra-installation .../Ghidra/application.properties ok ghidra-version 12.1.4 ok ghidra-headless .../support/analyzeHeadless ok ghidra-native_decompiler reports mach-o arm64, which matches darwin/arm64 false ghidra-java detail: java classification: missing_dependency ``` Homebrew's `openjdk` is *keg-only*: it is neither on `PATH` nor resolved by `/usr/libexec/java_home`. A working JDK 27 is present at `/opt/homebrew/opt/openjdk/libexec/openjdk.jdk/Contents/Home`, and setting `JAVA_HOME` to it makes every check pass, so the installation itself is fine. ### Reproduction ```bash brew install ghidra openjdk # openjdk is keg-only env -u JAVA_HOME \ GHIDRA_INSTALL_DIR=/opt/homebrew/Cellar/ghidra/12.1.4/libexec \ PATH="/usr/bin:/bin:/opt/homebrew/bin" \ rea doctor --provider ghidra # ghidra-java: ok: false, detail: java ``` ### Expected behavior With a usable JDK present in a standard Homebrew location, the `ghidra-java` check should find it, or the remediation should name the keg-only case so the user knows to set `JAVA_HOME` to the keg path. ### Impact and scope Affects the default macOS setup path (`brew install ghidra openjdk`). The provider is otherwise fully healthy, so the failure reads as a missing JDK when one is installed. Workaround is to export `JAVA_HOME` manually, which is easy once you know the cause and not obvious before. ### REA version 6.5.0 ### Runtime and platform Node 26.11.1, darwin/arm64 (macOS), Ghidra 12.1.4 via Homebrew, OpenJDK 27 via Homebrew (keg-only) ### Target and artifact context Not target-specific: the check fails during provider admission, before any artifact is opened. ### Interface CLI ### Earliest failure stage Setup or provider admission ### Relevant output or evidence `detail: java` is the bare command name, which matches the `javaCommand` value built in `src/ghidra/GhidraInstallation.ts` when `options.javaHome` is `undefined`. ### Typed outcome or recovery expected `ghidra-java` reports `ok: true` when a usable JDK exists in a standard Homebrew location, or its remediation names the keg-only case explicitly. ### Analysis or suspected cause `src/ghidra/GhidraInstallation.ts`, lines 463-472: ```ts const javaCommand = options.javaHome === undefined ? platform === "win32" ? "java.exe" : "java" : path.join( options.javaHome, "bin", platform === "win32" ? "java.exe" : "java", ); ``` With `javaHome` undefined the command is bare `"java"`, so a keg-only JDK is never found. A candidate list would follow the idiom the same file already uses for the native decompiler at line 458 (`].find((candidate) => host.executable(candidate)) ?? null`): ```ts const javaCandidates = options.javaHome === undefined ? platform === "win32" ? ["java.exe"] : [ "java", "/opt/homebrew/opt/openjdk/libexec/openjdk.jdk/Contents/Home/bin/java", "/usr/local/opt/openjdk/libexec/openjdk.jdk/Contents/Home/bin/java", ] : [path.join(options.javaHome, "bin", platform === "win32" ? "java.exe" : "java")]; const javaCommand = javaCandidates.find((candidate) => host.executable(candidate)) ?? javaCandidates[0]; ``` This respects the constraint your own comment states a few lines below: > The configuration check rejects a relative JAVA_HOME. Probing it would run whatever `bin/java` the current directory holds. All added candidates are absolute, so no cwd-relative probing is introduced. `"java"` stays first, preserving current behavior when it resolves. Worth noting: the keg path contains a JDK 27, but a found JDK below Ghidra's minimum should probably report "found but insufficient" rather than "missing", since those need different remediations. ### Additional context **Declaring the state of our own verification, so you can weigh this accordingly.** We are a small team building an evidence layer on top of REA, and **our own system is still under sanity checks** — we are actively finding and fixing defects in it, including several of our own today. So treat our verification as careful but not yet hardened: - The reproduction above was run on this machine and is what we observed. - The source lines and the existing idiom were read directly in `src/ghidra/GhidraInstallation.ts` at v6.5.0. - **We did not run your test suite**, so the proposed patch is reasoned, not CI-verified. - We do run the equivalent candidate-list logic in our own adapter and it resolves the keg-only JDK, but that is our code, not yours, and it is not evidence about your CI. Separately, and not a request: `inspect-macho` returning `exhaustive: true|false` per collection is excellent. Our evidence contract stated that a universal negation needs a closed-universe certificate "which does not exist yet", and that certificate turned out to be a boolean in your output. Thanks for that one.
Author
Owner

@vadlachary2002 commented on GitHub (Oct 16, 2026):

Thanks for the detailed report and analysis! I've opened PR #4557 to implement the proposed candidate list check for keg-only Homebrew OpenJDK paths.

<!-- gh-comment-id:6222757884 --> @vadlachary2002 commented on GitHub (Oct 16, 2026): Thanks for the detailed report and analysis! I've opened PR #4557 to implement the proposed candidate list check for keg-only Homebrew OpenJDK paths.
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#2368
No description provided.