[GH-ISSUE #7344] [Bug] Purge deletes a nested caller-selected Kuna cache after reporting it retained #4364

Open
opened 2026-10-31 00:49:14 +00:00 by bsakaguchi · 0 comments
Owner

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

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

rea uninstall --purge-data --json reports kuna-custom-cache: retained for a caller-selected directory below the default Kuna cache, then removes that directory with its default-cache ancestor. The public CLI exits 0 with status: "complete", but the selected directory's sentinel file is gone. Selecting a directory outside the purge tree preserves the sentinel; omitting --purge-data also preserves the nested sentinel.

Reproduction

Run from the baseline source checkout with its compiled runtime already available and Node.js 24.22.1 on PATH. This uses only disposable synthetic files, a fresh working directory and an explicit child-environment whitelist. It does not install anything or use an analysis provider. The path fixture is for macOS.

python3 - <<'PY'
import json, pathlib, shutil, subprocess, tempfile
repo = pathlib.Path.cwd()
node = shutil.which('node')
assert node is not None
with tempfile.TemporaryDirectory(prefix='rea-nested-purge-') as base:
    for name, nested, purge in [
        ('nested-purge', True, True),
        ('outside-purge', False, True),
        ('nested-no-purge', True, False),
    ]:
        root = pathlib.Path(base) / name
        home = root / 'home'
        default = home / 'Library/Caches/rea/kuna'
        custom = default / 'custom' if nested else home / 'custom-kuna'
        sibling = home / 'Library/Caches/rea/other'
        workspace, temp = root / 'workspace', root / 'temp'
        for directory in [default, custom, sibling, workspace, temp]:
            directory.mkdir(parents=True, exist_ok=True)
        selected, managed, other = custom/'keep.txt', default/'owned.txt', sibling/'keep.txt'
        selected.write_text('selected cache sentinel\n')
        managed.write_text('default cache sentinel\n')
        other.write_text('outside purge sentinel\n')
        env = {
            'PATH': '/usr/bin:/bin',
            'HOME': str(home), 'TMPDIR': str(temp),
            'XDG_CONFIG_HOME': str(home/'.config'),
            'XDG_CACHE_HOME': str(home/'.cache'),
            'REA_KUNA_CACHE_DIR': str(custom),
            'GEMINI_CLI_SYSTEM_SETTINGS_PATH': str(home/'gemini-system/settings.json'),
            'GEMINI_CLI_SYSTEM_DEFAULTS_PATH': str(home/'gemini-system/defaults.json'),
        }
        args = [node, str(repo/'scripts/rea.mjs'), 'uninstall']
        if purge:
            args.append('--purge-data')
        run = subprocess.run(args + ['--json'], cwd=workspace, env=env,
                             capture_output=True, text=True, timeout=30)
        data = json.loads(run.stdout)
        print(json.dumps({
            'case': name, 'exit': run.returncode, 'status': data['status'],
            'items': [{k: item[k] for k in ['name', 'status']}
                      for item in data['items']
                      if item['name'] in ['kuna-custom-cache', 'kuna']],
            'selected_exists': selected.exists(),
            'selected_unchanged': selected.exists() and selected.read_text() == 'selected cache sentinel\n',
            'managed_exists': managed.exists(),
            'sibling_unchanged': other.read_text() == 'outside purge sentinel\n',
            'stderr': run.stderr,
        }))
PY

Expected behavior

docs/installation.md:433-439 says caller-selected REA_KUNA_CACHE_DIR directories are retained and reported because REA cannot establish ownership of their entire contents. Here custom is a genuinely different directory, rather than another spelling of the default. Its sentinel bytes should survive purge and the result should accurately describe retained data. Removing only the retained message while still deleting the selected directory would not restore this preservation contract.

Impact and scope

Observed impact is deletion of one synthetic selected-cache sentinel despite an explicit retention result. No real user data was used, and irrecoverable loss of real data was not established. Reach is limited to explicit purge with a selected cache nested below a removed ancestor. The macOS default-cache case was executed; other purge ancestors and Linux were not runtime-tested. Population frequency and typical cache contents are unknown. Both selecting a cache outside the purge tree and omitting --purge-data preserved the selected sentinel in the controls.

REA version

rea-agents 6.8.0, source revision dbe2c97af3. The supplied successful build receipt identifies the already compiled runtime used; no build was run during verification.

Runtime and platform

Node.js v24.22.1; macOS 27.0.1; arm64. Python 3.15.1 ran the fixture driver. No Kuna, Hopper or Ghidra process was used.

Target and artifact context

Synthetic ordinary directories and text sentinels in disposable homes. No binary or application target, real cache, client configuration, or real installation was operated on.

Interface

CLI

Earliest failure stage

Cleanup, cancellation, or lifecycle

Relevant output or evidence

The independent CLI probe and a second execution of the exact inline reproduction produced the same result summary: two nested failures and two successful executions of each control. Each case used a separate disposable home; stderr was empty throughout.

Case Exit / status Selected sentinel Default sentinel Sibling sentinel Custom-cache item
Nested, purge 0 / complete Deleted Deleted Unchanged retained
Outside default, purge 0 / complete Unchanged Deleted Unchanged retained
Nested, no purge 0 / complete Unchanged Unchanged Unchanged Absent

Relevant nested-purge items, with fixture paths expressed relative to the disposable HOME:

{"name":"kuna-custom-cache","status":"retained","detail":"Caller-selected Kuna cache $HOME/Library/Caches/rea/kuna/custom was retained because purge does not establish ownership of that entire directory."}
{"name":"kuna","status":"removed","detail":"Removed $HOME/Library/Caches/rea/kuna."}

Typed outcome or recovery expected

The selected custom directory must remain intact when retention is reported. The containing default-cache outcome must accurately state any cleanup limitation required to preserve that selected directory.

Analysis or suspected cause

Source-derived explanation: src/application/Uninstall.ts:1054-1064 adds a retained custom-cache item when its path string differs from the default, but lines 1071-1075 still purge the default ancestor. purgeManagedData and removeManagedPath at lines 1080-1143 check the managed parent and leaf for symlinks, then recursively remove the default directory. The synthetic fixture contains only ordinary directories, so these guards admit removal of the entire ancestor, including the retained descendant. src/config/kunaCacheDirectory.ts:5-20 supplies the platform default.

Additional context

  • Expected source: docs/installation.md:433-439; tests/boundary/filesystem/managedDataPurge.test.ts separately asserts retention of an arbitrary custom cache. The nested path meets the documented absolute-directory requirement in docs/kuna-provider.md:27.
  • Failure source: independent public CLI execution and sentinel readback shown above. The public route is scripts/rea.mjs to src/cli/setupCommands.ts:157-181, then runUninstall and its production host in src/application/Uninstall.ts. The empty fake home has no failed client/skill preflight; the supported purge flag reaches the real recursive removal path. No installed provider is required by this route.
  • Evidence provenance: observed CLI results and filesystem checks; causal explanation is separately source-derived. The earlier hunter report was treated as supplied evidence and independently replayed.
  • Local verification: reproduced. No source, tests, documentation, dependencies or build artifacts were changed. No installation, build, full suite, network API or real provider operation was performed.
  • Reproduction completeness: complete for the macOS synthetic case and both controls. The fixture was reduced to cache directories and sentinels without client configuration or a legacy-cache prerequisite.
  • Restoration check: proposed, unexecuted. Repeat the nested purge and preserve the selected sentinel bytes with truthful retained reporting. Keep the outside-custom and no-purge controls passing, and retain ordinary default-cache cleanup where no custom descendant is selected. No fix was attempted.

Related history: open PR #7207 addresses equivalent spellings of the default directory by suppressing false retention while still removing that owned directory. This case selects a different child directory that must survive; the PR's stated identity comparison does not prevent removal through an ancestor. Closed issue #6531 covers omitted current cache roots; open issue #7320 covers malformed shared-configuration documentation; closed issue #1816 and its closed fix #1821 cover symlinked legacy roots. None has the same preservation check for a regular nested custom directory. The complete saved history, final live open/closed issue and pull-request searches, related current discussions, and recent new reports contained no exact duplicate of the nested-directory preservation failure.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: Explicit purge deletes a selected-cache sentinel while reporting retention and successful completion. This is consequential cleanup behavior bounded to an overlapping directory selection; severe real-data loss and affected-user frequency were not established.
  • Workaround status: available
  • Workaround basis: The independently executed no-purge control preserved the same nested sentinel. The outside-custom control also preserved its selected sentinel while removing the default cache.
Originally created by @coygeek on GitHub (Oct 27, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/7344 ## 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 `rea uninstall --purge-data --json` reports `kuna-custom-cache: retained` for a caller-selected directory below the default Kuna cache, then removes that directory with its default-cache ancestor. The public CLI exits 0 with `status: "complete"`, but the selected directory's sentinel file is gone. Selecting a directory outside the purge tree preserves the sentinel; omitting `--purge-data` also preserves the nested sentinel. ## Reproduction Run from the baseline source checkout with its compiled runtime already available and Node.js 24.22.1 on PATH. This uses only disposable synthetic files, a fresh working directory and an explicit child-environment whitelist. It does not install anything or use an analysis provider. The path fixture is for macOS. ```sh python3 - <<'PY' import json, pathlib, shutil, subprocess, tempfile repo = pathlib.Path.cwd() node = shutil.which('node') assert node is not None with tempfile.TemporaryDirectory(prefix='rea-nested-purge-') as base: for name, nested, purge in [ ('nested-purge', True, True), ('outside-purge', False, True), ('nested-no-purge', True, False), ]: root = pathlib.Path(base) / name home = root / 'home' default = home / 'Library/Caches/rea/kuna' custom = default / 'custom' if nested else home / 'custom-kuna' sibling = home / 'Library/Caches/rea/other' workspace, temp = root / 'workspace', root / 'temp' for directory in [default, custom, sibling, workspace, temp]: directory.mkdir(parents=True, exist_ok=True) selected, managed, other = custom/'keep.txt', default/'owned.txt', sibling/'keep.txt' selected.write_text('selected cache sentinel\n') managed.write_text('default cache sentinel\n') other.write_text('outside purge sentinel\n') env = { 'PATH': '/usr/bin:/bin', 'HOME': str(home), 'TMPDIR': str(temp), 'XDG_CONFIG_HOME': str(home/'.config'), 'XDG_CACHE_HOME': str(home/'.cache'), 'REA_KUNA_CACHE_DIR': str(custom), 'GEMINI_CLI_SYSTEM_SETTINGS_PATH': str(home/'gemini-system/settings.json'), 'GEMINI_CLI_SYSTEM_DEFAULTS_PATH': str(home/'gemini-system/defaults.json'), } args = [node, str(repo/'scripts/rea.mjs'), 'uninstall'] if purge: args.append('--purge-data') run = subprocess.run(args + ['--json'], cwd=workspace, env=env, capture_output=True, text=True, timeout=30) data = json.loads(run.stdout) print(json.dumps({ 'case': name, 'exit': run.returncode, 'status': data['status'], 'items': [{k: item[k] for k in ['name', 'status']} for item in data['items'] if item['name'] in ['kuna-custom-cache', 'kuna']], 'selected_exists': selected.exists(), 'selected_unchanged': selected.exists() and selected.read_text() == 'selected cache sentinel\n', 'managed_exists': managed.exists(), 'sibling_unchanged': other.read_text() == 'outside purge sentinel\n', 'stderr': run.stderr, })) PY ``` ## Expected behavior `docs/installation.md:433-439` says caller-selected `REA_KUNA_CACHE_DIR` directories are retained and reported because REA cannot establish ownership of their entire contents. Here `custom` is a genuinely different directory, rather than another spelling of the default. Its sentinel bytes should survive purge and the result should accurately describe retained data. Removing only the retained message while still deleting the selected directory would not restore this preservation contract. ## Impact and scope Observed impact is deletion of one synthetic selected-cache sentinel despite an explicit retention result. No real user data was used, and irrecoverable loss of real data was not established. Reach is limited to explicit purge with a selected cache nested below a removed ancestor. The macOS default-cache case was executed; other purge ancestors and Linux were not runtime-tested. Population frequency and typical cache contents are unknown. Both selecting a cache outside the purge tree and omitting `--purge-data` preserved the selected sentinel in the controls. ## REA version rea-agents 6.8.0, source revision dbe2c97af34361b11b9504e37611dd47715b52cb. The supplied successful build receipt identifies the already compiled runtime used; no build was run during verification. ## Runtime and platform Node.js v24.22.1; macOS 27.0.1; arm64. Python 3.15.1 ran the fixture driver. No Kuna, Hopper or Ghidra process was used. ## Target and artifact context Synthetic ordinary directories and text sentinels in disposable homes. No binary or application target, real cache, client configuration, or real installation was operated on. ## Interface CLI ## Earliest failure stage Cleanup, cancellation, or lifecycle ## Relevant output or evidence The independent CLI probe and a second execution of the exact inline reproduction produced the same result summary: two nested failures and two successful executions of each control. Each case used a separate disposable home; stderr was empty throughout. | Case | Exit / status | Selected sentinel | Default sentinel | Sibling sentinel | Custom-cache item | | --- | --- | --- | --- | --- | --- | | Nested, purge | 0 / complete | Deleted | Deleted | Unchanged | retained | | Outside default, purge | 0 / complete | Unchanged | Deleted | Unchanged | retained | | Nested, no purge | 0 / complete | Unchanged | Unchanged | Unchanged | Absent | Relevant nested-purge items, with fixture paths expressed relative to the disposable HOME: ```json {"name":"kuna-custom-cache","status":"retained","detail":"Caller-selected Kuna cache $HOME/Library/Caches/rea/kuna/custom was retained because purge does not establish ownership of that entire directory."} {"name":"kuna","status":"removed","detail":"Removed $HOME/Library/Caches/rea/kuna."} ``` ## Typed outcome or recovery expected The selected custom directory must remain intact when retention is reported. The containing default-cache outcome must accurately state any cleanup limitation required to preserve that selected directory. ## Analysis or suspected cause Source-derived explanation: `src/application/Uninstall.ts:1054-1064` adds a retained custom-cache item when its path string differs from the default, but lines 1071-1075 still purge the default ancestor. `purgeManagedData` and `removeManagedPath` at lines 1080-1143 check the managed parent and leaf for symlinks, then recursively remove the default directory. The synthetic fixture contains only ordinary directories, so these guards admit removal of the entire ancestor, including the retained descendant. `src/config/kunaCacheDirectory.ts:5-20` supplies the platform default. ## Additional context - Expected source: `docs/installation.md:433-439`; `tests/boundary/filesystem/managedDataPurge.test.ts` separately asserts retention of an arbitrary custom cache. The nested path meets the documented absolute-directory requirement in `docs/kuna-provider.md:27`. - Failure source: independent public CLI execution and sentinel readback shown above. The public route is `scripts/rea.mjs` to `src/cli/setupCommands.ts:157-181`, then `runUninstall` and its production host in `src/application/Uninstall.ts`. The empty fake home has no failed client/skill preflight; the supported purge flag reaches the real recursive removal path. No installed provider is required by this route. - Evidence provenance: observed CLI results and filesystem checks; causal explanation is separately source-derived. The earlier hunter report was treated as supplied evidence and independently replayed. - Local verification: reproduced. No source, tests, documentation, dependencies or build artifacts were changed. No installation, build, full suite, network API or real provider operation was performed. - Reproduction completeness: complete for the macOS synthetic case and both controls. The fixture was reduced to cache directories and sentinels without client configuration or a legacy-cache prerequisite. - Restoration check: proposed, unexecuted. Repeat the nested purge and preserve the selected sentinel bytes with truthful retained reporting. Keep the outside-custom and no-purge controls passing, and retain ordinary default-cache cleanup where no custom descendant is selected. No fix was attempted. Related history: [open PR #7207](https://github.com/morluto/rea/pull/7207) addresses equivalent spellings of the default directory by suppressing false retention while still removing that owned directory. This case selects a different child directory that must survive; the PR's stated identity comparison does not prevent removal through an ancestor. [Closed issue #6531](https://github.com/morluto/rea/issues/6531) covers omitted current cache roots; [open issue #7320](https://github.com/morluto/rea/issues/7320) covers malformed shared-configuration documentation; [closed issue #1816](https://github.com/morluto/rea/issues/1816) and its [closed fix #1821](https://github.com/morluto/rea/pull/1821) cover symlinked legacy roots. None has the same preservation check for a regular nested custom directory. The complete saved history, final live open/closed issue and pull-request searches, related current discussions, and recent new reports contained no exact duplicate of the nested-directory preservation failure. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: Explicit purge deletes a selected-cache sentinel while reporting retention and successful completion. This is consequential cleanup behavior bounded to an overlapping directory selection; severe real-data loss and affected-user frequency were not established. - Workaround status: available - Workaround basis: The independently executed no-purge control preserved the same nested sentinel. The outside-custom control also preserved its selected sentinel while removing the default cache.
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#4364
No description provided.