Skip to content

OpenCode integration dead-ends in permanent 'conflict' after any unrelated user edit to opencode.json (e.g. adding an MCP server) #1631

Description

@RobinBially

Client or integration

Other

Area

Other

Summary

The OpenCode (and any non-omp) client integration dead-ends in a permanent conflict state as soon as the user makes any edit to their own config file — even edits that never touch the opencodex-owned block. Adding an MCP server to ~/.config/opencode/opencode.json is enough: the dashboard shows Konflikt, the toggle turns off, and there is no non-destructive recovery path. The only UI option, restoring the snapshot, would revert the user's newer edits; the only clean fix is manual surgery (delete the provider.opencodex block by hand, then re-enable).

I expected: edits outside the opencodex-owned block (normal, frequent usage of one's own config — MCP servers, instructions, permissions) either don't trip the conflict state, or there is a supported, non-destructive way to re-apply/re-own from the dashboard or CLI.

Root causeclassifyIntegration (src/integrations/state.ts) checks two fingerprints:

if (clientId !== "omp" && fingerprint(input.fileText ?? "") !== input.record.fileFingerprint) {
  return { state: "conflict", reason: "foreign-edit" };   // ← fires on ANY file edit
}
if (recordedFragmentFingerprint(input.parsed, input.record) !== input.record.blockFingerprint) {
  return { state: "conflict", reason: "foreign-edit" };   // ← the actual tamper check
}

The whole-file check fires even when the second check proves the owned block is untouched. I understand this is deliberate (the test "whole-document serializers still conflict on an unrelated source edit" pins it): non-omp clients rewrite the whole document on apply, so a rewrite could normalize a user's formatting or drop comments, and the classifier fails closed. But the practical effect is that the integration breaks permanently on the first config edit the user makes themselves, with no recovery path.

Proposal — two options, not mutually exclusive; I'm happy to send a PR with regression tests for whichever direction fits the project's invariants:

  • A. Confirm-gated re-apply from conflict when the owned block is intact (mirrors the existing restore --confirm-drift pattern). If recordedFragmentFingerprint(parsed, record) === record.blockFingerprint, apply merges into the current parsed document and a snapshot is taken first, so no user content can be lost — only formatting may normalize. CLI: ocx integration client enable --client opencode --confirm-drift (or similar); dashboard: a confirm dialog instead of the dead-end badge.
  • B. Classifier refinement for lossless cases: when the owned block is intact and the file round-trips byte-identically through the client's serializer (fingerprint(serializeDocument(parsed, format)) === fingerprint(fileText)), classify current/stale instead of conflict — rewriting such a file provably cannot destroy formatting or comments. Files with custom formatting/comments still fail closed and would use path A.

Reproduction

  1. ocx integration client enable --client opencode → state current.
  2. Add any MCP server (or any other key) to ~/.config/opencode/opencode.json without touching provider.opencodex, e.g.:
    "mcp": { "playwright": { "type": "local", "command": ["npx", "-y", "@playwright/mcp@latest"], "enabled": true } }
  3. Dashboard → Integrations → OpenCode now shows Konflikt, toggle off.
  4. ocx integration client enable --client opencode refuses with <path> changed after opencodex wrote it.
  5. No non-destructive way out: "restore this state" reverts the user's own edit from step 2; the only clean recovery is deleting the provider.opencodex block manually and enabling again.

Version

2.14.2

Operating system

macOS 27 (arm64)

Provider and model

No response

Logs or error output

$ ocx integration client enable --client opencode
refused: /Users/<user>/.config/opencode/opencode.json changed after opencodex wrote it (conflict)

$ ocx integration client status --client opencode
clientId: opencode
state: conflict

Screenshots and supporting files

No response

Redacted configuration

No response

Checks

  • I searched existing issues and documentation.
  • I removed secrets, tokens, account details, request credentials, and personal data.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingtoolstool_calls, MCP, web-search / sidecar tools

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions