[GH-ISSUE #2515] fix(setup): preserve exact unrelated integer keys in Hermes configuration #832

Closed
opened 2026-10-15 18:28:50 +00:00 by bsakaguchi · 1 comment
Owner

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

Summary

Adding REA to a Hermes configuration or removing an owned REA registration can change an unrelated unquoted integer key. In independent filesystem probes, the synthetic key 1187654321098765433 became 1187654321098765300, and both operations reported success. This violates the promise to preserve unrelated settings. A fresh automatic backup retained the original input, but the active configuration contained the changed key.

Runtime or environment

The failure was verified at REA source revision a2d8b94dd50dab8d966239dd63ab84718aa8b4b4 and is still present at 8b181716aca2cb467be499cba226abfb5ae19141, package version 6.4.0, Node.js v26.10.0, npm 11.19.1, macOS arm64, and installed lockfile dependencies including yaml 2.9.0 and Vitest 5.0.3. The probes exercised production application and filesystem boundaries; the public CLI and a running Hermes client were not executed.

Steps to reproduce

Create src/application/hermesNumberPreservation.test.ts with the following test, then run the focused command below from the checkout. The two cases use independent disposable homes so uninstall receives an exact original key rather than an already rounded setup result. The integer is synthetic and unrelated to any real account or channel.

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

it.each(["setup", "uninstall"])("preserves the original key during %s", async action => {
  const home = await mkdtemp(join(tmpdir(), "rea-integer-check-"));
  const command = npxRegistrationCommand(process.platform);
  try {
    await mkdir(join(home, ".hermes"));
    const client = supportedClients(home, process.platform, {})
      .find(client => client.name === "hermes")!;
    const settings = "discord:\n  channel_prompts:\n    1187654321098765433: Build questions.\nmcp_servers:\n  other:\n    command: other-server\n";
    const registration = `  rea:\n    command: ${command[0]}\n    args: ${JSON.stringify(command.slice(1))}\n    enabled: true\n`;
    const input = settings + (action === "uninstall" ? registration : "");
    await writeFile(client.configPath, input);
    if (action === "setup") {
      expect((await configureClientConfiguration(client, {}, command)).status)
        .toBe("configured");
    } else {
      const host = systemUninstallHost(home, undefined, {}, process.platform);
      try {
        expect(await host.inspectClient(client)).toBeUndefined();
        expect((await host.removeClient(client)).status).toBe("removed");
      } finally {
        await host.close?.();
      }
    }
    expect(await readFile(`${client.configPath}.rea.backup`, "utf8")).toBe(input);
    expect(await readFile(client.configPath, "utf8"))
      .toContain("1187654321098765433");
  } finally {
    await rm(home, { recursive: true, force: true });
  }
});
npm run test:focused -- src/application/hermesNumberPreservation.test.ts

The independent diagnostic probe executed this same production boundary sequence with assertions recording the rounded result, plus quoted-key, backup, comment, ordinary-number, other-server and setup-idempotence controls. The exact test above was then run independently and both cases failed at the final key-preservation assertion. All preceding success-status and backup assertions passed; the focused runner exited 1. Through the public interface, setup reaches this writer when rea setup --client hermes approves a needed registration edit; rea uninstall reaches removal for an owned Hermes registration. Uninstall has no client-selection option. An aligned setup or absent uninstall registration does not trigger this rewrite. Source tracing also shows that uninstall retains a foreign rea-named registration without rewriting the file.

Expected behavior

Changing REA's registration must preserve the exact value of unrelated configuration entries. The synthetic integer key must still denote 1187654321098765433 after either adding or removing the REA entry, and other servers must remain configured.

Actual behavior

The fresh setup returned configured, and the independent fresh uninstall returned removed. Each wrote 1187654321098765300: Build questions. in place of the original mapping entry. Both passed their registration readback checks. The quoted version of the same key stayed exact in both operations; comments, a 0.7 setting and the other server survived. A subsequent setup returned unchanged without rewriting the file. These observations establish configuration alteration, not a tested downstream Hermes runtime failure.

Evidence

  • Expected source: docs/client-configuration.md:143-144 promises preservation of unrelated Hermes settings; src/application/ClientConfigurationDocument.ts:1401-1405 describes editing only REA's entry.
  • Failure source: Independent isolated filesystem observations above, explained by original-YAML parsing and whole-document emission in src/application/ClientConfigurationDocument.ts:1422-1427; setup persistence and readback at src/application/SetupClientConfiguration.ts:169-249, and uninstall ownership, persistence and readback at src/application/Uninstall.ts:312-388.
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: complete

The focused diagnostic test passed with four isolated scenarios because it asserted the observed numeric rewrite and controls. Fresh setup and fresh uninstall each changed the unquoted key once; their quoted-key controls did not. Exact backup-byte comparisons passed in all four scenarios. The exact inline reproduction was also executed: setup and uninstall both failed only at the final preservation assertion, showing the rounded key in the received output. No source fix was applied.

The existing report morluto/rea#1606 concerns lost environment settings inside REA's own registration and is closed. Its report and fixing change address a different contract and trigger; this failure changes an unrelated YAML key.

Restoration check

With the unquoted fixture, verify independently that setup adds the owned registration and uninstall removes it while the unrelated integer key retains its exact numeric value. Require registration readback to remain successful, the other server and comment to survive, and an aligned setup rerun to make no changes. Keep the quoted-key and ordinary-number controls passing and retain the existing first-backup behavior. Restoration must support the original accepted input rather than requiring users to quote previously valid values.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: A successful registration edit persistently changes a bounded unrelated configuration value for particular unquoted integers. The fresh tested edits retain the exact original input in an automatic backup; no live Hermes malfunction or broader irreversible data loss was established.
  • Workaround status: available
  • Workaround basis: In both fresh edit scenarios, the automatic .rea.backup was byte-identical to the original input. Users can recover the exact key from that backup and manually correct the active configuration while preserving the intended registration state. An older existing backup may not contain the latest input, so its contents must be checked before using it. Quoting preserved the key in the serializer controls, but Hermes runtime acceptance of that alternative was not verified.
Originally created by @coygeek on GitHub (Oct 13, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/2515 ## Summary Adding REA to a Hermes configuration or removing an owned REA registration can change an unrelated unquoted integer key. In independent filesystem probes, the synthetic key `1187654321098765433` became `1187654321098765300`, and both operations reported success. This violates the promise to preserve unrelated settings. A fresh automatic backup retained the original input, but the active configuration contained the changed key. ## Runtime or environment The failure was verified at REA source revision `a2d8b94dd50dab8d966239dd63ab84718aa8b4b4` and is still present at `8b181716aca2cb467be499cba226abfb5ae19141`, package version 6.4.0, Node.js v26.10.0, npm 11.19.1, macOS arm64, and installed lockfile dependencies including yaml 2.9.0 and Vitest 5.0.3. The probes exercised production application and filesystem boundaries; the public CLI and a running Hermes client were not executed. ## Steps to reproduce Create `src/application/hermesNumberPreservation.test.ts` with the following test, then run the focused command below from the checkout. The two cases use independent disposable homes so uninstall receives an exact original key rather than an already rounded setup result. The integer is synthetic and unrelated to any real account or channel. ```ts import { mkdtemp, mkdir, readFile, writeFile, rm } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { expect, it } from "vitest"; import { configureClientConfiguration } from "./SetupClientConfiguration.js"; import { npxRegistrationCommand } from "./ClientRegistrationIdentity.js"; import { supportedClients } from "./SupportedClients.js"; import { systemUninstallHost } from "./Uninstall.js"; it.each(["setup", "uninstall"])("preserves the original key during %s", async action => { const home = await mkdtemp(join(tmpdir(), "rea-integer-check-")); const command = npxRegistrationCommand(process.platform); try { await mkdir(join(home, ".hermes")); const client = supportedClients(home, process.platform, {}) .find(client => client.name === "hermes")!; const settings = "discord:\n channel_prompts:\n 1187654321098765433: Build questions.\nmcp_servers:\n other:\n command: other-server\n"; const registration = ` rea:\n command: ${command[0]}\n args: ${JSON.stringify(command.slice(1))}\n enabled: true\n`; const input = settings + (action === "uninstall" ? registration : ""); await writeFile(client.configPath, input); if (action === "setup") { expect((await configureClientConfiguration(client, {}, command)).status) .toBe("configured"); } else { const host = systemUninstallHost(home, undefined, {}, process.platform); try { expect(await host.inspectClient(client)).toBeUndefined(); expect((await host.removeClient(client)).status).toBe("removed"); } finally { await host.close?.(); } } expect(await readFile(`${client.configPath}.rea.backup`, "utf8")).toBe(input); expect(await readFile(client.configPath, "utf8")) .toContain("1187654321098765433"); } finally { await rm(home, { recursive: true, force: true }); } }); ``` ```sh npm run test:focused -- src/application/hermesNumberPreservation.test.ts ``` The independent diagnostic probe executed this same production boundary sequence with assertions recording the rounded result, plus quoted-key, backup, comment, ordinary-number, other-server and setup-idempotence controls. The exact test above was then run independently and both cases failed at the final key-preservation assertion. All preceding success-status and backup assertions passed; the focused runner exited 1. Through the public interface, setup reaches this writer when `rea setup --client hermes` approves a needed registration edit; `rea uninstall` reaches removal for an owned Hermes registration. Uninstall has no client-selection option. An aligned setup or absent uninstall registration does not trigger this rewrite. Source tracing also shows that uninstall retains a foreign rea-named registration without rewriting the file. ## Expected behavior Changing REA's registration must preserve the exact value of unrelated configuration entries. The synthetic integer key must still denote `1187654321098765433` after either adding or removing the REA entry, and other servers must remain configured. ## Actual behavior The fresh setup returned `configured`, and the independent fresh uninstall returned `removed`. Each wrote `1187654321098765300: Build questions.` in place of the original mapping entry. Both passed their registration readback checks. The quoted version of the same key stayed exact in both operations; comments, a `0.7` setting and the other server survived. A subsequent setup returned `unchanged` without rewriting the file. These observations establish configuration alteration, not a tested downstream Hermes runtime failure. ## Evidence - Expected source: `docs/client-configuration.md:143-144` promises preservation of unrelated Hermes settings; `src/application/ClientConfigurationDocument.ts:1401-1405` describes editing only REA's entry. - Failure source: Independent isolated filesystem observations above, explained by original-YAML parsing and whole-document emission in `src/application/ClientConfigurationDocument.ts:1422-1427`; setup persistence and readback at `src/application/SetupClientConfiguration.ts:169-249`, and uninstall ownership, persistence and readback at `src/application/Uninstall.ts:312-388`. - Evidence provenance: observed - Local verification: reproduced - Reproduction completeness: complete The focused diagnostic test passed with four isolated scenarios because it asserted the observed numeric rewrite and controls. Fresh setup and fresh uninstall each changed the unquoted key once; their quoted-key controls did not. Exact backup-byte comparisons passed in all four scenarios. The exact inline reproduction was also executed: setup and uninstall both failed only at the final preservation assertion, showing the rounded key in the received output. No source fix was applied. The existing report [morluto/rea#1606](https://github.com/morluto/rea/issues/1606) concerns lost environment settings inside REA's own registration and is closed. Its report and fixing change address a different contract and trigger; this failure changes an unrelated YAML key. ## Restoration check With the unquoted fixture, verify independently that setup adds the owned registration and uninstall removes it while the unrelated integer key retains its exact numeric value. Require registration readback to remain successful, the other server and comment to survive, and an aligned setup rerun to make no changes. Keep the quoted-key and ordinary-number controls passing and retain the existing first-backup behavior. Restoration must support the original accepted input rather than requiring users to quote previously valid values. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: A successful registration edit persistently changes a bounded unrelated configuration value for particular unquoted integers. The fresh tested edits retain the exact original input in an automatic backup; no live Hermes malfunction or broader irreversible data loss was established. - Workaround status: available - Workaround basis: In both fresh edit scenarios, the automatic `.rea.backup` was byte-identical to the original input. Users can recover the exact key from that backup and manually correct the active configuration while preserving the intended registration state. An older existing backup may not contain the latest input, so its contents must be checked before using it. Quoting preserved the key in the serializer controls, but Hermes runtime acceptance of that alternative was not verified.
bsakaguchi 2026-10-15 18:28:50 +00:00
  • closed this issue
  • added the
    bug
    label
Author
Owner

@morluto commented on GitHub (Oct 13, 2026):

Verified during the bug/fix-PR audit. The current Hermes YAML owner preserves source spelling for numeric scalars, including distinct 0600 and 600 keys, instead of rounding or rewriting unrelated keys.

Current implementation: src/application/HermesConfigurationYaml.ts. Validation on macOS arm64 with Node.js 24.18.0: npm run test:local -- src/application/HermesConfigurationYaml.test.ts src/ida/IdaSessionClient.test.ts src/native/NativeMachoInspection.test.ts src/browser/PlaywrightElectronActiveProvider.test.ts passed (84 tests). This is source/provider-boundary regression coverage; no real IDA GUI session was launched for this check.

Closing because the reported behavior is already addressed on main.

<!-- gh-comment-id:6157481973 --> @morluto commented on GitHub (Oct 13, 2026): Verified during the bug/fix-PR audit. The current Hermes YAML owner preserves source spelling for numeric scalars, including distinct `0600` and `600` keys, instead of rounding or rewriting unrelated keys. Current implementation: `src/application/HermesConfigurationYaml.ts`. Validation on macOS arm64 with Node.js 24.18.0: `npm run test:local -- src/application/HermesConfigurationYaml.test.ts src/ida/IdaSessionClient.test.ts src/native/NativeMachoInspection.test.ts src/browser/PlaywrightElectronActiveProvider.test.ts` passed (84 tests). This is source/provider-boundary regression coverage; no real IDA GUI session was launched for this check. Closing because the reported behavior is already addressed on main.
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#832
No description provided.