[GH-ISSUE #5919] [Bug] Linux Hopper setup reports launcher inspection errors as launcher_missing #3595

Closed
opened 2026-10-31 00:42:14 +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/5919

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

The production Linux installation host reports an existing launcher as missing when ldd cannot start or when reading the launcher for hashing fails. The catch discards the failure. A focused run confirmed both cases: access succeeded; missing ldd produced ENOENT, and an execute-only launcher produced a directly checked EACCES on reading. Both returned missing. With readable bytes and a matching hash the probe returned ready.

Source-derived CLI effect: after a package install, installLinuxHopper converts this status to launcher_missing, which setup exposes as its failure code. The public CLI itself was not executed.

Reproduction

The case below was executed as part of one three-case focused source test run; all three characterization tests passed. Passing characterizes the current incorrect classification. Use the repository-pinned Node 24.18.0 and installed lockfile dependencies. Save this case as src/application/repro.test.ts, run it with:

node node_modules/vitest/vitest.mjs run --project services src/application/repro.test.ts --maxWorkers 1

Run the permission cases as an ordinary non-root account on macOS or another POSIX host. The Linux application code was exercised on macOS. No actual Hopper executable or package was used.

import { createHash } from "node:crypto";
import { access, chmod, readFile, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { expect, it, vi } from "vitest";
import { systemLinuxHopperInstallHost } from "./LinuxHopper.js";
import { setupInstallFailure } from "./SetupInstallFailure.js";
import * as nativeCommands from "../process/ExecFileOutput.js";
import { createTestTempDirectory } from "../../tests/fixtures/temporaryDirectory.js";

it("existing launcher with failed ldd or hash is reported missing", async () => {
  const directory = await createTestTempDirectory("rea-hopper-classification-");
  const launcher = join(directory, "launcher");
  await writeFile(launcher, "inert fixture; never executed", { mode: 0o500 });
  const host = systemLinuxHopperInstallHost({ PATH: directory });
  try {
    await access(launcher);
    await expect(nativeCommands.execFileOutput("ldd", [launcher], { env: { PATH: directory } })).rejects.toMatchObject({ code: "ENOENT" });
    expect(await host.launcherStatus(launcher, "0".repeat(64))).toBe("missing");
    expect(setupInstallFailure("launcher_missing", undefined, "linux").code).toBe("launcher_missing");
    const command = vi.spyOn(nativeCommands, "execFileOutput").mockResolvedValue({ stdout: "linked fixture", stderr: "" });
    try {
      await chmod(launcher, 0o100);
      await access(launcher);
      await expect(readFile(launcher)).rejects.toMatchObject({ code: "EACCES" });
      expect(await host.launcherStatus(launcher, "0".repeat(64))).toBe("missing");
      await chmod(launcher, 0o500);
      expect(await host.launcherStatus(launcher, createHash("sha256").update("inert fixture; never executed").digest("hex"))).toBe("ready");
    } finally { command.mockRestore(); await chmod(launcher, 0o500); }
    console.log("launcher exists; ldd spawn ENOENT => missing; unreadable hash => missing; restored readability => ready");
  } finally { await host.close?.(); }
});

Remove the test after running it. The fixture restores permissions and closes the Linux publisher where applicable.

Expected behavior

Only a conclusive absence should produce missing/launcher_missing. Preserve launcher-inspection failures and their cause as an unresolved or failed inspection, while continuing to refuse an unverified launcher. The repository requires preserving uncertainty and meaningful failure reasons at boundaries: AGENTS.md.

Impact and scope

P2: bounded diagnostic loss after Linux installation. A caller receives an absence code instead of the linking/read failure needed for recovery. In the controlled hashing fixture, restoring read permission changed the probe to ready; production Linux frequency and real Hopper behavior are NOT_ENOUGH_INFO. No security boundary crossing, target execution or data loss was observed.

REA version

rea-agents 6.7.0; source revision 5241597594.

Runtime and platform

Node.js v24.18.0; macOS 27.0.1 arm64; lockfile Vitest 5.0.3. No Hopper engine exercised.

Target and artifact context

Inert fixture launcher bytes; missing linker tool and a real file-read permission denial. No analysis target or installed Hopper package.

Interface

Other

Earliest failure stage

Setup or provider admission

Relevant output or evidence

Executed boundary checks returned:

launcher exists; ldd spawn ENOENT => missing; unreadable hash => missing; restored readability => ready

The production probe catch returns missing for non-cleanup failures. The installation workflow maps that to launcher_missing. SetupHost forwards it through the setup mapping.

Typed outcome or recovery expected

Only a conclusive absence should produce missing/launcher_missing. Preserve launcher-inspection failures and their cause as an unresolved or failed inspection, while continuing to refuse an unverified launcher.

Analysis or suspected cause

The adapter catch covers existence, linker invocation and hashing in one block. It has no failure/unknown variant, so unrelated errors become the same missing status. Correct this producer rather than changing all launcher_missing remediation to guess at inspection errors.

Additional context

Existing provider-fingerprint and publisher-close reports concern different producers. A pending change to remove ldd alone does not repair the unreadable-hash case demonstrated here.

Originally created by @coygeek on GitHub (Oct 21, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/5919 ### 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 The production Linux installation host reports an existing launcher as `missing` when `ldd` cannot start or when reading the launcher for hashing fails. The catch discards the failure. A focused run confirmed both cases: `access` succeeded; missing `ldd` produced `ENOENT`, and an execute-only launcher produced a directly checked `EACCES` on reading. Both returned `missing`. With readable bytes and a matching hash the probe returned `ready`. Source-derived CLI effect: after a package install, `installLinuxHopper` converts this status to `launcher_missing`, which setup exposes as its failure code. The public CLI itself was not executed. ### Reproduction The case below was executed as part of one three-case focused source test run; all three characterization tests passed. Passing characterizes the current incorrect classification. Use the repository-pinned Node 24.18.0 and installed lockfile dependencies. Save this case as `src/application/repro.test.ts`, run it with: ```sh node node_modules/vitest/vitest.mjs run --project services src/application/repro.test.ts --maxWorkers 1 ``` Run the permission cases as an ordinary non-root account on macOS or another POSIX host. The Linux application code was exercised on macOS. No actual Hopper executable or package was used. ```ts import { createHash } from "node:crypto"; import { access, chmod, readFile, writeFile } from "node:fs/promises"; import { join } from "node:path"; import { expect, it, vi } from "vitest"; import { systemLinuxHopperInstallHost } from "./LinuxHopper.js"; import { setupInstallFailure } from "./SetupInstallFailure.js"; import * as nativeCommands from "../process/ExecFileOutput.js"; import { createTestTempDirectory } from "../../tests/fixtures/temporaryDirectory.js"; it("existing launcher with failed ldd or hash is reported missing", async () => { const directory = await createTestTempDirectory("rea-hopper-classification-"); const launcher = join(directory, "launcher"); await writeFile(launcher, "inert fixture; never executed", { mode: 0o500 }); const host = systemLinuxHopperInstallHost({ PATH: directory }); try { await access(launcher); await expect(nativeCommands.execFileOutput("ldd", [launcher], { env: { PATH: directory } })).rejects.toMatchObject({ code: "ENOENT" }); expect(await host.launcherStatus(launcher, "0".repeat(64))).toBe("missing"); expect(setupInstallFailure("launcher_missing", undefined, "linux").code).toBe("launcher_missing"); const command = vi.spyOn(nativeCommands, "execFileOutput").mockResolvedValue({ stdout: "linked fixture", stderr: "" }); try { await chmod(launcher, 0o100); await access(launcher); await expect(readFile(launcher)).rejects.toMatchObject({ code: "EACCES" }); expect(await host.launcherStatus(launcher, "0".repeat(64))).toBe("missing"); await chmod(launcher, 0o500); expect(await host.launcherStatus(launcher, createHash("sha256").update("inert fixture; never executed").digest("hex"))).toBe("ready"); } finally { command.mockRestore(); await chmod(launcher, 0o500); } console.log("launcher exists; ldd spawn ENOENT => missing; unreadable hash => missing; restored readability => ready"); } finally { await host.close?.(); } }); ``` Remove the test after running it. The fixture restores permissions and closes the Linux publisher where applicable. ### Expected behavior Only a conclusive absence should produce `missing`/`launcher_missing`. Preserve launcher-inspection failures and their cause as an unresolved or failed inspection, while continuing to refuse an unverified launcher. The repository requires preserving uncertainty and meaningful failure reasons at boundaries: [AGENTS.md](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/AGENTS.md#L19-L31). ### Impact and scope P2: bounded diagnostic loss after Linux installation. A caller receives an absence code instead of the linking/read failure needed for recovery. In the controlled hashing fixture, restoring read permission changed the probe to ready; production Linux frequency and real Hopper behavior are NOT_ENOUGH_INFO. No security boundary crossing, target execution or data loss was observed. ### REA version rea-agents 6.7.0; source revision 52415975942a4b9c53a817915b313ccc404f280e. ### Runtime and platform Node.js v24.18.0; macOS 27.0.1 arm64; lockfile Vitest 5.0.3. No Hopper engine exercised. ### Target and artifact context Inert fixture launcher bytes; missing linker tool and a real file-read permission denial. No analysis target or installed Hopper package. ### Interface Other ### Earliest failure stage Setup or provider admission ### Relevant output or evidence Executed boundary checks returned: ```text launcher exists; ldd spawn ENOENT => missing; unreadable hash => missing; restored readability => ready ``` The [production probe catch](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/application/LinuxHopper.ts#L473-L498) returns `missing` for non-cleanup failures. The [installation workflow](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/application/LinuxHopper.ts#L391-L401) maps that to `launcher_missing`. [SetupHost](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/application/SetupHost.ts#L213-L234) forwards it through the [setup mapping](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/application/SetupInstallFailure.ts#L127-L133). ### Typed outcome or recovery expected Only a conclusive absence should produce `missing`/`launcher_missing`. Preserve launcher-inspection failures and their cause as an unresolved or failed inspection, while continuing to refuse an unverified launcher. ### Analysis or suspected cause The adapter catch covers existence, linker invocation and hashing in one block. It has no failure/unknown variant, so unrelated errors become the same missing status. Correct this producer rather than changing all launcher_missing remediation to guess at inspection errors. ### Additional context Existing provider-fingerprint and publisher-close reports concern different producers. A pending change to remove ldd alone does not repair the unreadable-hash case demonstrated here.
Author
Owner

@coygeek commented on GitHub (Oct 24, 2026):

Checked the reported fix on main at d87e73f2db.

The production launcher probe records its stage and returns missing only for ENOENT during initial access. Dependency or hash errors become inspection_failed with operation, path and systemCode; installLinuxHopper maps that to launcher_inspection rather than launcher_missing. Tests exercise a real inert existing launcher with missing ldd, a truly absent launcher control, and forwarding an EACCES hash diagnostic.

Implementation: LinuxHopper.ts, LinuxHopper.ts.

The relevant regression checks in LinuxHopper.test.ts passed in the focused test run on this revision. This closure is based on the current source and those checks; provider fixtures do not establish a real-engine run. Closing as completed on main.

<!-- gh-comment-id:6380746935 --> @coygeek commented on GitHub (Oct 24, 2026): Checked the reported fix on `main` at d87e73f2db50be72d33a4b3c7199af35518b441c. The production launcher probe records its stage and returns missing only for ENOENT during initial access. Dependency or hash errors become inspection_failed with operation, path and systemCode; installLinuxHopper maps that to launcher_inspection rather than launcher_missing. Tests exercise a real inert existing launcher with missing ldd, a truly absent launcher control, and forwarding an EACCES hash diagnostic. Implementation: [LinuxHopper.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/application/LinuxHopper.ts#L559), [LinuxHopper.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/application/LinuxHopper.ts#L580). The relevant regression checks in [LinuxHopper.test.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/application/LinuxHopper.test.ts) passed in the focused test run on this revision. This closure is based on the current source and those checks; provider fixtures do not establish a real-engine run. Closing as completed on main.
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#3595
No description provided.