fix(opencode): prevent ACP session/new hang from inherited MCP servers - #789
fix(opencode): prevent ACP session/new hang from inherited MCP servers#789dsebban wants to merge 6 commits into
Conversation
OpenCode merges global/project MCP entries into ACP session/new and waits for them to connect. A hanging non-RepoPrompt server (or recursive RepoPrompt CE CLI entry) can exceed the 30s bootstrap timeout after initialize succeeds. Disable discovered inherited MCP names in the process-ephemeral OPENCODE_CONFIG_CONTENT overlay and improve startup timeout guidance.
baron
left a comment
There was a problem hiding this comment.
Blocking on exact head 716d2e10940614422ff2603ea51e5325bec9779c.
The mitigation only discovers the global/current-directory opencode.json paths plus .opencode/opencode.json, and parses strict JSON (OpenCodeIntegrationConfiguration.swift:224-261). OpenCode's configuration authority also supports JSONC, OPENCODE_CONFIG, ancestor project configs, .opencode configs, and inline content; those sources are merged rather than replaced. An inherited MCP server from any omitted source can therefore remain enabled and reproduce the session/new hang. The current tests cover only the narrow JSON path (OpenCodeIntegrationConfigurationTests.swift:6-90).
Please make the overlay match the actual loader sources (or disable project discovery through an authoritative supported mechanism), and add JSONC/custom-path/ancestor-source regressions. Official config authority: https://opencode.ai/docs/config
baron
left a comment
There was a problem hiding this comment.
Blocking issue: the neutralization pass does not discover the same config sources that OpenCode actually inherits.
discoverInheritedMCPServerNames currently checks only global opencode.json, <cwd>/opencode.json, and <cwd>/.opencode/opencode.json, all via strict JSONSerialization. OpenCode v1.18.16 also loads global config.json and opencode.jsonc, walks ancestor directories to the worktree boundary for opencode.json[c] and .opencode/opencode.json[c], and accepts OPENCODE_CONFIG / OPENCODE_CONFIG_DIR sources. A hanging MCP defined in any of those supported locations is absent from the overlay, so session/new can still take the same timeout path this PR is intended to prevent.
Please either mirror the authoritative OpenCode source-resolution boundary (including JSONC and env-directed sources) or isolate the launched process from inherited MCP startup through an upstream-supported mechanism. Add coverage for at least global JSONC, an ancestor project config, and env-directed config. The new tests currently cover only the three paths implemented here.
Upstream v1.18.16 evidence:
- global/custom/project/inline load order: https://github.com/anomalyco/opencode/blob/a3647eb025c7615159d417dcc49fc39fdaeba65b/packages/opencode/src/config/config.ts#L246-L260
- custom and upward project loading: https://github.com/anomalyco/opencode/blob/a3647eb025c7615159d417dcc49fc39fdaeba65b/packages/opencode/src/config/config.ts#L398-L429
- ancestor path resolution: https://github.com/anomalyco/opencode/blob/a3647eb025c7615159d417dcc49fc39fdaeba65b/packages/opencode/src/config/paths.ts#L10-L40
dsebban
left a comment
There was a problem hiding this comment.
Follow-up notes for the two blocking config-authority reviews.
| // content is replaced rather than inherited and cannot contribute an additional MCP name. | ||
| var seen = Set<String>() | ||
| return urls | ||
| .map(\.standardizedFileURL.path) |
There was a problem hiding this comment.
Addressed the loader-authority gap here: inherited MCP discovery now covers global config.json / opencode.json / opencode.jsonc, OPENCODE_CONFIG, upward project opencode.json[c] and .opencode/opencode.json[c] to the worktree boundary, OPENCODE_CONFIG_DIR, and JSONC parsing. Parent OPENCODE_CONFIG_CONTENT is intentionally not scanned because the child launch replaces that variable with RepoPrompt's overlay.
| XCTAssertEqual((mcp["Other"] as? [String: Any])?["enabled"] as? Bool, false) | ||
| XCTAssertEqual((mcp["RepoPromptCE"] as? [String: Any])?["enabled"] as? Bool, false) | ||
| } | ||
|
|
There was a problem hiding this comment.
Regression coverage now exercises the review-requested authorities independently: global JSONC, ancestor project + .opencode, env-directed OPENCODE_CONFIG / OPENCODE_CONFIG_DIR, the project-disable flag, and the inline-content replacement contract.
Audit disposition — remaining routing blocker (2026-08-14)The inherited-MCP diagnosis is credible, but the overlay skips the managed RepoPrompt server name using a case-insensitive comparison while JSON object keys are case-sensitive. An inherited differently cased recursive entry can therefore be omitted from the disabling overlay and remain active. Disable each exact inherited key except the exact key that the overlay replaces, and add case-variant/duplicate-name tests. Malformed, oversized, unreadable, or concurrently changed configuration should also produce conservative startup guidance rather than silently disappearing from discovery. Please fix those cases before merge. |
Deep-review assessment — 2026-08-14Disposition: block pending exact-key neutralization and conservative discovery failure handling. The reproduced root cause is convincing, and neutralizing inherited MCP entries in the process-ephemeral overlay is the right boundary. However, inherited names equal to Disable every exact discovered key first, then install the canonical managed |
Summary
session/newwas timing out after 30s with initialize succeeding (no matching ACP response; last stdout is the initialize result).mcpentries into ACP startup and waits for them to connect. RepoPrompt only overrode the same-nameRepoPromptCEentry inOPENCODE_CONFIG_CONTENT, so other hanging MCP servers (and recursive RepoPrompt CE CLI configs) could still block bootstrap.Fix
~/.config/opencode/opencode.json/ XDG) and project (opencode.json,.opencode/opencode.json) configs.Validation
make dev-test FILTER=OpenCodeIntegrationConfigurationTests✅ (3 tests)make dev-test FILTER=OpenCodeACPLaunchResolverTests✅ (11 tests)session/new; neutralizing discovered names returned a session in ~0.3s.Note
Not part of #782. Separate PR off
main.