[GH-ISSUE #7288] [Bug] Hermes setup refresh deletes the comment on the REA registration #4325

Closed
opened 2026-10-31 00:48:56 +00:00 by bsakaguchi · 0 comments
Owner

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

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

setup --client hermes --skill=false --yes --json removes # REA registration note from rea: # REA registration note while refreshing a stale launcher. The public CLI exits 0 with ready and Hermes configured. The file and peer comments survive, the stale command is replaced, and the backup contains the exact original bytes. The loss reproduced in two fresh isolated homes.

Reproduction

Run this from the baseline checkout root with its locked dependencies and built dist/ already available, using Node 24 on PATH. It invokes the public dispatcher with an isolated environment and disposable home; the Hopper override points at a nonexistent fixture path, and skill installation is disabled.

python3 - <<'PYCODE'
import json, os, subprocess, tempfile
from pathlib import Path

cli = (Path.cwd() / 'scripts/rea.mjs').resolve()
with tempfile.TemporaryDirectory() as directory:
    home = Path(directory)
    config = home / '.hermes/config.yaml'
    config.parent.mkdir()
    (home / 'temp').mkdir()
    original = '# file note\nmcp_servers:\n  rea: # REA registration note\n    command: old-rea\n    args: [mcp]\n    enabled: true\n  other: # peer note\n    command: other-server\n'
    config.write_text(original)
    environment = {
        'PATH': os.environ['PATH'], 'HOME': str(home), 'USERPROFILE': str(home),
        'HERMES_HOME': str(config.parent), 'XDG_CONFIG_HOME': str(home / '.config'),
        'XDG_CACHE_HOME': str(home / '.cache'), 'TMPDIR': str(home / 'temp'),
        'HOPPER_LAUNCHER_PATH': str(home / 'absent-hopper'),
        'REA_LOG_LEVEL': 'silent', 'NO_COLOR': '1',
    }
    command = ['node', str(cli), 'setup', '--client', 'hermes', '--skill=false', '--json']
    for label, flags in [('dry-run', ['--dry-run']), ('apply', ['--yes']), ('repeat', ['--yes'])]:
        before = config.read_text()
        process = subprocess.run(command + flags, env=environment, cwd=home, capture_output=True, text=True, timeout=45)
        result = json.loads(process.stdout)
        after = config.read_text()
        backup = Path(str(config) + '.rea.backup')
        print(json.dumps({
            'case': label, 'exit': process.returncode, 'status': result['status'],
            'hermes': result['clients'].get('hermes', {}).get('status'),
            'commentRetained': '# REA registration note' in after,
            'controlsRetained': all(value in after for value in ['# file note', '# peer note', 'command: other-server', 'enabled: true']),
            'commandUpdated': 'command: old-rea' not in after,
            'bytesUnchanged': before == after,
            'backupMatchesOriginal': backup.exists() and backup.read_text() == original,
        }))
    config.write_text(config.read_text().replace('  rea:\n', '  rea: # REA registration note\n'))
    before = config.read_text()
    process = subprocess.run(command + ['--yes'], env=environment, cwd=home, capture_output=True, text=True, timeout=45)
    result = json.loads(process.stdout)
    print(json.dumps({'case': 'restored-comment', 'exit': process.returncode, 'status': result['status'], 'commentRetained': '# REA registration note' in config.read_text(), 'bytesUnchanged': before == config.read_text()}))
PYCODE

Expected behavior

Refreshing the launcher should preserve the caller's explanatory comment on the REA entry, along with the other comments and settings. The Hermes setup documentation promises comment preservation without excluding comments on this entry.

Impact and scope

Observed impact is loss of annotation text on a refresh that rewrites the existing registration. The tested launcher refresh and unrelated server settings remain correct; no native Hermes session was exercised. The original text is recoverable from config.yaml.rea.backup. Manually restoring the comment after setup was tested and survived another setup call while the launcher remained current. Another material refresh can trigger the same loss.

REA version

Source baseline 0d1a454e25, package version 6.8.0.

Runtime and platform

Node.js 24.22.1; macOS 27.0.1; arm64. Executed the built source-checkout dispatcher with isolated home/config/cache/temp directories.

Target and artifact context

A synthetic Hermes config.yaml containing a stale local REA registration, its inline explanatory comment, and one peer server. No analysis target or artifact is involved.

Interface

CLI

Earliest failure stage

Setup or provider admission

Relevant output or evidence

  • Expected source: docs/client-configuration.md:484-488 promises that Hermes setup preserves comments and unrelated settings; docs/installation.md:66-77 documents comment preservation and recovery backups.
  • Failure source: the executed public CLI and resulting file in the reproduction above; the selected write reaches src/application/SetupHost.ts:385-399, src/application/ClientConfigurationDocument.ts:1711-1731, and src/application/HermesConfigurationYaml.ts:456-469.
  • Evidence provenance: observed for CLI results, comment loss, retained controls, backup equality and manual recovery; the source explanation below is static.
  • Local verification: reproduced
  • Reproduction completeness: complete

The exact inline reproduction produced:

{"case": "dry-run", "exit": 0, "status": "planned", "hermes": null, "commentRetained": true, "controlsRetained": true, "commandUpdated": false, "bytesUnchanged": true, "backupMatchesOriginal": false}
{"case": "apply", "exit": 0, "status": "ready", "hermes": "configured", "commentRetained": false, "controlsRetained": true, "commandUpdated": true, "bytesUnchanged": false, "backupMatchesOriginal": true}
{"case": "repeat", "exit": 0, "status": "ready", "hermes": null, "commentRetained": false, "controlsRetained": true, "commandUpdated": true, "bytesUnchanged": true, "backupMatchesOriginal": true}
{"case": "restored-comment", "exit": 0, "status": "ready", "commentRetained": true, "bytesUnchanged": true}

The dry run preserved all input bytes. The approved refresh lost only the selected annotation among the checked comments, while updating the launcher and retaining the peer command and enabled setting. Repeat setup had no client action and preserved the resulting bytes. Restoring the comment manually also left the next current-registration setup byte-identical.

Typed outcome or recovery expected

Restoration check: the same approved refresh must still return ready with Hermes configured, replace the stale launcher and retain enabled: true, while also retaining # REA registration note. Preserve the file/peer comments, peer command and exact original backup. The dry-run and repeat-setup byte-preservation controls must continue to pass; skipping the required launcher refresh would not restore this contract.

Analysis or suspected cause

Source-derived explanation: retainHermesScalarSource in src/application/HermesConfigurationYaml.ts:332-359 reuses unchanged child nodes, but the containing registration mapping is newly created without the previous mapping's comment metadata. This matches the observed loss on replacement and preservation when no rewrite occurs. No fix was tested.

Additional context

Related reports were checked live, including bodies and comments. #6207 and #1163 are closed TOML comment-loss reports. #6997 concerns Hermes tools.include values. Open #5152 preserves external consumers of YAML anchors and their copied comments; this fixture has no anchors or aliases. Open #1732 explicitly excludes comments inside the REA entry from its field-retention scope. No exact open duplicate was found. This report makes no removal or uninstall claim.

Triage assessment

  • Impact level: P3
  • Assessment status: supported
  • Impact basis: The executed refresh removes an explanatory YAML comment while preserving the tested configuration values and a byte-exact original backup; no consequential runtime behavior change was established.
  • Workaround status: available
  • Workaround basis: The original comment is present in the verified backup; manually reinserting it after refresh was executed and survived another setup call with the current registration. Repeat this recovery after a later material refresh if needed.
Originally created by @coygeek on GitHub (Oct 26, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/7288 ## 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 `setup --client hermes --skill=false --yes --json` removes `# REA registration note` from `rea: # REA registration note` while refreshing a stale launcher. The public CLI exits 0 with `ready` and Hermes `configured`. The file and peer comments survive, the stale command is replaced, and the backup contains the exact original bytes. The loss reproduced in two fresh isolated homes. ## Reproduction Run this from the baseline checkout root with its locked dependencies and built `dist/` already available, using Node 24 on `PATH`. It invokes the public dispatcher with an isolated environment and disposable home; the Hopper override points at a nonexistent fixture path, and skill installation is disabled. ```sh python3 - <<'PYCODE' import json, os, subprocess, tempfile from pathlib import Path cli = (Path.cwd() / 'scripts/rea.mjs').resolve() with tempfile.TemporaryDirectory() as directory: home = Path(directory) config = home / '.hermes/config.yaml' config.parent.mkdir() (home / 'temp').mkdir() original = '# file note\nmcp_servers:\n rea: # REA registration note\n command: old-rea\n args: [mcp]\n enabled: true\n other: # peer note\n command: other-server\n' config.write_text(original) environment = { 'PATH': os.environ['PATH'], 'HOME': str(home), 'USERPROFILE': str(home), 'HERMES_HOME': str(config.parent), 'XDG_CONFIG_HOME': str(home / '.config'), 'XDG_CACHE_HOME': str(home / '.cache'), 'TMPDIR': str(home / 'temp'), 'HOPPER_LAUNCHER_PATH': str(home / 'absent-hopper'), 'REA_LOG_LEVEL': 'silent', 'NO_COLOR': '1', } command = ['node', str(cli), 'setup', '--client', 'hermes', '--skill=false', '--json'] for label, flags in [('dry-run', ['--dry-run']), ('apply', ['--yes']), ('repeat', ['--yes'])]: before = config.read_text() process = subprocess.run(command + flags, env=environment, cwd=home, capture_output=True, text=True, timeout=45) result = json.loads(process.stdout) after = config.read_text() backup = Path(str(config) + '.rea.backup') print(json.dumps({ 'case': label, 'exit': process.returncode, 'status': result['status'], 'hermes': result['clients'].get('hermes', {}).get('status'), 'commentRetained': '# REA registration note' in after, 'controlsRetained': all(value in after for value in ['# file note', '# peer note', 'command: other-server', 'enabled: true']), 'commandUpdated': 'command: old-rea' not in after, 'bytesUnchanged': before == after, 'backupMatchesOriginal': backup.exists() and backup.read_text() == original, })) config.write_text(config.read_text().replace(' rea:\n', ' rea: # REA registration note\n')) before = config.read_text() process = subprocess.run(command + ['--yes'], env=environment, cwd=home, capture_output=True, text=True, timeout=45) result = json.loads(process.stdout) print(json.dumps({'case': 'restored-comment', 'exit': process.returncode, 'status': result['status'], 'commentRetained': '# REA registration note' in config.read_text(), 'bytesUnchanged': before == config.read_text()})) PYCODE ``` ## Expected behavior Refreshing the launcher should preserve the caller's explanatory comment on the REA entry, along with the other comments and settings. The Hermes setup documentation promises comment preservation without excluding comments on this entry. ## Impact and scope Observed impact is loss of annotation text on a refresh that rewrites the existing registration. The tested launcher refresh and unrelated server settings remain correct; no native Hermes session was exercised. The original text is recoverable from `config.yaml.rea.backup`. Manually restoring the comment after setup was tested and survived another setup call while the launcher remained current. Another material refresh can trigger the same loss. ## REA version Source baseline 0d1a454e25db825567782e461da21530bad4b158, package version 6.8.0. ## Runtime and platform Node.js 24.22.1; macOS 27.0.1; arm64. Executed the built source-checkout dispatcher with isolated home/config/cache/temp directories. ## Target and artifact context A synthetic Hermes `config.yaml` containing a stale local REA registration, its inline explanatory comment, and one peer server. No analysis target or artifact is involved. ## Interface CLI ## Earliest failure stage Setup or provider admission ## Relevant output or evidence - Expected source: `docs/client-configuration.md:484-488` promises that Hermes setup preserves comments and unrelated settings; `docs/installation.md:66-77` documents comment preservation and recovery backups. - Failure source: the executed public CLI and resulting file in the reproduction above; the selected write reaches `src/application/SetupHost.ts:385-399`, `src/application/ClientConfigurationDocument.ts:1711-1731`, and `src/application/HermesConfigurationYaml.ts:456-469`. - Evidence provenance: observed for CLI results, comment loss, retained controls, backup equality and manual recovery; the source explanation below is static. - Local verification: reproduced - Reproduction completeness: complete The exact inline reproduction produced: ```json {"case": "dry-run", "exit": 0, "status": "planned", "hermes": null, "commentRetained": true, "controlsRetained": true, "commandUpdated": false, "bytesUnchanged": true, "backupMatchesOriginal": false} {"case": "apply", "exit": 0, "status": "ready", "hermes": "configured", "commentRetained": false, "controlsRetained": true, "commandUpdated": true, "bytesUnchanged": false, "backupMatchesOriginal": true} {"case": "repeat", "exit": 0, "status": "ready", "hermes": null, "commentRetained": false, "controlsRetained": true, "commandUpdated": true, "bytesUnchanged": true, "backupMatchesOriginal": true} {"case": "restored-comment", "exit": 0, "status": "ready", "commentRetained": true, "bytesUnchanged": true} ``` The dry run preserved all input bytes. The approved refresh lost only the selected annotation among the checked comments, while updating the launcher and retaining the peer command and enabled setting. Repeat setup had no client action and preserved the resulting bytes. Restoring the comment manually also left the next current-registration setup byte-identical. ## Typed outcome or recovery expected Restoration check: the same approved refresh must still return `ready` with Hermes `configured`, replace the stale launcher and retain `enabled: true`, while also retaining `# REA registration note`. Preserve the file/peer comments, peer command and exact original backup. The dry-run and repeat-setup byte-preservation controls must continue to pass; skipping the required launcher refresh would not restore this contract. ## Analysis or suspected cause Source-derived explanation: `retainHermesScalarSource` in `src/application/HermesConfigurationYaml.ts:332-359` reuses unchanged child nodes, but the containing registration mapping is newly created without the previous mapping's comment metadata. This matches the observed loss on replacement and preservation when no rewrite occurs. No fix was tested. ## Additional context Related reports were checked live, including bodies and comments. [#6207](https://github.com/morluto/rea/issues/6207) and [#1163](https://github.com/morluto/rea/issues/1163) are closed TOML comment-loss reports. [#6997](https://github.com/morluto/rea/issues/6997) concerns Hermes `tools.include` values. Open [#5152](https://github.com/morluto/rea/pull/5152) preserves external consumers of YAML anchors and their copied comments; this fixture has no anchors or aliases. Open [#1732](https://github.com/morluto/rea/pull/1732) explicitly excludes comments inside the REA entry from its field-retention scope. No exact open duplicate was found. This report makes no removal or uninstall claim. ## Triage assessment - Impact level: P3 - Assessment status: supported - Impact basis: The executed refresh removes an explanatory YAML comment while preserving the tested configuration values and a byte-exact original backup; no consequential runtime behavior change was established. - Workaround status: available - Workaround basis: The original comment is present in the verified backup; manually reinserting it after refresh was executed and survived another setup call with the current registration. Repeat this recovery after a later material refresh if needed.
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#4325
No description provided.