[GH-ISSUE #5554] [Bug] Doctor reports Kuna ready when its cache root is a dangling symlink #3383

Closed
opened 2026-10-31 00:39:46 +00:00 by bsakaguchi · 1 comment
Owner

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

Before submitting

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

Area

Provider or capability discovery

Observed behavior

With REA_KUNA_CACHE_DIR pointing to a dangling symbolic link under a writable parent, node scripts/rea.mjs doctor --provider kuna --json exits 0, reports healthy: true, and sets the kuna-cache scope check to ok: true. Its detail promises to create the cache on first use. A separate Node recursive mkdir of that root's projects child fails with ENOENT and exits 1.

An independent verifier reproduced these results using fake provider executables and an isolated environment. The overall environment_healthy value was false because unrelated informational checks failed; the incorrect success concerns explicitly selected Kuna readiness.

Reproduction

From the built source checkout with dependencies installed, the independent verifier ran this Python probe on macOS. It invokes only doctor and a separate filesystem probe, with no target, database, real provider, or network request. All fixtures and child HOME/XDG/PATH/TMPDIR values are confined to the generated scratch directory.

python3 - <<'PYTHON'
import json, os, pathlib, shutil, subprocess, tempfile
root = pathlib.Path(tempfile.mkdtemp(prefix="rea-cache-verifier-"))
for name in ("bin", "home", "config", "data", "cache", "state", "tmp", "output"):
    (root / name).mkdir()
(root / "bin/node").symlink_to(shutil.which("node"))
for name, body in (("kuna", 'printf "kuna 1.833\\n"'), ("hopper", "exit 0")):
    executable = root / "bin" / name
    executable.write_text("#!/bin/sh\n" + body + "\n")
    executable.chmod(0o700)
env = {"HOME": str(root / "home"), "PATH": str(root / "bin"), "TMPDIR": str(root / "tmp"),
       "REA_KUNA_COMMAND": str(root / "bin/kuna"), "HOPPER_LAUNCHER_PATH": str(root / "bin/hopper")}
for kind in ("CONFIG", "DATA", "CACHE", "STATE"):
    env["XDG_" + kind + "_HOME"] = str(root / kind.lower())
(root / "cache-link").symlink_to(root / "missing-target")
(root / "existing").mkdir()
(root / "occupied").write_text("not a directory")
results = []
for case in ("cache-link", "absent", "existing", "occupied"):
    env["REA_KUNA_CACHE_DIR"] = str(root / case)
    command = [str(root / "bin/node"), "scripts/rea.mjs", "doctor", "--provider", "kuna", "--json"]
    run = subprocess.run(command, env=env, capture_output=True, text=True, timeout=45)
    (root / "output" / (case + ".json")).write_text(run.stdout)
    (root / "output" / (case + ".stderr")).write_text(run.stderr)
    report = json.loads(run.stdout)
    check = next(c for c in report["scope_checks"] if c["name"] == "kuna-cache")
    row = {"case": case, "exit": run.returncode, "healthy": report["healthy"], "cache_ok": check["ok"]}
    row["created_by_doctor"] = (root / case).exists() if case == "absent" else None
    row["detail"] = check["detail"].replace(str(root), "<scratch>")
    if case != "occupied":
        probe = subprocess.run([str(root / "bin/node"), "-e", 'try {require("node:fs").mkdirSync(require("node:path").join(process.env.REA_KUNA_CACHE_DIR,"projects"),{recursive:true,mode:0o700}); console.log("success")}catch(e){console.log(e.code);process.exitCode=1}'], env=env, capture_output=True, text=True)
        row["mkdir"] = probe.stdout.strip()
        row["mkdir_exit"] = probe.returncode
    results.append(row)
(root / "output/results.json").write_text(json.dumps(results, indent=2))
print(json.dumps(results, indent=2))
print("Evidence directory:", root)
PYTHON

The dangling-link result also occurred in an initial run. The final isolated run exercised the absent-root, existing-directory, and occupied-file controls shown above. The filesystem probe is not a full analysis request.

Expected behavior

Doctor should identify the configured dangling root as unusable, fail its selected cache-readiness check, report healthy: false, and exit 1 without creating the cache or changing the link. A genuinely absent root under a writable parent and an existing writable directory should continue to pass. This does not require rejecting symlinks that resolve to usable directories.

The expectation follows from docs/kuna-provider.md:19,110-120, which describes REA-owned export cache storage and diagnosis without creation; docs/cli.md:514, which specifies exit 1 for failed selected readiness; and tests/boundary/providers/kuna/kunaDoctor.test.ts:97-170, which covers unusable/non-creatable roots and writable-directory success at the reported baseline.

Impact and scope

P2: callers receive a false readiness success for this bounded cache-path configuration, delaying the error until cache use. Choosing an existing writable directory is an observed workaround: both doctor and the separate mkdir passed for that control. Doctor left the absent-root control uncreated until the filesystem probe.

Source-derived downstream impact: KunaProjectCache creates the same child directories before an export and returns an export failure if creation fails. No export-dependent analysis request was executed, and inventory-only requests need not use this cache. Other operating systems, real-provider analysis, data loss, and production frequency were not tested.

REA version

6.7.0; built source commit 074768901f3cdc8205b61773fb1faf6f6c7b3ca8.

Runtime and platform

Node.js v26.12.0; macOS arm64. Fake Kuna prints kuna 1.833; a fake Hopper launcher prevents unrelated real-provider discovery. NOT_ENOUGH_INFO: the verifier did not record the exact macOS release.

Target and artifact context

No binary target or analysis database. Disposable fake provider scripts and four cache roots: a dangling symlink, an absent path, an existing writable directory, and an occupied regular file.

Interface

CLI

Earliest failure stage

Setup or provider admission

Relevant output or evidence

The independent verifier recorded these results from the built CLI and separate recursive mkdir, with generated paths replaced by <scratch>:

cache-link: doctor exit=0; healthy=true; kuna-cache.ok=true
  detail: REA creates the Kuna project cache at <scratch>/cache-link on first use.
  recursive mkdir of projects: ENOENT; exit=1; link target remains missing
absent: doctor exit=0; healthy=true; kuna-cache.ok=true
  root still absent after doctor; recursive mkdir succeeds; exit=0
existing: doctor exit=0; healthy=true; kuna-cache.ok=true
  recursive mkdir succeeds; exit=0
occupied: doctor exit=1; healthy=false; kuna-cache.ok=false
  detail identifies the root as not a directory

Upstream source re-check at eac296a7da3356f1c5342155d635975ddd89cba6: changes since the tested baseline in KunaDoctor.ts add cancellation/diagnostic handling, leaving the cache check and ancestor logic unchanged. KunaProjectCache.ts is unchanged. Current-main applicability is source-inspected; runtime reproduction was at the baseline above.

Typed outcome or recovery expected

Return a failing selected cache-readiness result identifying the unusable configured root, healthy: false, and exit 1. Preserve diagnosis without cache creation and the passing absent/writable-directory controls.

Analysis or suspected cause

Source-derived: src/kuna/KunaDoctor.ts:157-182 follows the cache-root symlink with stat and treats ENOENT as a potentially creatable root. Its ancestor search at :216-239 starts at the parent, so a writable parent yields success despite the conflicting link. src/kuna/KunaProjectCache.ts:725-743 recursively creates cache children before export, matching the filesystem operation that failed in the probe.

Additional context

Prepared with AI assistance and independently reproduced. Existing #5105 concerns setup advertising Kuna after its cache check correctly fails; this report concerns the cache check itself incorrectly succeeding for a dangling root. Cancellation work in #5241 does not change that cache logic. NOT_ENOUGH_INFO: no last known good version or regression range was established.

Originally created by @coygeek on GitHub (Oct 20, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/5554 ### 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 Provider or capability discovery ### Observed behavior With `REA_KUNA_CACHE_DIR` pointing to a dangling symbolic link under a writable parent, `node scripts/rea.mjs doctor --provider kuna --json` exits 0, reports `healthy: true`, and sets the `kuna-cache` scope check to `ok: true`. Its detail promises to create the cache on first use. A separate Node recursive mkdir of that root's `projects` child fails with `ENOENT` and exits 1. An independent verifier reproduced these results using fake provider executables and an isolated environment. The overall `environment_healthy` value was false because unrelated informational checks failed; the incorrect success concerns explicitly selected Kuna readiness. ### Reproduction From the built source checkout with dependencies installed, the independent verifier ran this Python probe on macOS. It invokes only doctor and a separate filesystem probe, with no target, database, real provider, or network request. All fixtures and child HOME/XDG/PATH/TMPDIR values are confined to the generated scratch directory. ```sh python3 - <<'PYTHON' import json, os, pathlib, shutil, subprocess, tempfile root = pathlib.Path(tempfile.mkdtemp(prefix="rea-cache-verifier-")) for name in ("bin", "home", "config", "data", "cache", "state", "tmp", "output"): (root / name).mkdir() (root / "bin/node").symlink_to(shutil.which("node")) for name, body in (("kuna", 'printf "kuna 1.833\\n"'), ("hopper", "exit 0")): executable = root / "bin" / name executable.write_text("#!/bin/sh\n" + body + "\n") executable.chmod(0o700) env = {"HOME": str(root / "home"), "PATH": str(root / "bin"), "TMPDIR": str(root / "tmp"), "REA_KUNA_COMMAND": str(root / "bin/kuna"), "HOPPER_LAUNCHER_PATH": str(root / "bin/hopper")} for kind in ("CONFIG", "DATA", "CACHE", "STATE"): env["XDG_" + kind + "_HOME"] = str(root / kind.lower()) (root / "cache-link").symlink_to(root / "missing-target") (root / "existing").mkdir() (root / "occupied").write_text("not a directory") results = [] for case in ("cache-link", "absent", "existing", "occupied"): env["REA_KUNA_CACHE_DIR"] = str(root / case) command = [str(root / "bin/node"), "scripts/rea.mjs", "doctor", "--provider", "kuna", "--json"] run = subprocess.run(command, env=env, capture_output=True, text=True, timeout=45) (root / "output" / (case + ".json")).write_text(run.stdout) (root / "output" / (case + ".stderr")).write_text(run.stderr) report = json.loads(run.stdout) check = next(c for c in report["scope_checks"] if c["name"] == "kuna-cache") row = {"case": case, "exit": run.returncode, "healthy": report["healthy"], "cache_ok": check["ok"]} row["created_by_doctor"] = (root / case).exists() if case == "absent" else None row["detail"] = check["detail"].replace(str(root), "<scratch>") if case != "occupied": probe = subprocess.run([str(root / "bin/node"), "-e", 'try {require("node:fs").mkdirSync(require("node:path").join(process.env.REA_KUNA_CACHE_DIR,"projects"),{recursive:true,mode:0o700}); console.log("success")}catch(e){console.log(e.code);process.exitCode=1}'], env=env, capture_output=True, text=True) row["mkdir"] = probe.stdout.strip() row["mkdir_exit"] = probe.returncode results.append(row) (root / "output/results.json").write_text(json.dumps(results, indent=2)) print(json.dumps(results, indent=2)) print("Evidence directory:", root) PYTHON ``` The dangling-link result also occurred in an initial run. The final isolated run exercised the absent-root, existing-directory, and occupied-file controls shown above. The filesystem probe is not a full analysis request. ### Expected behavior Doctor should identify the configured dangling root as unusable, fail its selected cache-readiness check, report `healthy: false`, and exit 1 without creating the cache or changing the link. A genuinely absent root under a writable parent and an existing writable directory should continue to pass. This does not require rejecting symlinks that resolve to usable directories. The expectation follows from `docs/kuna-provider.md:19,110-120`, which describes REA-owned export cache storage and diagnosis without creation; `docs/cli.md:514`, which specifies exit 1 for failed selected readiness; and `tests/boundary/providers/kuna/kunaDoctor.test.ts:97-170`, which covers unusable/non-creatable roots and writable-directory success at the reported baseline. ### Impact and scope P2: callers receive a false readiness success for this bounded cache-path configuration, delaying the error until cache use. Choosing an existing writable directory is an observed workaround: both doctor and the separate mkdir passed for that control. Doctor left the absent-root control uncreated until the filesystem probe. Source-derived downstream impact: `KunaProjectCache` creates the same child directories before an export and returns an export failure if creation fails. No export-dependent analysis request was executed, and inventory-only requests need not use this cache. Other operating systems, real-provider analysis, data loss, and production frequency were not tested. ### REA version 6.7.0; built source commit `074768901f3cdc8205b61773fb1faf6f6c7b3ca8`. ### Runtime and platform Node.js v26.12.0; macOS arm64. Fake Kuna prints `kuna 1.833`; a fake Hopper launcher prevents unrelated real-provider discovery. NOT_ENOUGH_INFO: the verifier did not record the exact macOS release. ### Target and artifact context No binary target or analysis database. Disposable fake provider scripts and four cache roots: a dangling symlink, an absent path, an existing writable directory, and an occupied regular file. ### Interface CLI ### Earliest failure stage Setup or provider admission ### Relevant output or evidence The independent verifier recorded these results from the built CLI and separate recursive mkdir, with generated paths replaced by `<scratch>`: ```text cache-link: doctor exit=0; healthy=true; kuna-cache.ok=true detail: REA creates the Kuna project cache at <scratch>/cache-link on first use. recursive mkdir of projects: ENOENT; exit=1; link target remains missing absent: doctor exit=0; healthy=true; kuna-cache.ok=true root still absent after doctor; recursive mkdir succeeds; exit=0 existing: doctor exit=0; healthy=true; kuna-cache.ok=true recursive mkdir succeeds; exit=0 occupied: doctor exit=1; healthy=false; kuna-cache.ok=false detail identifies the root as not a directory ``` Upstream source re-check at `eac296a7da3356f1c5342155d635975ddd89cba6`: changes since the tested baseline in `KunaDoctor.ts` add cancellation/diagnostic handling, leaving the cache check and ancestor logic unchanged. `KunaProjectCache.ts` is unchanged. Current-main applicability is source-inspected; runtime reproduction was at the baseline above. ### Typed outcome or recovery expected Return a failing selected cache-readiness result identifying the unusable configured root, `healthy: false`, and exit 1. Preserve diagnosis without cache creation and the passing absent/writable-directory controls. ### Analysis or suspected cause Source-derived: `src/kuna/KunaDoctor.ts:157-182` follows the cache-root symlink with `stat` and treats `ENOENT` as a potentially creatable root. Its ancestor search at `:216-239` starts at the parent, so a writable parent yields success despite the conflicting link. `src/kuna/KunaProjectCache.ts:725-743` recursively creates cache children before export, matching the filesystem operation that failed in the probe. ### Additional context Prepared with AI assistance and independently reproduced. Existing #5105 concerns setup advertising Kuna after its cache check correctly fails; this report concerns the cache check itself incorrectly succeeding for a dangling root. Cancellation work in #5241 does not change that cache logic. NOT_ENOUGH_INFO: no last known good version or regression range was established.
Author
Owner

@matbrgz commented on GitHub (Oct 20, 2026):

Cross-referencing with canonical issue #5105 ([Bug] Setup advertises Kuna for deep analysis after its cache check fails) and sibling #4624. Addresses Kuna provider readiness, cache directory validation, and dangling symlink checks during rea doctor. Cataloged under Cluster BD in consolidation tracker #4330.

<!-- gh-comment-id:6295547340 --> @matbrgz commented on GitHub (Oct 20, 2026): Cross-referencing with canonical issue #5105 (`[Bug] Setup advertises Kuna for deep analysis after its cache check fails`) and sibling #4624. Addresses Kuna provider readiness, cache directory validation, and dangling symlink checks during `rea doctor`. Cataloged under Cluster BD in consolidation tracker #4330.
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#3383
No description provided.