[GH-ISSUE #6573] [Bug] Setup still leaves inherited OpenCode V2 registration disabled when the upper file has no native table #3961

Closed
opened 2026-10-31 00:45:37 +00:00 by bsakaguchi · 0 comments
Owner

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

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

Executed the production setup configuration functions and registration reader against three isolated OpenCode user configurations on current main. When the lower config.json contains a disabled native V2 registration, an upper opencode.json containing only {"theme":"system"} makes setup write a V1 mcp.rea entry with enabled: true. Configuration returns configured, and subsequent setup inspection returns already_current, but REA's effective registration inspection still returns activation: "disabled" and state: "stale".

An absent upper file has the same result. An upper file containing {"mcp":{"servers":{}},"theme":"system"} is the passing control: setup writes native mcp.servers.rea.disabled: false, and effective registration inspection reports enabled/aligned. The lower file remains byte-identical in all three cases.

These are executed production-function results, not an executed CLI setup command or real OpenCode session. This is a residual main-branch case after the fix for #3318; the explicit-native-table fixture in that report now passes, while the ordinary empty/missing-upper-table cases do not.

Reproduction

The checkout already had its locked dependencies installed. No build, installation, global setup, or real client modification was performed. Each case ran once before drafting. The caller set a clean environment containing only HOME, USERPROFILE, PATH (with the real Node 24.22.1 directory first), and npm_config_prefix under the trial home, then ran from inside that home. Thus every client, provider, Java, and XDG override was absent.

Save the following executed probe as probe.ts outside the checkout:

import { mkdir, readFile, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import { pathToFileURL } from 'node:url';

(async () => {
  const repo = process.argv[2]!;
  const mode = process.argv[3]!;
  const { supportedClients } = await import(pathToFileURL(join(repo, 'src/application/SupportedClients.ts')).href);
  const { inspectClientConfiguration, configureClientConfiguration } = await import(pathToFileURL(join(repo, 'src/application/SetupClientConfiguration.ts')).href);
  const { readClientRegistrationInspections } = await import(pathToFileURL(join(repo, 'src/application/ClientRegistrationStatus.ts')).href);
  const { readClientConfigurationFiles, effectiveClientConfiguration } = await import(pathToFileURL(join(repo, 'src/application/ClientConfigurationFiles.ts')).href);
  const home = process.env.HOME!;
  const dir = join(home, '.config/opencode');
  await mkdir(dir, { recursive: true });
  const lower = join(dir, 'config.json');
  const upper = join(dir, 'opencode.json');
  const command = ['npx', '-y', 'rea-agents@6.7.0', 'mcp'];
  const original = JSON.stringify({mcp:{servers:{rea:{type:'local', command, disabled:true, environment:{REA_MCP_OUTPUT_SCHEMA_PROFILE:'off'}}, other:{type:'local',command:['other-server'],disabled:true}}}});
  await writeFile(lower, original);
  if(mode !== 'missing') await writeFile(upper, JSON.stringify(mode === 'v2-control' ? {mcp:{servers:{}},theme:'system'} : {theme:'system'}));
  const client = supportedClients(home, 'linux', process.env).find((c:any) => c.name === 'opencode');
  const before = await inspectClientConfiguration(client, {}, command);
  const result = await configureClientConfiguration(client, {}, command);
  const files = await readClientConfigurationFiles(client);
  const effective = files.ok ? effectiveClientConfiguration(files.value) : files;
  const inspections = (await readClientRegistrationInspections(home, join(repo,'scripts/rea.mjs'), {environment:process.env})).filter((x:any) => x.status.client === 'opencode');
  const after = await inspectClientConfiguration(client, {}, command);
  console.log(JSON.stringify({mode,node:process.version,before,result,after,lowerPreserved: (await readFile(lower,'utf8'))===original,upper:JSON.parse(await readFile(upper,'utf8')),effective,inspections},null,2));
})().catch(e=>{console.error(e);process.exitCode=1});

From the checkout root, with Node 24.22.1 selected, the parameterized equivalent of the executed isolated invocation is:

task_repo=$PWD
task_node_bin=$(dirname "$(node -p process.execPath)")
task_probe=/absolute/path/to/probe.ts
for task_mode in v2-control plain missing; do
  task_home=$(mktemp -d)
  (
    cd "$task_home"
    env -i HOME="$task_home" USERPROFILE="$task_home"       PATH="$task_node_bin:/usr/bin:/bin"       npm_config_prefix="$task_home/.npm-global"       "$task_repo/node_modules/.bin/tsx" "$task_probe" "$task_repo" "$task_mode"
  )
done

plain uses a valid upper settings file without an MCP table. missing omits the upper file. v2-control creates its explicit native table. None of the commands launch the registered npx server.

Expected behavior

Approved setup must produce an enabled effective REA registration and agree with the registration reader about whether another update is required. A native table inherited from an applicable user layer must not leave a successful write followed by an already_current inspection and a disabled/stale effective registration. Preserve unrelated settings, lower-layer contents, and the backup behavior.

The documented OpenCode configuration contract says setup uses native V2 tables where present and doctor checks the merged user registration. The existing regression test requires enabled/aligned setup over a lower disabled registration, but initializes the upper file with its own native table. The missing-table case should satisfy that same effective activation contract.

Impact and scope

P2: a supported layered OpenCode configuration remains disabled in REA's own registration view after setup writes it, and the next inspection offers no repair action. This is bounded to the inherited native registration plus an upper file without a native table. No data loss was observed; all lower contents were preserved. The executed scope is Linux x64 and the three synthetic user-configuration fixtures. Public CLI exit/status, real OpenCode behavior, other clients, and other platforms were not executed.

A practical workaround is to give the upper file an explicit mcp.servers object before setup. The fresh-home control with that shape was executed and produced an enabled/aligned registration while preserving the lower file and theme. Editing and recovering an already-failed real client configuration was not performed.

REA version

Main source 46111dd091f2ae01baab9eb119b53b9bd309a05b; package metadata 6.7.0. Executed on main only. The latest published tag rea-agents-6.7.0 (27dc42d10249f0410904a75c0997842eb03b931c) was not runtime-tested. The passing native-table control uses the unreleased c592ea6baca075c4d232125104144e03d9a85dcb change for #3318; this report does not imply that fix shipped.

Runtime and platform

Node.js v24.22.1; Linux x86_64; shared locked dependencies loaded with the checkout's tsx. No Hopper, Ghidra, or native client was invoked.

Target and artifact context

Synthetic OpenCode user settings in isolated .config/opencode/config.json and .config/opencode/opencode.json. The lower file contains one disabled native REA registration, an unrelated server, and a retained environment override. No analysis target or artifact.

Interface

Package or setup

Earliest failure stage

Setup or provider admission

Relevant output or evidence

Compact projection of the executed output:

[
  {
    "mode": "v2-control",
    "before": "update",
    "configuration": "configured",
    "after": "already_current",
    "writtenPath": "mcp.servers.rea",
    "writtenActivation": {"disabled": false},
    "effectiveActivation": "enabled",
    "registrationState": "aligned",
    "lowerPreserved": true
  },
  {
    "mode": "plain",
    "before": "update",
    "configuration": "configured",
    "after": "already_current",
    "writtenPath": "mcp.rea",
    "writtenActivation": {"enabled": true},
    "effectiveActivation": "disabled",
    "registrationState": "stale",
    "lowerPreserved": true
  },
  {
    "mode": "missing",
    "before": "create",
    "configuration": "configured",
    "after": "already_current",
    "writtenPath": "mcp.rea",
    "writtenActivation": {"enabled": true},
    "effectiveActivation": "disabled",
    "registrationState": "stale",
    "lowerPreserved": true
  }
]

In both failures the effective server contains disabled: true from the lower file and enabled: true from the newly written upper file. These output projections were read from the recorded initial probe; no post-draft rerun or independent re-verification was performed.

Typed outcome or recovery expected

After successful configuration, return an enabled/aligned effective registration. If setup cannot repair the effective layer combination, retain an actionable update/refusal instead of already_current. Checking only the written fragment must not suppress the remaining effective activation failure.

Analysis or suspected cause

Source-derived: desired registration generation selects the target fragment's dialect, even though it also computes an effective layered view. The parser labels a file without its own native table as V1. The V1 entry sets enabled: true, while only V2 supplies disabled: false. The effective merge retains omitted lower scalar values, so disabled: true survives. The current-registration gate compares only its provided fragment, accounting for the empty subsequent repair plan.

Additional context

Closest existing report: closed #3318, fixed by #3469 in c592ea6baca075c4d232125104144e03d9a85dcb. That report's explicit upper native-table fixture is the passing control here. This follow-up supplies a narrower, still-failing input: no native table in the upper file. It may be appropriate to reopen/extend #3318 rather than create another issue; maintainer/submitter deduplication should preserve this relationship.

Also compared the supplied open-issue inventory and searched open and closed issues with opencode disabled, opencode layered, opencode V2, opencode "missing" "table", opencode "dialect", and "setup" "current" "opencode". No separate report of this absent-upper-table case was found. #816 concerns general V1/V2 compatibility; #3690 concerns npx workspace EOVERRIDE, which this probe never invokes.

AI-assisted source audit and one initial three-fixture production-function probe. No source changes, AutoReview, independent reviewer, or post-draft verification.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: The executed production configuration path writes an entry and reports it current while the effective registration reader still reports disabled/stale for two ordinary layered settings shapes. The failure is bounded and no data loss was observed; public CLI and actual OpenCode execution remain untested.
  • Workaround status: available
  • Workaround basis: The executed explicit-upper-native-table control produced enabled/aligned results with the same disabled lower input and preserved unrelated contents. Recovery of an already-failed real configuration was not executed.
Originally created by @coygeek on GitHub (Oct 23, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6573 ## 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 Executed the production setup configuration functions and registration reader against three isolated OpenCode user configurations on current main. When the lower `config.json` contains a disabled native V2 registration, an upper `opencode.json` containing only `{"theme":"system"}` makes setup write a V1 `mcp.rea` entry with `enabled: true`. Configuration returns `configured`, and subsequent setup inspection returns `already_current`, but REA's effective registration inspection still returns `activation: "disabled"` and `state: "stale"`. An absent upper file has the same result. An upper file containing `{"mcp":{"servers":{}},"theme":"system"}` is the passing control: setup writes native `mcp.servers.rea.disabled: false`, and effective registration inspection reports enabled/aligned. The lower file remains byte-identical in all three cases. These are executed production-function results, not an executed CLI setup command or real OpenCode session. This is a residual main-branch case after the fix for #3318; the explicit-native-table fixture in that report now passes, while the ordinary empty/missing-upper-table cases do not. ## Reproduction The checkout already had its locked dependencies installed. No build, installation, global setup, or real client modification was performed. Each case ran once before drafting. The caller set a clean environment containing only `HOME`, `USERPROFILE`, `PATH` (with the real Node 24.22.1 directory first), and `npm_config_prefix` under the trial home, then ran from inside that home. Thus every client, provider, Java, and XDG override was absent. Save the following executed probe as `probe.ts` outside the checkout: ```ts import { mkdir, readFile, writeFile } from 'node:fs/promises'; import { join } from 'node:path'; import { pathToFileURL } from 'node:url'; (async () => { const repo = process.argv[2]!; const mode = process.argv[3]!; const { supportedClients } = await import(pathToFileURL(join(repo, 'src/application/SupportedClients.ts')).href); const { inspectClientConfiguration, configureClientConfiguration } = await import(pathToFileURL(join(repo, 'src/application/SetupClientConfiguration.ts')).href); const { readClientRegistrationInspections } = await import(pathToFileURL(join(repo, 'src/application/ClientRegistrationStatus.ts')).href); const { readClientConfigurationFiles, effectiveClientConfiguration } = await import(pathToFileURL(join(repo, 'src/application/ClientConfigurationFiles.ts')).href); const home = process.env.HOME!; const dir = join(home, '.config/opencode'); await mkdir(dir, { recursive: true }); const lower = join(dir, 'config.json'); const upper = join(dir, 'opencode.json'); const command = ['npx', '-y', 'rea-agents@6.7.0', 'mcp']; const original = JSON.stringify({mcp:{servers:{rea:{type:'local', command, disabled:true, environment:{REA_MCP_OUTPUT_SCHEMA_PROFILE:'off'}}, other:{type:'local',command:['other-server'],disabled:true}}}}); await writeFile(lower, original); if(mode !== 'missing') await writeFile(upper, JSON.stringify(mode === 'v2-control' ? {mcp:{servers:{}},theme:'system'} : {theme:'system'})); const client = supportedClients(home, 'linux', process.env).find((c:any) => c.name === 'opencode'); const before = await inspectClientConfiguration(client, {}, command); const result = await configureClientConfiguration(client, {}, command); const files = await readClientConfigurationFiles(client); const effective = files.ok ? effectiveClientConfiguration(files.value) : files; const inspections = (await readClientRegistrationInspections(home, join(repo,'scripts/rea.mjs'), {environment:process.env})).filter((x:any) => x.status.client === 'opencode'); const after = await inspectClientConfiguration(client, {}, command); console.log(JSON.stringify({mode,node:process.version,before,result,after,lowerPreserved: (await readFile(lower,'utf8'))===original,upper:JSON.parse(await readFile(upper,'utf8')),effective,inspections},null,2)); })().catch(e=>{console.error(e);process.exitCode=1}); ``` From the checkout root, with Node 24.22.1 selected, the parameterized equivalent of the executed isolated invocation is: ```bash task_repo=$PWD task_node_bin=$(dirname "$(node -p process.execPath)") task_probe=/absolute/path/to/probe.ts for task_mode in v2-control plain missing; do task_home=$(mktemp -d) ( cd "$task_home" env -i HOME="$task_home" USERPROFILE="$task_home" PATH="$task_node_bin:/usr/bin:/bin" npm_config_prefix="$task_home/.npm-global" "$task_repo/node_modules/.bin/tsx" "$task_probe" "$task_repo" "$task_mode" ) done ``` `plain` uses a valid upper settings file without an MCP table. `missing` omits the upper file. `v2-control` creates its explicit native table. None of the commands launch the registered `npx` server. ## Expected behavior Approved setup must produce an enabled effective REA registration and agree with the registration reader about whether another update is required. A native table inherited from an applicable user layer must not leave a successful write followed by an `already_current` inspection and a disabled/stale effective registration. Preserve unrelated settings, lower-layer contents, and the backup behavior. The [documented OpenCode configuration contract](https://github.com/morluto/rea/blob/46111dd091f2ae01baab9eb119b53b9bd309a05b/docs/client-configuration.md#L433-L441) says setup uses native V2 tables where present and doctor checks the merged user registration. The [existing regression test](https://github.com/morluto/rea/blob/46111dd091f2ae01baab9eb119b53b9bd309a05b/tests/acceptance/setup/opencodeV2ClientSetup.test.ts#L68-L129) requires enabled/aligned setup over a lower disabled registration, but initializes the upper file with its own native table. The missing-table case should satisfy that same effective activation contract. ## Impact and scope P2: a supported layered OpenCode configuration remains disabled in REA's own registration view after setup writes it, and the next inspection offers no repair action. This is bounded to the inherited native registration plus an upper file without a native table. No data loss was observed; all lower contents were preserved. The executed scope is Linux x64 and the three synthetic user-configuration fixtures. Public CLI exit/status, real OpenCode behavior, other clients, and other platforms were not executed. A practical workaround is to give the upper file an explicit `mcp.servers` object before setup. The fresh-home control with that shape was executed and produced an enabled/aligned registration while preserving the lower file and theme. Editing and recovering an already-failed real client configuration was not performed. ## REA version Main source `46111dd091f2ae01baab9eb119b53b9bd309a05b`; package metadata `6.7.0`. Executed on main only. The latest published tag `rea-agents-6.7.0` (`27dc42d10249f0410904a75c0997842eb03b931c`) was not runtime-tested. The passing native-table control uses the unreleased `c592ea6baca075c4d232125104144e03d9a85dcb` change for #3318; this report does not imply that fix shipped. ## Runtime and platform Node.js `v24.22.1`; Linux x86_64; shared locked dependencies loaded with the checkout's `tsx`. No Hopper, Ghidra, or native client was invoked. ## Target and artifact context Synthetic OpenCode user settings in isolated `.config/opencode/config.json` and `.config/opencode/opencode.json`. The lower file contains one disabled native REA registration, an unrelated server, and a retained environment override. No analysis target or artifact. ## Interface Package or setup ## Earliest failure stage Setup or provider admission ## Relevant output or evidence Compact projection of the executed output: ```json [ { "mode": "v2-control", "before": "update", "configuration": "configured", "after": "already_current", "writtenPath": "mcp.servers.rea", "writtenActivation": {"disabled": false}, "effectiveActivation": "enabled", "registrationState": "aligned", "lowerPreserved": true }, { "mode": "plain", "before": "update", "configuration": "configured", "after": "already_current", "writtenPath": "mcp.rea", "writtenActivation": {"enabled": true}, "effectiveActivation": "disabled", "registrationState": "stale", "lowerPreserved": true }, { "mode": "missing", "before": "create", "configuration": "configured", "after": "already_current", "writtenPath": "mcp.rea", "writtenActivation": {"enabled": true}, "effectiveActivation": "disabled", "registrationState": "stale", "lowerPreserved": true } ] ``` In both failures the effective server contains `disabled: true` from the lower file and `enabled: true` from the newly written upper file. These output projections were read from the recorded initial probe; no post-draft rerun or independent re-verification was performed. ## Typed outcome or recovery expected After successful configuration, return an enabled/aligned effective registration. If setup cannot repair the effective layer combination, retain an actionable update/refusal instead of `already_current`. Checking only the written fragment must not suppress the remaining effective activation failure. ## Analysis or suspected cause Source-derived: [desired registration generation selects the target fragment's dialect](https://github.com/morluto/rea/blob/46111dd091f2ae01baab9eb119b53b9bd309a05b/src/application/SetupClientConfiguration.ts#L732-L745), even though it also computes an effective layered view. [The parser labels a file without its own native table as V1](https://github.com/morluto/rea/blob/46111dd091f2ae01baab9eb119b53b9bd309a05b/src/application/ClientConfigurationDocument.ts#L248-L275). The [V1 entry sets `enabled: true`, while only V2 supplies `disabled: false`](https://github.com/morluto/rea/blob/46111dd091f2ae01baab9eb119b53b9bd309a05b/src/application/ClientConfigurationDocument.ts#L1653-L1689). The [effective merge retains omitted lower scalar values](https://github.com/morluto/rea/blob/46111dd091f2ae01baab9eb119b53b9bd309a05b/src/application/ClientConfigurationFiles.ts#L164-L196), so `disabled: true` survives. The [current-registration gate compares only its provided fragment](https://github.com/morluto/rea/blob/46111dd091f2ae01baab9eb119b53b9bd309a05b/src/application/SetupClientConfiguration.ts#L720-L730), accounting for the empty subsequent repair plan. ## Additional context Closest existing report: closed [#3318](https://github.com/morluto/rea/issues/3318), fixed by [#3469](https://github.com/morluto/rea/pull/3469) in `c592ea6baca075c4d232125104144e03d9a85dcb`. That report's explicit upper native-table fixture is the passing control here. This follow-up supplies a narrower, still-failing input: no native table in the upper file. It may be appropriate to reopen/extend #3318 rather than create another issue; maintainer/submitter deduplication should preserve this relationship. Also compared the supplied open-issue inventory and searched open and closed issues with `opencode disabled`, `opencode layered`, `opencode V2`, `opencode "missing" "table"`, `opencode "dialect"`, and `"setup" "current" "opencode"`. No separate report of this absent-upper-table case was found. #816 concerns general V1/V2 compatibility; #3690 concerns `npx` workspace `EOVERRIDE`, which this probe never invokes. AI-assisted source audit and one initial three-fixture production-function probe. No source changes, AutoReview, independent reviewer, or post-draft verification. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: The executed production configuration path writes an entry and reports it current while the effective registration reader still reports disabled/stale for two ordinary layered settings shapes. The failure is bounded and no data loss was observed; public CLI and actual OpenCode execution remain untested. - Workaround status: available - Workaround basis: The executed explicit-upper-native-table control produced enabled/aligned results with the same disabled lower input and preserved unrelated contents. Recovery of an already-failed real configuration was not executed.
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#3961
No description provided.