You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
OpenCode: op=mcp_install still refuses the write on v0.10.4 after the .jsonc targeting fix #1630
Filed from discussion #1560, where this has been reported by two people on two operating systems and has no tracking issue. Reproduced on v0.10.4, today's release.
PR #1575 fixed which file we target: OpenCode reads either opencode.json or opencode.jsonc, we always wrote the .json name, so a user with a .jsonc config got a second file OpenCode ignores. That fix is real and it shipped in v0.10.4.
The write still refuses. We now pick the correct file and then fail to write it. So the targeting was necessary but not sufficient, and the v0.10.4 release notes overstate it — I have a correction to make there.
Reporters and scope
@iandol — Linux, ~/.config/opencode/opencode.jsonc, fails on 0.10.3 and 0.10.4
@gotspatel — Windows, the same op=mcp_install against a .json config
Leading hypothesis
The JSON editor cannot parse a real .jsonc file. JSONC permits // and /* */ comments and trailing commas; a strict JSON parser refuses all three, and the editor fails closed on anything it cannot model. That would explain a correctly-targeted file being refused, and would also explain the Windows .json case if that file carries comments too.
Not yet confirmed — it needs the reporter's actual file, which is why this is an issue rather than a PR.
What would make this diagnosable
#1629 makes the refusal report what the target is (kind and size), which separates "structural refusal" from a permissions or missing-file problem. The real fix is for the editors to carry their own refusal reason out, the way cbm_daemon_ipc_validation_detail() already does for the daemon — nine distinct fail-closed conditions currently collapse into a single -1.
Asks
@iandol, @gotspatel — if you are willing: the first ~20 lines of your opencode.jsonc / opencode.json (redact anything private), and specifically whether it contains comments or trailing commas. That would confirm or kill the hypothesis immediately.
With apologies that this has taken several releases. You have both been unusually patient and precise.
Root cause confirmed on Windows (v0.10.4) — and it is not JSONC comments/trailing commas. My config is a plain .json with no comments, and the write still refused. Two separate JSON-matching bugs, both reproduced in isolation:
1. Extra fields break ownership match (cbm_json_like_match_object_entry)
The MCP-local schema expects exactly 2 members: command + type.
member_count == found_count is enforced — a real OpenCode entry conventionally carries "enabled": true too, which makes 3 members → mismatch → refusal.
On Windows the config conventionally stores "C:\\Users\\...\\codebase-memory-mcp.exe".
The installer normalizes the binary path to forward slashes (C:/Users/.../), and ownership is decided by exact string equality — so C:\... never matches C:/....
Writing the command with forward slashes (["C:/Users/.../codebase-memory-mcp.exe"]) makes the write pass.
Confirmed end-to-end: with enabled removed AND forward-slash command, install -y --force completes EXIT 0 and the OpenCode config is written. The .jsonc targeting fix (#1575) was necessary but insufficient, as the issue says — these two checks are the actual blockers.
Suggested fixes:
For (1): either ignore unknown members when matching owned entries (or make found_count ≤ member_count instead of ==), or document that enabled must be omitted from the entry CBM owns.
For (2): compare paths case-insensitively after normalizing both sides to forward slashes (cbm_path_normalize), rather than exact strcmp.
This is the answer, and it rules out both of my guesses. Thank you — sharing the file settled it in minutes.
Your file is strict, valid JSON (I parsed it to be sure). No comments, no trailing commas. So the JSONC-parsing hypothesis in the issue is wrong, and so was the .jsonc targeting theory once #1575 landed.
Our ownership check requires the entry's key set to match exactly. From src/cli/config_json_like.c:2837:
if (member_count!=found_count|| !captured) {
returnCBM_JSON_LIKE_OBJECT_MISMATCH;
}
We only ever write command and type. Your entry has three keys, we recognise two, member_count != found_count, and the entry is therefore classified as foreign — so we refuse to touch it rather than clobber what looks like someone else's configuration.
The intent is right: an installer that overwrites a user's hand-edited config is worse than one that refuses. The execution is too strict. enabled is a key OpenCode itself supports, so the natural act of toggling your MCP server off and on again permanently locks our installer out of the entry it originally wrote.
That also explains @gotspatel's Windows .json case without needing a second mechanism, and it explains why this survived a fix that was genuinely correct — #1575 fixed which file we open, and the refusal happens after that, on what we find inside.
The fix I intend, in order of preference:
Treat extra keys as compatible when every key we own matches. If command and type are ours, the entry is ours — additional keys the client understands get preserved untouched through the edit. That is the same posture we already take for foreign sibling entries.
Keep exact-match only for the case where a key we own has an unexpected value, which is the real "someone else owns this" signal.
I want to be careful rather than quick here, because loosening an ownership check is exactly how an installer starts overwriting things it should not. It needs a test with your file shape as a fixture, plus one proving we still refuse an entry whose command points somewhere else.
Would you mind if your entry shape (with enabled) goes in as a regression fixture? Paths and provider details redacted — only the structure matters.
And thank you for staying with this across five releases. You have reported install failures on every one from 0.10.0 to 0.10.4, and this file is what finally made it diagnosable.
Correction to my own fix proposal above, before anyone acts on it.
I suggested "treat extra keys as compatible when every key we own matches". On its own that would be worse than the bug, and I should have checked before proposing it.
cbm_json_like_upsert_entry is documented as "Insert or replace entry_key" — it replaces the whole entry, not the fields we own. So relaxing the ownership check alone would make the installer accept your entry and then rewrite it without enabled, silently discarding a setting you had deliberately set. A refusal you can see is strictly better than a quiet deletion you cannot.
So the fix is two parts, and the second is the load-bearing one:
Preserve unknown keys through the edit. The upsert must merge our fields into the existing entry rather than replace it — unknown keys survive byte-identically.
Only then relax ownership to "every field we own matches", with extra keys tolerated.
Doing (2) without (1) trades a visible failure for an invisible one, which is the wrong direction for an installer that writes into files people hand-maintain.
There is a design question inside (1) worth naming: if a key we own has drifted — say type is now something else — do we correct it or refuse? I lean toward correcting only the fields we own and leaving everything else alone, so the entry converges on being valid without us asserting authorship of the parts we did not write.
That is a bigger change than the one-line relaxation I implied, and it is why this stays an issue rather than becoming a PR today. It needs your file shape as a fixture, a test proving enabled survives the edit byte-for-byte, and a test proving we still refuse an entry whose command points at a different binary.
Flagging the correction publicly because the earlier comment reads as a plan, and following it as written would have cost someone their config.
The deferred field-merge landed in 93e9308 (PR #1683), shipping in v0.10.6: annotated entries repair in place on the authorized channels (relocating update; the Windows dead-path probe) with client keys and comments preserved, and ownership comparison is now separator-insensitive (#1582's C:\ vs C:/ refusal). Together with dbd20ea in v0.10.5 this closes out the OpenCode write-refusal class we could reproduce.
Activity
rg6304 commentedon Aug 14, 2026
Root cause confirmed on Windows (v0.10.4) — and it is not JSONC comments/trailing commas. My config is a plain
.jsonwith no comments, and the write still refused. Two separate JSON-matching bugs, both reproduced in isolation:1. Extra fields break ownership match (
cbm_json_like_match_object_entry)command+type.member_count == found_countis enforced — a real OpenCode entry conventionally carries"enabled": truetoo, which makes 3 members → mismatch → refusal.enabledmakes the write pass.2. Backslash paths fail exact
strcmp(cbm_json_mcp_owned_command)"C:\\Users\\...\\codebase-memory-mcp.exe".C:/Users/.../), and ownership is decided by exact string equality — soC:\...never matchesC:/....["C:/Users/.../codebase-memory-mcp.exe"]) makes the write pass.Confirmed end-to-end: with
enabledremoved AND forward-slash command,install -y --forcecompletes EXIT 0 and the OpenCode config is written. The.jsonctargeting fix (#1575) was necessary but insufficient, as the issue says — these two checks are the actual blockers.Suggested fixes:
found_count≤member_countinstead of ==), or document thatenabledmust be omitted from the entry CBM owns.cbm_path_normalize), rather than exactstrcmp.iandol commentedon Aug 14, 2026
My
opencode.jsonc, no comments and I think clean json in fact:{ "$schema": "https://opencode.ai/config.json", "mcp": { "codebase-memory-mcp": { "enabled": true, "type": "local", "command": [ "/home/cog5/.local/bin/codebase-memory-mcp" ] } }, "provider": { "bigmodel": { "npm": "@ai-sdk/openai-compatible", "name": "Zhipu AI (bigmodel.cn)", "options": { "baseURL": "https://open.bigmodel.cn/api/paas/v4" }, "models": { "glm-5.2": { "name": "GLM-5.2" }, "glm-5.1": { "name": "GLM-5.1" } } } } }DeusData commentedon Aug 14, 2026
This is the answer, and it rules out both of my guesses. Thank you — sharing the file settled it in minutes.
Your file is strict, valid JSON (I parsed it to be sure). No comments, no trailing commas. So the JSONC-parsing hypothesis in the issue is wrong, and so was the
.jsonctargeting theory once #1575 landed.The actual cause is visible in your entry:
Our ownership check requires the entry's key set to match exactly. From
src/cli/config_json_like.c:2837:We only ever write
commandandtype. Your entry has three keys, we recognise two,member_count != found_count, and the entry is therefore classified as foreign — so we refuse to touch it rather than clobber what looks like someone else's configuration.The intent is right: an installer that overwrites a user's hand-edited config is worse than one that refuses. The execution is too strict.
enabledis a key OpenCode itself supports, so the natural act of toggling your MCP server off and on again permanently locks our installer out of the entry it originally wrote.That also explains @gotspatel's Windows
.jsoncase without needing a second mechanism, and it explains why this survived a fix that was genuinely correct — #1575 fixed which file we open, and the refusal happens after that, on what we find inside.The fix I intend, in order of preference:
commandandtypeare ours, the entry is ours — additional keys the client understands get preserved untouched through the edit. That is the same posture we already take for foreign sibling entries.I want to be careful rather than quick here, because loosening an ownership check is exactly how an installer starts overwriting things it should not. It needs a test with your file shape as a fixture, plus one proving we still refuse an entry whose
commandpoints somewhere else.Would you mind if your entry shape (with
enabled) goes in as a regression fixture? Paths and provider details redacted — only the structure matters.And thank you for staying with this across five releases. You have reported install failures on every one from 0.10.0 to 0.10.4, and this file is what finally made it diagnosable.
DeusData commentedon Aug 14, 2026
Correction to my own fix proposal above, before anyone acts on it.
I suggested "treat extra keys as compatible when every key we own matches". On its own that would be worse than the bug, and I should have checked before proposing it.
cbm_json_like_upsert_entryis documented as "Insert or replace entry_key" — it replaces the whole entry, not the fields we own. So relaxing the ownership check alone would make the installer accept your entry and then rewrite it withoutenabled, silently discarding a setting you had deliberately set. A refusal you can see is strictly better than a quiet deletion you cannot.So the fix is two parts, and the second is the load-bearing one:
Doing (2) without (1) trades a visible failure for an invisible one, which is the wrong direction for an installer that writes into files people hand-maintain.
There is a design question inside (1) worth naming: if a key we own has drifted — say
typeis now something else — do we correct it or refuse? I lean toward correcting only the fields we own and leaving everything else alone, so the entry converges on being valid without us asserting authorship of the parts we did not write.That is a bigger change than the one-line relaxation I implied, and it is why this stays an issue rather than becoming a PR today. It needs your file shape as a fixture, a test proving
enabledsurvives the edit byte-for-byte, and a test proving we still refuse an entry whosecommandpoints at a different binary.Flagging the correction publicly because the earlier comment reads as a plan, and following it as written would have cost someone their config.
fix(cli): accept an MCP entry the client annotated, instead of refusi…
Merge pull request #1683 from DeusData/fix/install-cluster
DeusData commentedon Aug 17, 2026
The deferred field-merge landed in 93e9308 (PR #1683), shipping in v0.10.6: annotated entries repair in place on the authorized channels (relocating update; the Windows dead-path probe) with client keys and comments preserved, and ownership comparison is now separator-insensitive (#1582's C:\ vs C:/ refusal). Together with dbd20ea in v0.10.5 this closes out the OpenCode write-refusal class we could reproduce.
fix(cli): install-entry compatibility repairs for goose and annotated…