[GH-ISSUE #7286] [Bug] DSH configuration edits silently discard later YAML documents #4326

Open
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/7286

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

node scripts/rea.mjs setup --client dsh --skill=false --yes --format json returns exit 0 and status: ready for a two-document cordis.patch.yml, but rewrites it with only the first document and the new REA entry. The second document's unrelated operation and comment disappear. uninstall --format json also discards that second document when removing an REA entry from the first document, while reporting status: complete and the DSH item as removed.

Both operations created backups containing the complete original bytes. Restoring those backups recovered the original fixture. This report concerns preservation of the selected file; whether DeepSeek Harness loads subsequent YAML documents was not established.

Reproduction

Run the following from a prepared source checkout with its existing built runtime, Python 3 and the repository's supported Node.js on PATH. Each CLI invocation receives a fresh environment and disposable home/config/cache/state/temp paths, runs from its own fixture directory and disables skill installation. No DSH client or analysis target is required. The first fixture contains two separate operation lists with an intervening ---; the uninstall fixture adds the same second document to a registration obtained from the single-document control. The script restores each backup before deleting the disposable fixtures.

import json, os, pathlib, shutil, subprocess, tempfile
root = pathlib.Path.cwd()
node = pathlib.Path(shutil.which('node')).resolve()
cli = root / 'scripts' / 'rea.mjs'
first = '# keep first comment\n- insert:\n    - id: first-tool\n      name: caller-plugin\n      config:\n        command: first-server\n'
second = '---\n# keep second comment\n- insert:\n    - id: second-tool\n      name: caller-plugin\n      config:\n        command: second-server\n'
with tempfile.TemporaryDirectory(prefix='rea-cordis-') as temporary:
    base = pathlib.Path(temporary)
    def trial(name, text, args):
        home = base / name
        config = home / '.dsh' / 'cordis.patch.yml'
        config.parent.mkdir(parents=True)
        config.write_text(text)
        env = {'PATH': str(node.parent) + os.pathsep + os.defpath, 'HOME': str(home), 'DSH_HOME': str(config.parent), 'DSH_AGENTS_HOME': str(home / '.agents'), 'XDG_CONFIG_HOME': str(home / '.config'), 'XDG_CACHE_HOME': str(home / '.cache'), 'XDG_DATA_HOME': str(home / '.local/share'), 'XDG_STATE_HOME': str(home / '.local/state'), 'TMPDIR': str(home / 'temp'), 'npm_config_prefix': str(home / '.npm-global'), 'NO_COLOR': '1'}
        (home / 'temp').mkdir()
        result = subprocess.run([str(node), str(cli), *args, '--format', 'json'], cwd=home, env=env, capture_output=True, text=True, timeout=30)
        data = json.loads(result.stdout)
        after = config.read_text()
        backup = pathlib.Path(str(config) + '.rea.backup')
        item = next((i for i in data.get('items', []) if i.get('name') == 'dsh'), {})
        print(json.dumps({'case': name, 'exit': result.returncode, 'status': data.get('status'), 'appliedActions': data.get('appliedActions'), 'dsh_status': item.get('status'), 'first_retained': 'first-tool' in after, 'second_retained': 'second-tool' in after, 'second_comment_retained': 'keep second comment' in after, 'unchanged': after == text, 'backup_equal': backup.exists() and backup.read_text() == text}))
        if backup.exists():
            config.write_bytes(backup.read_bytes())
            print(name + ' recovery_equal=' + str(config.read_bytes() == text.encode()))
        return after
    trial('setup-two-documents', first + second, ['setup', '--client', 'dsh', '--skill=false', '--yes'])
    registered = trial('setup-single-document', first, ['setup', '--client', 'dsh', '--skill=false', '--yes'])
    trial('uninstall-two-documents', registered + second, ['uninstall'])

Each of the two destructive cases ran once in this script and reproduced; a separate local CLI probe reproduced both cases as well. The single-document setup control retained its unrelated operation and first comment. In the separate probe, a repeat of that single-document setup applied no actions and left the file byte-identical. A malformed single-document - insert: not-a-list control returned exit 1 / needs_human, left the file unchanged and made no backup.

Expected behavior

Editing the DSH home patch must preserve unrelated content or refuse the edit without changing its bytes. docs/client-configuration.md:599-606 explicitly promises to preserve unrelated patches and comments in this file. docs/installation.md:61-76 says malformed or unsafe existing configuration blocks the transaction before writes and describes preservation and backups. If multiple YAML documents are unsupported for this configuration, a refusal satisfies that contract; supporting them as functional DSH configuration is not required by this report.

Impact and scope

P2: the reproduced setup and uninstall edits silently remove unrelated file content on a bounded input shape, while reporting success. Both retained complete recovery backups, and recovery was executed successfully. No irreversible loss or functional DSH registration failure was demonstrated. The parser and both writers share this interpretation, but population frequency, other platforms and DSH's own multi-document behavior were not measured.

REA version

6.8.0; source revision 0d1a454e25db825567782e461da21530bad4b158.

Runtime and platform

Node.js v24.22.1; macOS arm64; locked yaml dependency 2.9.0. No native analysis engine or DSH session was used.

Target and artifact context

Agent configuration only: $DSH_HOME/cordis.patch.yml, with two YAML documents. No binary or captured target artifact.

Interface

CLI

Earliest failure stage

Setup or provider admission

Relevant output or evidence

Selected fields from the executed script's real output:

setup-two-documents: exit=0, status=ready, appliedActions=[configured_dsh], first_retained=true, second_retained=false, second_comment_retained=false, unchanged=false, backup_equal=true
setup-two-documents recovery_equal=True
setup-single-document: exit=0, status=ready, appliedActions=[configured_dsh], first_retained=true, unchanged=false, backup_equal=true
uninstall-two-documents: exit=0, status=complete, dsh_status=removed, first_retained=true, second_retained=false, second_comment_retained=false, unchanged=false, backup_equal=true
uninstall-two-documents recovery_equal=True

The independent parser probe reported MULTIPLE_DOCS for the two-document fixture under yaml.parseDocument defaults. The identical call with { logLevel: "silent" }, as used by REA, reported errors: [] and warnings: []; parseAllDocuments counted two documents. A source-loaded DshCordisDocument consequently reported no editability error and serialized only the first document. These probes corroborate the CLI results; the public CLI edit is the primary failure evidence.

  • Expected source: docs/client-configuration.md:599-606 and docs/installation.md:61-76, scoped to preserving unrelated configuration or refusing unsafe edits; src/application/DshCordisPatch.ts:113-139 implements existing structural edit refusals.
  • Failure source: the executed setup/uninstall fixture and selected output above; src/application/DshCordisYaml.ts:29-55 admits a single parsed document with silent diagnostics, and src/application/DshCordisPatch.ts:298-368 serializes only that document.
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: complete

Typed outcome or recovery expected

For unsupported multi-document input, report an invalid/refused configuration result before publishing an edit and leave the original bytes unchanged. An implementation that deliberately supports the full stream must instead retain every unrelated document and comment. A backup enables recovery but does not make a successful lossy rewrite satisfy the preservation contract.

Analysis or suspected cause

Source-derived and corroborated by execution: parseClientConfiguration dispatches the DSH dialect to parseDshCordisDocument (src/application/ClientConfigurationDocument.ts:231-239). REA supplies logLevel: "silent" to parseDocument. The locked dependency's node_modules/yaml/dist/public-api.js:46-52 only adds MULTIPLE_DOCS when the log level is not silent, so REA's subsequent errors/warnings check cannot detect this stream. The document owner classifies and clones only the returned first document. Setup reaches that owner through explicit client selection, configuration inspection and serialization; uninstall reaches it through configuration preflight, ownership admission and removal. Both complete their existing registration readback checks after unrelated later documents have already been discarded.

Additional context

The same input with REA only in the second document caused uninstall to report skipped / REA registration is absent, leave bytes unchanged and create no backup. This supports the first-document-only interpretation; it does not establish a functional DSH registration defect. NOT_ENOUGH_INFO: DeepSeek Harness's own multi-document loading rules, affected-user prevalence and a last-known-good REA version were not established.

Related closed reports address different triggers: #2730 preserves fields within an operation emptied by removal; #5439, fixed by #6724, preserves diagnostic reasons when expression-ID YAML is refused. Neither describes successful truncation of subsequent documents. Fresh open-and-closed searches and the related discussions did not identify an exact duplicate. The two writers belong in one report because the failing stream admission and preservation contract are shared.

Restoration check

Replay both two-document cases. Passing means each unsafe edit is refused with byte-identical original content, or the successful edit retains the complete unrelated second document and its comment. For a supporting implementation, repeat setup and require byte-identical idempotence and a single REA entry. Keep the single-document control successful and idempotent, and keep malformed operations refused without writes, so a blanket refusal or relaxed parser does not masquerade as restoration. Check the uninstall case independently because correcting only setup leaves the same preservation failure reachable through removal.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: Both supported CLI writers silently discarded unrelated later-document content while returning success, but the trigger is bounded and complete recovery snapshots were observed; severe functional interruption and irreversible loss were not established.
  • Workaround status: available
  • Workaround basis: The script restored the complete original file from each edit's verified backup and confirmed byte equality. Retain that file and avoid further REA edits of this unsupported stream shape until corrected; no claim is made that manually merging lists has been verified in a DSH runtime.
Originally created by @coygeek on GitHub (Oct 26, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/7286 ## 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 `node scripts/rea.mjs setup --client dsh --skill=false --yes --format json` returns exit 0 and `status: ready` for a two-document `cordis.patch.yml`, but rewrites it with only the first document and the new REA entry. The second document's unrelated operation and comment disappear. `uninstall --format json` also discards that second document when removing an REA entry from the first document, while reporting `status: complete` and the DSH item as `removed`. Both operations created backups containing the complete original bytes. Restoring those backups recovered the original fixture. This report concerns preservation of the selected file; whether DeepSeek Harness loads subsequent YAML documents was not established. ## Reproduction Run the following from a prepared source checkout with its existing built runtime, Python 3 and the repository's supported Node.js on PATH. Each CLI invocation receives a fresh environment and disposable home/config/cache/state/temp paths, runs from its own fixture directory and disables skill installation. No DSH client or analysis target is required. The first fixture contains two separate operation lists with an intervening `---`; the uninstall fixture adds the same second document to a registration obtained from the single-document control. The script restores each backup before deleting the disposable fixtures. ```python import json, os, pathlib, shutil, subprocess, tempfile root = pathlib.Path.cwd() node = pathlib.Path(shutil.which('node')).resolve() cli = root / 'scripts' / 'rea.mjs' first = '# keep first comment\n- insert:\n - id: first-tool\n name: caller-plugin\n config:\n command: first-server\n' second = '---\n# keep second comment\n- insert:\n - id: second-tool\n name: caller-plugin\n config:\n command: second-server\n' with tempfile.TemporaryDirectory(prefix='rea-cordis-') as temporary: base = pathlib.Path(temporary) def trial(name, text, args): home = base / name config = home / '.dsh' / 'cordis.patch.yml' config.parent.mkdir(parents=True) config.write_text(text) env = {'PATH': str(node.parent) + os.pathsep + os.defpath, 'HOME': str(home), 'DSH_HOME': str(config.parent), 'DSH_AGENTS_HOME': str(home / '.agents'), 'XDG_CONFIG_HOME': str(home / '.config'), 'XDG_CACHE_HOME': str(home / '.cache'), 'XDG_DATA_HOME': str(home / '.local/share'), 'XDG_STATE_HOME': str(home / '.local/state'), 'TMPDIR': str(home / 'temp'), 'npm_config_prefix': str(home / '.npm-global'), 'NO_COLOR': '1'} (home / 'temp').mkdir() result = subprocess.run([str(node), str(cli), *args, '--format', 'json'], cwd=home, env=env, capture_output=True, text=True, timeout=30) data = json.loads(result.stdout) after = config.read_text() backup = pathlib.Path(str(config) + '.rea.backup') item = next((i for i in data.get('items', []) if i.get('name') == 'dsh'), {}) print(json.dumps({'case': name, 'exit': result.returncode, 'status': data.get('status'), 'appliedActions': data.get('appliedActions'), 'dsh_status': item.get('status'), 'first_retained': 'first-tool' in after, 'second_retained': 'second-tool' in after, 'second_comment_retained': 'keep second comment' in after, 'unchanged': after == text, 'backup_equal': backup.exists() and backup.read_text() == text})) if backup.exists(): config.write_bytes(backup.read_bytes()) print(name + ' recovery_equal=' + str(config.read_bytes() == text.encode())) return after trial('setup-two-documents', first + second, ['setup', '--client', 'dsh', '--skill=false', '--yes']) registered = trial('setup-single-document', first, ['setup', '--client', 'dsh', '--skill=false', '--yes']) trial('uninstall-two-documents', registered + second, ['uninstall']) ``` Each of the two destructive cases ran once in this script and reproduced; a separate local CLI probe reproduced both cases as well. The single-document setup control retained its unrelated operation and first comment. In the separate probe, a repeat of that single-document setup applied no actions and left the file byte-identical. A malformed single-document `- insert: not-a-list` control returned exit 1 / `needs_human`, left the file unchanged and made no backup. ## Expected behavior Editing the DSH home patch must preserve unrelated content or refuse the edit without changing its bytes. `docs/client-configuration.md:599-606` explicitly promises to preserve unrelated patches and comments in this file. `docs/installation.md:61-76` says malformed or unsafe existing configuration blocks the transaction before writes and describes preservation and backups. If multiple YAML documents are unsupported for this configuration, a refusal satisfies that contract; supporting them as functional DSH configuration is not required by this report. ## Impact and scope P2: the reproduced setup and uninstall edits silently remove unrelated file content on a bounded input shape, while reporting success. Both retained complete recovery backups, and recovery was executed successfully. No irreversible loss or functional DSH registration failure was demonstrated. The parser and both writers share this interpretation, but population frequency, other platforms and DSH's own multi-document behavior were not measured. ## REA version 6.8.0; source revision `0d1a454e25db825567782e461da21530bad4b158`. ## Runtime and platform Node.js v24.22.1; macOS arm64; locked yaml dependency 2.9.0. No native analysis engine or DSH session was used. ## Target and artifact context Agent configuration only: `$DSH_HOME/cordis.patch.yml`, with two YAML documents. No binary or captured target artifact. ## Interface CLI ## Earliest failure stage Setup or provider admission ## Relevant output or evidence Selected fields from the executed script's real output: ```text setup-two-documents: exit=0, status=ready, appliedActions=[configured_dsh], first_retained=true, second_retained=false, second_comment_retained=false, unchanged=false, backup_equal=true setup-two-documents recovery_equal=True setup-single-document: exit=0, status=ready, appliedActions=[configured_dsh], first_retained=true, unchanged=false, backup_equal=true uninstall-two-documents: exit=0, status=complete, dsh_status=removed, first_retained=true, second_retained=false, second_comment_retained=false, unchanged=false, backup_equal=true uninstall-two-documents recovery_equal=True ``` The independent parser probe reported `MULTIPLE_DOCS` for the two-document fixture under `yaml.parseDocument` defaults. The identical call with `{ logLevel: "silent" }`, as used by REA, reported `errors: []` and `warnings: []`; `parseAllDocuments` counted two documents. A source-loaded `DshCordisDocument` consequently reported no editability error and serialized only the first document. These probes corroborate the CLI results; the public CLI edit is the primary failure evidence. - Expected source: `docs/client-configuration.md:599-606` and `docs/installation.md:61-76`, scoped to preserving unrelated configuration or refusing unsafe edits; `src/application/DshCordisPatch.ts:113-139` implements existing structural edit refusals. - Failure source: the executed setup/uninstall fixture and selected output above; `src/application/DshCordisYaml.ts:29-55` admits a single parsed document with silent diagnostics, and `src/application/DshCordisPatch.ts:298-368` serializes only that document. - Evidence provenance: observed - Local verification: reproduced - Reproduction completeness: complete ## Typed outcome or recovery expected For unsupported multi-document input, report an invalid/refused configuration result before publishing an edit and leave the original bytes unchanged. An implementation that deliberately supports the full stream must instead retain every unrelated document and comment. A backup enables recovery but does not make a successful lossy rewrite satisfy the preservation contract. ## Analysis or suspected cause Source-derived and corroborated by execution: `parseClientConfiguration` dispatches the DSH dialect to `parseDshCordisDocument` (`src/application/ClientConfigurationDocument.ts:231-239`). REA supplies `logLevel: "silent"` to `parseDocument`. The locked dependency's `node_modules/yaml/dist/public-api.js:46-52` only adds `MULTIPLE_DOCS` when the log level is not silent, so REA's subsequent errors/warnings check cannot detect this stream. The document owner classifies and clones only the returned first document. Setup reaches that owner through explicit client selection, configuration inspection and serialization; uninstall reaches it through configuration preflight, ownership admission and removal. Both complete their existing registration readback checks after unrelated later documents have already been discarded. ## Additional context The same input with REA only in the second document caused uninstall to report `skipped` / `REA registration is absent`, leave bytes unchanged and create no backup. This supports the first-document-only interpretation; it does not establish a functional DSH registration defect. NOT_ENOUGH_INFO: DeepSeek Harness's own multi-document loading rules, affected-user prevalence and a last-known-good REA version were not established. Related closed reports address different triggers: [#2730](https://github.com/morluto/rea/pull/2730) preserves fields within an operation emptied by removal; [#5439](https://github.com/morluto/rea/issues/5439), fixed by [#6724](https://github.com/morluto/rea/pull/6724), preserves diagnostic reasons when expression-ID YAML is refused. Neither describes successful truncation of subsequent documents. Fresh open-and-closed searches and the related discussions did not identify an exact duplicate. The two writers belong in one report because the failing stream admission and preservation contract are shared. ## Restoration check Replay both two-document cases. Passing means each unsafe edit is refused with byte-identical original content, or the successful edit retains the complete unrelated second document and its comment. For a supporting implementation, repeat setup and require byte-identical idempotence and a single REA entry. Keep the single-document control successful and idempotent, and keep malformed operations refused without writes, so a blanket refusal or relaxed parser does not masquerade as restoration. Check the uninstall case independently because correcting only setup leaves the same preservation failure reachable through removal. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: Both supported CLI writers silently discarded unrelated later-document content while returning success, but the trigger is bounded and complete recovery snapshots were observed; severe functional interruption and irreversible loss were not established. - Workaround status: available - Workaround basis: The script restored the complete original file from each edit's verified backup and confirmed byte equality. Retain that file and avoid further REA edits of this unsupported stream shape until corrected; no claim is made that manually merging lists has been verified in a DSH runtime.
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#4326
No description provided.