[PR #5565] [CLOSED] fix(kuna): reject a dangling cache root link in doctor #6967

Closed
opened 2026-11-01 21:44:23 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/5565
Author: @kaoru0822-kitauji
Created: 10/20/2026
Status: ❌ Closed

Base: main ← Head: fix/kuna-doctor-dangling-cache


📝 Commits (2)

  • bd1d281 fix(kuna): reject a dangling cache root link in doctor
  • 13cc7a7 Merge origin/main into fix/kuna-doctor-dangling-cache

📊 Changes

2 files changed (+74 additions, -9 deletions)

View changed files

📝 src/kuna/KunaDoctor.ts (+30 -8)
📝 tests/boundary/providers/kuna/kunaDoctor.test.ts (+44 -1)

📄 Description

Summary

Fixes #5554. rea doctor --provider kuna reported the Kuna cache ready when REA_KUNA_CACHE_DIR was a dangling symbolic link under a writable parent. KunaProjectCache then fails its recursive mkdir with ENOENT on first export. Doctor now fails the selected cache check for that root, reports healthy: false, and exits 1.

Problem and expected behavior

cacheCheck followed the root with stat. It treated ENOENT as "absent, creatable" and checked only whether the parent was writable. A dangling link also returns ENOENT from stat, but its name is occupied, so mkdir -p <root>/projects cannot succeed. The doctor's diagnosis disagreed with what the cache owner can actually do.

Expected: a dangling root (or dangling ancestor) fails the check, without creating the cache or changing the link. An absent root under a writable parent, an existing writable directory, and a symlink resolving to a usable directory still pass.

Change and scope

  • nearestDirectoryWritable now starts at the root itself instead of its parent. It steps to a parent only when lstat confirms that nothing occupies the name. If an entry is there but cannot be resolved (a dangling link), the check fails with a reason naming it. An lstat failure other than ENOENT also fails rather than being treated as absent.
  • Symlinks that resolve are unchanged: stat still follows them.
  • Not included: any change to KunaProjectCache or to setup's handling of a failed cache check (#5105).

Ordinary fix within existing boundaries; maintainer scope decision: not applicable.

Contract and boundary impact

  • Semantic owner and earliest changed stage: src/kuna/KunaDoctor.ts cache readiness check.

  • Contribution scope fit and linked maintainer decision: bug fix within existing doctor behavior; not applicable.

  • CLI and MCP/tool-catalog contract: no schema change. The kuna-cache check now returns ok: false / open_options_invalid for a dangling root.

  • Provider, bridge, target-format, or platform compatibility: Kuna only. The test file is already skipped on Windows.

  • Evidence, artifact, provenance, or reconstruction contract: none.

  • Target selection, authority, upstream behavior, and cleanup details: doctor remains read-only (stat/lstat/access only).

  • Generated metadata, package, or installation impact: none.

  • Read existing files or captured data.

Evidence and regression coverage

  • Tests added or updated: tests/boundary/providers/kuna/kunaDoctor.test.ts adds "reports a dangling cache link as unusable and accepts a resolving link". It covers a dangling root, a path beneath a dangling link, and a resolving link, and asserts that the link and its missing target are unchanged.
  • Base reproduction: on unmodified origin/main source, the new test failed. It got ok: true with "REA creates the Kuna project cache at …/dangling on first use."
  • User-visible CLI output (issue's repro script against the fixed build; scratch paths redacted):
    cache-link exit 1 healthy False ok False | REA cannot create the Kuna cache root <scratch>/cache-link: <scratch>/cache-link is a symbolic link whose target does not exist.
    absent     exit 0 healthy True  ok True  | REA creates the Kuna project cache at <scratch>/absent on first use.
    existing   exit 0 healthy True  ok True  | Kuna project exports are cached in <scratch>/existing (at most 4294967296 bytes).
    occupied   exit 1 healthy False ok False | The Kuna cache root <scratch>/occupied exists but is not a directory.
    resolving  exit 0 healthy True  ok True  | Kuna project exports are cached in <scratch>/resolving (at most 4294967296 bytes).
    link target: <scratch>/missing-target target exists: False absent created: False
    
  • Remaining proof gaps: macOS only; no real Kuna, and no export run through KunaProjectCache.

Validation performed

  • npm run test:focused -- tests/boundary/providers/kuna/kunaDoctor.test.ts: 9 passed, 1 failed. The failure is "cancels and joins the composed Kuna doctor probe on caller cancellation" (expect.poll timeout waiting for the fake Kuna call). It also failed on the unmodified base in this environment, which was under heavy load (load average about 9). I did not investigate it further, and it is unrelated to this change.
  • Issue #5554's Python repro (extended with a resolving-symlink case) against the built CLI: output above.
  • npm run check:fast: passed (typecheck and lint).

Compatibility, safety, and release

  • Breaking changes or migration steps: none. Configurations with a dangling cache root now fail doctor instead of failing later at export.
  • Real Hopper/Ghidra, browser, or OS coverage: macOS arm64 with fake Kuna/Hopper executables only.
  • Package or release metadata impact: none.
  • Security, privacy, process, or containment review: read-only filesystem probes; no new writes.

Author readiness

Human owner: @kaoru0822-kitauji

  • I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work.
  • I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps.
  • I declare this PR ready for initial maintainer review.

Before merge

  • This PR addresses a concrete problem or an agreed enhancement.
  • Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff.
  • The PR has one focused outcome and the title follows type(scope): outcome.
  • Related issue is linked, or the reason for not linking one is stated above.
  • Tests cover changed observable behavior and meaningful failure paths.
  • Owning docs, contracts, and generated metadata are updated where needed.
  • User-visible CLI/MCP changes include representative output.
  • I checked the final diff for secrets, unrelated cleanup, and unsupported claims.

🤖 Generated with Claude Code


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/morluto/rea/pull/5565 **Author:** [@kaoru0822-kitauji](https://github.com/kaoru0822-kitauji) **Created:** 10/20/2026 **Status:** ❌ Closed **Base:** `main` ← **Head:** `fix/kuna-doctor-dangling-cache` --- ### 📝 Commits (2) - [`bd1d281`](https://github.com/morluto/rea/commit/bd1d28110778f203a15f10cf4870cfaa87e86b7d) fix(kuna): reject a dangling cache root link in doctor - [`13cc7a7`](https://github.com/morluto/rea/commit/13cc7a752b97da3d57518ab767960bb6912d23f1) Merge origin/main into fix/kuna-doctor-dangling-cache ### 📊 Changes **2 files changed** (+74 additions, -9 deletions) <details> <summary>View changed files</summary> 📝 `src/kuna/KunaDoctor.ts` (+30 -8) 📝 `tests/boundary/providers/kuna/kunaDoctor.test.ts` (+44 -1) </details> ### 📄 Description ## Summary Fixes #5554. `rea doctor --provider kuna` reported the Kuna cache ready when `REA_KUNA_CACHE_DIR` was a dangling symbolic link under a writable parent. `KunaProjectCache` then fails its recursive `mkdir` with `ENOENT` on first export. Doctor now fails the selected cache check for that root, reports `healthy: false`, and exits 1. ## Problem and expected behavior `cacheCheck` followed the root with `stat`. It treated `ENOENT` as "absent, creatable" and checked only whether the parent was writable. A dangling link also returns `ENOENT` from `stat`, but its name is occupied, so `mkdir -p <root>/projects` cannot succeed. The doctor's diagnosis disagreed with what the cache owner can actually do. Expected: a dangling root (or dangling ancestor) fails the check, without creating the cache or changing the link. An absent root under a writable parent, an existing writable directory, and a symlink resolving to a usable directory still pass. ## Change and scope - `nearestDirectoryWritable` now starts at the root itself instead of its parent. It steps to a parent only when `lstat` confirms that nothing occupies the name. If an entry is there but cannot be resolved (a dangling link), the check fails with a reason naming it. An `lstat` failure other than `ENOENT` also fails rather than being treated as absent. - Symlinks that resolve are unchanged: `stat` still follows them. - Not included: any change to `KunaProjectCache` or to setup's handling of a failed cache check (#5105). Ordinary fix within existing boundaries; maintainer scope decision: not applicable. ## Contract and boundary impact - Semantic owner and earliest changed stage: `src/kuna/KunaDoctor.ts` cache readiness check. - Contribution scope fit and linked maintainer decision: bug fix within existing doctor behavior; not applicable. - CLI and MCP/tool-catalog contract: no schema change. The `kuna-cache` check now returns `ok: false` / `open_options_invalid` for a dangling root. - Provider, bridge, target-format, or platform compatibility: Kuna only. The test file is already skipped on Windows. - Evidence, artifact, provenance, or reconstruction contract: none. - Target selection, authority, upstream behavior, and cleanup details: doctor remains read-only (`stat`/`lstat`/`access` only). - Generated metadata, package, or installation impact: none. - [x] Read existing files or captured data. ## Evidence and regression coverage - Tests added or updated: `tests/boundary/providers/kuna/kunaDoctor.test.ts` adds "reports a dangling cache link as unusable and accepts a resolving link". It covers a dangling root, a path beneath a dangling link, and a resolving link, and asserts that the link and its missing target are unchanged. - Base reproduction: on unmodified `origin/main` source, the new test failed. It got `ok: true` with "REA creates the Kuna project cache at …/dangling on first use." - User-visible CLI output (issue's repro script against the fixed build; scratch paths redacted): ``` cache-link exit 1 healthy False ok False | REA cannot create the Kuna cache root <scratch>/cache-link: <scratch>/cache-link is a symbolic link whose target does not exist. absent exit 0 healthy True ok True | REA creates the Kuna project cache at <scratch>/absent on first use. existing exit 0 healthy True ok True | Kuna project exports are cached in <scratch>/existing (at most 4294967296 bytes). occupied exit 1 healthy False ok False | The Kuna cache root <scratch>/occupied exists but is not a directory. resolving exit 0 healthy True ok True | Kuna project exports are cached in <scratch>/resolving (at most 4294967296 bytes). link target: <scratch>/missing-target target exists: False absent created: False ``` - Remaining proof gaps: macOS only; no real Kuna, and no export run through `KunaProjectCache`. ## Validation performed - `npm run test:focused -- tests/boundary/providers/kuna/kunaDoctor.test.ts`: 9 passed, 1 failed. The failure is "cancels and joins the composed Kuna doctor probe on caller cancellation" (`expect.poll` timeout waiting for the fake Kuna call). It also failed on the unmodified base in this environment, which was under heavy load (load average about 9). I did not investigate it further, and it is unrelated to this change. - Issue #5554's Python repro (extended with a resolving-symlink case) against the built CLI: output above. - `npm run check:fast`: passed (typecheck and lint). ## Compatibility, safety, and release - Breaking changes or migration steps: none. Configurations with a dangling cache root now fail doctor instead of failing later at export. - Real Hopper/Ghidra, browser, or OS coverage: macOS arm64 with fake Kuna/Hopper executables only. - Package or release metadata impact: none. - Security, privacy, process, or containment review: read-only filesystem probes; no new writes. ## Author readiness Human owner: @kaoru0822-kitauji - [x] I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work. - [x] I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps. - [x] I declare this PR ready for initial maintainer review. ## Before merge - [ ] This PR addresses a concrete problem or an agreed enhancement. - [ ] Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff. - [ ] The PR has one focused outcome and the title follows `type(scope): outcome`. - [ ] Related issue is linked, or the reason for not linking one is stated above. - [ ] Tests cover changed observable behavior and meaningful failure paths. - [ ] Owning docs, contracts, and generated metadata are updated where needed. - [ ] User-visible CLI/MCP changes include representative output. - [ ] I checked the final diff for secrets, unrelated cleanup, and unsupported claims. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-11-01 21:44:23 +00:00
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#6967
No description provided.