[PR #4727] [CLOSED] fix(native): retain failed plist descriptor cleanup ownership #6374

Closed
opened 2026-10-31 01:13:58 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

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

Base: main ← Head: fix/native-plist-descriptor-ownership


📝 Commits (1)

  • bd62f5d fix(native): retain failed plist descriptor cleanup ownership

📊 Changes

2 files changed (+281 additions, -71 deletions)

View changed files

📝 src/native/NativeMacOSProvider.ts (+131 -71)
➕ tests/boundary/providers/native/nativePlistFileCleanup.test.ts (+150 -0)

📄 Description

Summary

Fixes #4723. Native inspect_plist now keeps a plist descriptor whose close fails owned. It reports that cleanup as incomplete beside the physical read outcome, and client close retries the descriptor.

Problem and expected behavior

plistSourceFormat (generic-text prefix probe) used raw open / read / finally close. requireRegularPlist (admission) used stat plus a bare open().close(). When the prefix read and the close both rejected, the close rejection replaced the read failure and became an ordinary ProviderAdapterError with cleanupIncomplete: false. Nothing retained the descriptor, so client close could not retry it. Admission had the same unowned close.

Expected: preserve the physical failure and the cleanup failure separately, retain the exact descriptor owner, retry it on client close, and clear the original error's cleanup uncertainty once release is confirmed.

Change and scope

  • The native client gets a #plistResources ArtifactResourceScope. Both plist file stages run through one withPlistFile helper: scope.run → withArtifactRegularFile(..., "follow"). This is the shared owned regular-file operation that retains a failed close as a lease.
  • plistFileFailure projects the physical primary via artifactFilePrimaryFailure. It keeps the existing missing-file, non-regular-file and permission-denied classifications, maps owner-recorded cancellation to AnalysisCancelledError with the exact reason, and attaches a ProviderCleanupError plus bindArtifactFileCleanup when cleanup is unconfirmed.
  • close() retries the plist scope through a shared closeResourceScope helper. The signature scope's existing close projection now uses the same helper, and that helper also binds retained leases.
  • Side effect of the shared admission: plist admission now opens with O_NONBLOCK, so a FIFO is rejected as non-regular instead of blocking. Symlinks are still followed, as before.
  • Not included: the separate source-freshness question between file/plutil and the probe, which the issue calls out as distinct.

Contribution scope: ordinary fix within an existing boundary; maintainer scope decision not applicable.

Contract and boundary impact

  • Semantic owner and earliest changed stage: native provider plist file admission and prefix probe (src/native/NativeMacOSProvider.ts).

  • Contribution scope fit and linked maintainer decision: not applicable (lifecycle fix).

  • CLI and MCP/tool-catalog contract: none; only the error classification under close failure changes (now cleanup_incomplete).

  • Provider, bridge, target-format, or platform compatibility: macOS native provider only; source-format results are unchanged (existing 14 source-format cases pass).

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

  • Target selection, authority, upstream behavior, and cleanup details: reads only; failed descriptor closes are retained and retried.

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

  • Read existing files or captured data.

Evidence and regression coverage

  • Tests added: tests/boundary/providers/native/nativePlistFileCleanup.test.ts. It uses real plist descriptors with injected read/close faults and the production NativeMacOSProvider client with NativeFixtureRunner captures. Cases: prefix read and close both reject; prefix read succeeds and close rejects; admission close rejects. Each one asserts cleanup_incomplete, keeps the read failure in the cause chain, checks that client close retries the same descriptor while it still fails, then releases it and asserts the original error's cleanupIncomplete becomes false.
  • Base reproduction: with src/native/NativeMacOSProvider.ts restored to origin/main (79d2b7189), all 3 new tests fail (expected false to be true on cleanupIncomplete). With the fix, all 3 pass.
  • Remaining proof gaps: no real file/plutil run and no naturally occurring OS close failure. Cancellation during the probe and concurrent client close are not covered by a dedicated test.

Validation performed

  • npm run test:focused -- tests/boundary/providers/native/nativePlistFileCleanup.test.ts tests/boundary/providers/native/plistSourceFormat.test.ts tests/boundary/providers/native/nativeProvider.test.ts: 3 files, 65 tests passed.
  • Same new test file against base source: 3 failed.
  • npx tsc --noEmit -p .: clean. oxlint: no new warnings in the touched code. oxfmt --check: clean. Pre-commit hooks passed.

Compatibility, safety, and release

  • Breaking changes or migration steps: none.
  • Real Hopper/Ghidra, browser, or OS coverage: macOS arm64 / Node 24 controlled boundary only.
  • Package or release metadata impact: none.
  • Security, privacy, process, or containment review: read-only; no new authority.

Author readiness

Human owner:

  • 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/4727 **Author:** [@kaoru0822-kitauji](https://github.com/kaoru0822-kitauji) **Created:** 10/17/2026 **Status:** ❌ Closed **Base:** `main` ← **Head:** `fix/native-plist-descriptor-ownership` --- ### 📝 Commits (1) - [`bd62f5d`](https://github.com/morluto/rea/commit/bd62f5d0c522f46356e11b0f5d0a48aaf97eaf3d) fix(native): retain failed plist descriptor cleanup ownership ### 📊 Changes **2 files changed** (+281 additions, -71 deletions) <details> <summary>View changed files</summary> 📝 `src/native/NativeMacOSProvider.ts` (+131 -71) ➕ `tests/boundary/providers/native/nativePlistFileCleanup.test.ts` (+150 -0) </details> ### 📄 Description ## Summary Fixes #4723. Native `inspect_plist` now keeps a plist descriptor whose close fails owned. It reports that cleanup as incomplete beside the physical read outcome, and client close retries the descriptor. ## Problem and expected behavior `plistSourceFormat` (generic-text prefix probe) used raw `open` / `read` / `finally close`. `requireRegularPlist` (admission) used `stat` plus a bare `open().close()`. When the prefix read and the close both rejected, the close rejection replaced the read failure and became an ordinary `ProviderAdapterError` with `cleanupIncomplete: false`. Nothing retained the descriptor, so client close could not retry it. Admission had the same unowned close. Expected: preserve the physical failure and the cleanup failure separately, retain the exact descriptor owner, retry it on client close, and clear the original error's cleanup uncertainty once release is confirmed. ## Change and scope - The native client gets a `#plistResources` `ArtifactResourceScope`. Both plist file stages run through one `withPlistFile` helper: `scope.run` → `withArtifactRegularFile(..., "follow")`. This is the shared owned regular-file operation that retains a failed close as a lease. - `plistFileFailure` projects the physical primary via `artifactFilePrimaryFailure`. It keeps the existing missing-file, non-regular-file and permission-denied classifications, maps owner-recorded cancellation to `AnalysisCancelledError` with the exact reason, and attaches a `ProviderCleanupError` plus `bindArtifactFileCleanup` when cleanup is unconfirmed. - `close()` retries the plist scope through a shared `closeResourceScope` helper. The signature scope's existing close projection now uses the same helper, and that helper also binds retained leases. - Side effect of the shared admission: plist admission now opens with `O_NONBLOCK`, so a FIFO is rejected as non-regular instead of blocking. Symlinks are still followed, as before. - Not included: the separate source-freshness question between `file`/`plutil` and the probe, which the issue calls out as distinct. Contribution scope: ordinary fix within an existing boundary; maintainer scope decision not applicable. ## Contract and boundary impact - Semantic owner and earliest changed stage: native provider plist file admission and prefix probe (`src/native/NativeMacOSProvider.ts`). - Contribution scope fit and linked maintainer decision: not applicable (lifecycle fix). - CLI and MCP/tool-catalog contract: none; only the error classification under close failure changes (now `cleanup_incomplete`). - Provider, bridge, target-format, or platform compatibility: macOS native provider only; source-format results are unchanged (existing 14 source-format cases pass). - Evidence, artifact, provenance, or reconstruction contract: none. - Target selection, authority, upstream behavior, and cleanup details: reads only; failed descriptor closes are retained and retried. - Generated metadata, package, or installation impact: none. - [x] Read existing files or captured data. ## Evidence and regression coverage - Tests added: `tests/boundary/providers/native/nativePlistFileCleanup.test.ts`. It uses real plist descriptors with injected read/close faults and the production `NativeMacOSProvider` client with `NativeFixtureRunner` captures. Cases: prefix read and close both reject; prefix read succeeds and close rejects; admission close rejects. Each one asserts `cleanup_incomplete`, keeps the read failure in the cause chain, checks that client close retries the same descriptor while it still fails, then releases it and asserts the original error's `cleanupIncomplete` becomes `false`. - Base reproduction: with `src/native/NativeMacOSProvider.ts` restored to `origin/main` (79d2b7189), all 3 new tests fail (`expected false to be true` on `cleanupIncomplete`). With the fix, all 3 pass. - Remaining proof gaps: no real `file`/`plutil` run and no naturally occurring OS close failure. Cancellation during the probe and concurrent client close are not covered by a dedicated test. ## Validation performed - `npm run test:focused -- tests/boundary/providers/native/nativePlistFileCleanup.test.ts tests/boundary/providers/native/plistSourceFormat.test.ts tests/boundary/providers/native/nativeProvider.test.ts`: 3 files, 65 tests passed. - Same new test file against base source: 3 failed. - `npx tsc --noEmit -p .`: clean. `oxlint`: no new warnings in the touched code. `oxfmt --check`: clean. Pre-commit hooks passed. ## Compatibility, safety, and release - Breaking changes or migration steps: none. - Real Hopper/Ghidra, browser, or OS coverage: macOS arm64 / Node 24 controlled boundary only. - Package or release metadata impact: none. - Security, privacy, process, or containment review: read-only; no new authority. ## Author readiness Human owner: <!-- @GitHub-handle --> - [ ] 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](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-10-31 01:13:58 +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#6374
No description provided.