[GH-ISSUE #2664] fix(uninstall): ZCode fallback retains REA after native registration removal #964

Closed
opened 2026-10-15 18:29:57 +00:00 by bsakaguchi · 0 comments
Owner

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

Summary

Uninstall can report completion while leaving an REA-owned ZCode registration in the user-scope fallback. Removing the last native server makes that fallback eligible to load again under the documented ZCode configuration rules. The source uninstall workflow reproduced this with valid native and fallback configurations.

Steps to reproduce

From the source checkout identified below, save this test as src/application/fallback-cleanup.test.ts and run npm run test:local -- src/application/fallback-cleanup.test.ts. It uses a disposable home and an explicit environment, runs the production uninstall filesystem host, and never starts either configured command. The independently executed focused test exercised this same direct native/fallback input, plus a setup-created equivalent and a fallback-only case.

import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { expect, it } from "vitest";
import { supportedClients } from "./SupportedClients.js";
import { runUninstall, systemUninstallHost } from "./Uninstall.js";

it("reports native removal while leaving the fallback registration", async () => {
  const home = await mkdtemp(join(tmpdir(), "rea-fallback-"));
  const environment = { HOME: home, USERPROFILE: home };
  try {
    const client = supportedClients(home, "darwin", environment)
      .find(value => value.name === "zcode")!;
    const registration = {
      command: "npx", args: ["-y", "rea-agents@6.4.0", "mcp"],
    };
    await mkdir(dirname(client.configPath), { recursive: true });
    await mkdir(dirname(client.zcodeFallbackPath!), { recursive: true });
    await writeFile(client.configPath,
      JSON.stringify({ mcp: { servers: { rea: registration } } }));
    const originalFallback = JSON.stringify({ mcpServers: { rea: registration } });
    await writeFile(client.zcodeFallbackPath!, originalFallback);
    const result = await runUninstall(false,
      systemUninstallHost(home, undefined, environment, "darwin"));
    const native = JSON.parse(await readFile(client.configPath, "utf8"));
    const fallback = await readFile(client.zcodeFallbackPath!, "utf8");
    expect(result.status).toBe("complete");
    expect(result.items.find(value => value.name === "zcode")?.status)
      .toBe("removed");
    expect(native.mcp.servers).toEqual({});
    expect(fallback).toBe(originalFallback);
    console.log(JSON.stringify({ status: result.status, zcode: "removed",
      nativeServerCount: Object.keys(native.mcp.servers).length,
      fallbackReaRetained: Boolean(JSON.parse(fallback).mcpServers.rea) }));
  } finally {
    await rm(home, { recursive: true, force: true });
  }
});

The test records the current failure rather than asserting that cleanup has been restored. Its temporary root construction is portable; the executed verifier used a fixed owned fixture root. Public CLI reachability was traced from the uninstall handler to these production functions, but the packaged CLI was not executed.

Expected behavior

After successful uninstall, the REA-owned registration should be absent from both supported ZCode registration locations. An empty native table must not expose a leftover owned fallback entry. Unrelated fallback entries and settings must remain intact.

Actual behavior

The production uninstall workflow returned status: complete and marked ZCode removed. The native table contained zero servers, but the fallback remained byte-for-byte unchanged with its REA registration. This result occurred in both the direct fixture above and an equivalent configuration created by the production setup writer. A reduced fallback-only case also returned complete, marked ZCode skipped, and left the same owned entry intact.

{"status":"complete","zcode":"removed","nativeServerCount":0,"fallbackReaRetained":true}

The production doctor registration reader reported ZCode missing afterward in all three fixture cases. Under the documented fallback selection rule, the retained entry is eligible to load when the native table is empty. Actual ZCode startup and connection were not tested, and no released-package CLI exit status was measured.

Evidence

  • Expected source: docs/installation.md:156 describes removal of REA-owned registrations; docs/client-configuration.md:183-201 documents the native/fallback selection rule and setup preserving the fallback.
  • Failure source: a focused source test invoking runUninstall(false, systemUninstallHost(...)) produced the output above and confirmed unchanged fallback bytes. Production workflow locator: src/application/Uninstall.ts:209-218; public CLI entry point: src/cli/setupCommands.ts:143-166.
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: complete

The npx command in the fixture passed the production ownership check.

Related: #190 concerns npx registration ownership matching, and #2187 concerns Claude Code project-scope registration discovery and removal. Neither covers this owned ZCode fallback cleanup case.

Restoration check

With the same owned native and fallback entries, successful uninstall must leave neither location with the REA-owned key. Also check the setup-created and fallback-only cases. Preserve unrelated fallback servers and settings, including a foreign command stored under the name rea; deleting the whole fallback file would not restore the documented preservation behavior. The current direct fixture fails the cleanup condition even though uninstall reports completion. Changing doctor output alone does not remove the leftover registration.

Runtime or environment

Verified source revision a2d8b94dd50dab8d966239dd63ab84718aa8b4b4, whose package manifest is 6.4.0, using Node 24.18.0 and npm 11.16.0. The source test used the Darwin client-path branch with owned filesystem fixtures. The latest published package was identified as rea-agents 6.4.0, but its runtime was not tested. Current upstream recheck at 3ae826ceba6f25b08bce50bcb51001209bc16b57 reproduced all three source-workflow cases with the same Node and npm versions; the packaged CLI and actual ZCode startup and connection remain untested.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: A supported configuration state leaves an owned, loadable registration after uninstall reports completion. The failure is bounded to hosts with this fallback entry; no loss of unrelated data was observed.
  • Workaround status: available
  • Workaround basis: Remove only the owned rea key from the fallback's mcpServers object before restarting the client. This key-removal operation was checked on the disposable fixtures and preserved an unrelated server and an unrelated root setting. Do not clear the entire fallback file.
Originally created by @coygeek on GitHub (Oct 13, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/2664 ## Summary Uninstall can report completion while leaving an REA-owned ZCode registration in the user-scope fallback. Removing the last native server makes that fallback eligible to load again under the documented ZCode configuration rules. The source uninstall workflow reproduced this with valid native and fallback configurations. ## Steps to reproduce From the source checkout identified below, save this test as `src/application/fallback-cleanup.test.ts` and run `npm run test:local -- src/application/fallback-cleanup.test.ts`. It uses a disposable home and an explicit environment, runs the production uninstall filesystem host, and never starts either configured command. The independently executed focused test exercised this same direct native/fallback input, plus a setup-created equivalent and a fallback-only case. ```typescript import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { dirname, join } from "node:path"; import { expect, it } from "vitest"; import { supportedClients } from "./SupportedClients.js"; import { runUninstall, systemUninstallHost } from "./Uninstall.js"; it("reports native removal while leaving the fallback registration", async () => { const home = await mkdtemp(join(tmpdir(), "rea-fallback-")); const environment = { HOME: home, USERPROFILE: home }; try { const client = supportedClients(home, "darwin", environment) .find(value => value.name === "zcode")!; const registration = { command: "npx", args: ["-y", "rea-agents@6.4.0", "mcp"], }; await mkdir(dirname(client.configPath), { recursive: true }); await mkdir(dirname(client.zcodeFallbackPath!), { recursive: true }); await writeFile(client.configPath, JSON.stringify({ mcp: { servers: { rea: registration } } })); const originalFallback = JSON.stringify({ mcpServers: { rea: registration } }); await writeFile(client.zcodeFallbackPath!, originalFallback); const result = await runUninstall(false, systemUninstallHost(home, undefined, environment, "darwin")); const native = JSON.parse(await readFile(client.configPath, "utf8")); const fallback = await readFile(client.zcodeFallbackPath!, "utf8"); expect(result.status).toBe("complete"); expect(result.items.find(value => value.name === "zcode")?.status) .toBe("removed"); expect(native.mcp.servers).toEqual({}); expect(fallback).toBe(originalFallback); console.log(JSON.stringify({ status: result.status, zcode: "removed", nativeServerCount: Object.keys(native.mcp.servers).length, fallbackReaRetained: Boolean(JSON.parse(fallback).mcpServers.rea) })); } finally { await rm(home, { recursive: true, force: true }); } }); ``` The test records the current failure rather than asserting that cleanup has been restored. Its temporary root construction is portable; the executed verifier used a fixed owned fixture root. Public CLI reachability was traced from the `uninstall` handler to these production functions, but the packaged CLI was not executed. ## Expected behavior After successful uninstall, the REA-owned registration should be absent from both supported ZCode registration locations. An empty native table must not expose a leftover owned fallback entry. Unrelated fallback entries and settings must remain intact. ## Actual behavior The production uninstall workflow returned `status: complete` and marked ZCode `removed`. The native table contained zero servers, but the fallback remained byte-for-byte unchanged with its REA registration. This result occurred in both the direct fixture above and an equivalent configuration created by the production setup writer. A reduced fallback-only case also returned `complete`, marked ZCode `skipped`, and left the same owned entry intact. ```json {"status":"complete","zcode":"removed","nativeServerCount":0,"fallbackReaRetained":true} ``` The production doctor registration reader reported ZCode `missing` afterward in all three fixture cases. Under the documented fallback selection rule, the retained entry is eligible to load when the native table is empty. Actual ZCode startup and connection were not tested, and no released-package CLI exit status was measured. ## Evidence - Expected source: `docs/installation.md:156` describes removal of REA-owned registrations; `docs/client-configuration.md:183-201` documents the native/fallback selection rule and setup preserving the fallback. - Failure source: a focused source test invoking `runUninstall(false, systemUninstallHost(...))` produced the output above and confirmed unchanged fallback bytes. Production workflow locator: `src/application/Uninstall.ts:209-218`; public CLI entry point: `src/cli/setupCommands.ts:143-166`. - Evidence provenance: observed - Local verification: reproduced - Reproduction completeness: complete The npx command in the fixture passed the production ownership check. Related: #190 concerns npx registration ownership matching, and #2187 concerns Claude Code project-scope registration discovery and removal. Neither covers this owned ZCode fallback cleanup case. ## Restoration check With the same owned native and fallback entries, successful uninstall must leave neither location with the REA-owned key. Also check the setup-created and fallback-only cases. Preserve unrelated fallback servers and settings, including a foreign command stored under the name `rea`; deleting the whole fallback file would not restore the documented preservation behavior. The current direct fixture fails the cleanup condition even though uninstall reports completion. Changing doctor output alone does not remove the leftover registration. ## Runtime or environment Verified source revision `a2d8b94dd50dab8d966239dd63ab84718aa8b4b4`, whose package manifest is 6.4.0, using Node 24.18.0 and npm 11.16.0. The source test used the Darwin client-path branch with owned filesystem fixtures. The latest published package was identified as rea-agents 6.4.0, but its runtime was not tested. Current upstream recheck at `3ae826ceba6f25b08bce50bcb51001209bc16b57` reproduced all three source-workflow cases with the same Node and npm versions; the packaged CLI and actual ZCode startup and connection remain untested. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: A supported configuration state leaves an owned, loadable registration after uninstall reports completion. The failure is bounded to hosts with this fallback entry; no loss of unrelated data was observed. - Workaround status: available - Workaround basis: Remove only the owned `rea` key from the fallback's `mcpServers` object before restarting the client. This key-removal operation was checked on the disposable fixtures and preserved an unrelated server and an unrelated root setting. Do not clear the entire fallback file.
bsakaguchi 2026-10-15 18:29:57 +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#964
No description provided.