Skip to content

fix(settings-sync-daemon): force-overwrite Helix-owned context_servers - #2425

Merged
lukemarsden merged 1 commit into
mainfrom
fix/settings-sync-helix-owned-mcps
May 13, 2026
Merged

fix(settings-sync-daemon): force-overwrite Helix-owned context_servers#2425
lukemarsden merged 1 commit into
mainfrom
fix/settings-sync-helix-owned-mcps

Conversation

@lukemarsden

Copy link
Copy Markdown
Collaborator

Summary

Tail-end fix for the MCP-cache-contention investigation that PR #2418 addressed. After PR #2418 merged, the user reported chrome-devtools / drone-ci / github still showing "Context server request timeout" in Zed on a long-running spec-task container — even though the container was running the new helix-ubuntu:6de75e image (with all the global MCP binaries) and helix-api was running the new zed_config.go (with chrome-devtools pointing at /usr/bin/chrome-devtools-mcp instead of npx).

Root cause: ~/.config/zed/settings.json is a symlink to /home/retro/work/.zed-state/config/settings.json, which lives on the persistent ZFS-backed /home/retro/work volume. The settings-sync-daemon's mergeSettings deep-merge of context_servers lets USER entries win for every name, so the OLD command: "npx", args: ["chrome-devtools-mcp@latest"] written by the pre-PR-#2418 API last week was treated as a user customization and pinned forever, no matter how many times the new API generated the correct config.

extractUserOverrides had the symmetric bug: it captured the stale on-disk entry as a user override and round-tripped it back to the API.

Fix

New constant HELIX_OWNED_CONTEXT_SERVERS = {chrome-devtools, helix-session, helix-desktop} — the names hardcoded in api/pkg/external-agent/zed_config.go. Two corresponding behavior changes in api/cmd/settings-sync-daemon/main.go:

  1. mergeSettings — skip user-side context_servers entries whose name is in HELIX_OWNED_CONTEXT_SERVERS so Helix's hardcoded definition unconditionally wins. Also strip helix-owned names from the "user-only" branch so a stale on-disk entry can't survive even when the API temporarily emits no context_servers.

  2. extractUserOverrides — never capture helix-owned names as user overrides (otherwise the stale entry would round-trip back to the API and force the next sync to re-write the OLD value to disk, permanently nullifying the force-overwrite from Website #1).

User-configured MCPs (e.g. drone-ci, github, custom servers from project skills or app config) are NOT in the helix-owned set — those legitimately can be edited by the user in their on-disk settings.json and must round-trip.

Tests

  • TestMergeSettings_HelixOwnedContextServersWin (4 sub-tests):
    • force-overwrite chrome-devtools when user has stale npx version
    • force-overwrite helix-session when user has stale session_id URL/token
    • user-configured drone-ci still wins (positive control — non-helix-owned MCP)
    • strips helix-owned names even when helix temporarily has no context_servers
  • TestExtractUserOverrides_SkipsHelixOwnedContextServers (2 sub-tests):
    • stale on-disk helix-owned entries are not captured as user overrides
    • non-helix user overrides still round-trip (positive control)

Verified regression-test power: with both guards commented out (if false && HELIX_OWNED_CONTEXT_SERVERS[name] {), all 5 of the 6 force-overwrite/skip sub-tests fail with the diagnostic messages in the assertions — exactly the regression we're guarding against.

Test plan

  • CGO_ENABLED=0 go test -v -run "TestMergeSettings_HelixOwned|TestExtractUserOverrides_SkipsHelix" ./api/cmd/settings-sync-daemon/ -count=1 — all 6 sub-tests pass.
  • Disabled both guards, re-ran — 5 of 6 fail with the expected diagnostic. Restored.
  • go build ./api/cmd/settings-sync-daemon/ clean.
  • CI (Drone)

Follow-up after merge

The user's affected long-running container (spt_01kqc4ev5rt9rknk6g8dbkzj9a) needs a ./stack build-ubuntu + new session for the daemon binary to be replaced (per CLAUDE.md: settings-sync-daemon does not hot-reload). Once the new daemon runs, it'll force-overwrite the stale chrome-devtools/helix-session/helix-desktop entries on the next sync, and Zed will pick up the new /usr/bin/chrome-devtools-mcp config.

Related

🤖 Generated with Claude Code

When Helix changes the hardcoded definition of a Helix-owned
context_server (e.g. PR #2418 switched chrome-devtools from `npx
chrome-devtools-mcp@latest` to `/usr/bin/chrome-devtools-mcp`), the
daemon's deep-merge in mergeSettings was treating the on-disk OLD entry
as a "user override" and letting it win. This pinned the broken `npx`
config in long-running containers' persisted settings.json forever and
re-produced the 180s `chrome-devtools context server failed to start:
Context server request timeout` errors that PR #2418 was meant to fix
— even on containers running the new image and new API binary.

Bug observed in https://meta.helix.ml/orgs/helix/projects/prj_01kg02vqqyg178c1n2ydscn5fb/tasks/spt_01kqc4ev5rt9rknk6g8dbkzj9a
shortly after PR #2418 merged: chrome-devtools / drone-ci / github all
showed "Context server request timeout" in Zed, despite the container
running helix-ubuntu:6de75e (built post-merge with the new global MCP
binaries) and helix-api running the new zed_config.go.

Fix: introduce HELIX_OWNED_CONTEXT_SERVERS = {chrome-devtools,
helix-session, helix-desktop} — the set of context_server names
hardcoded in api/pkg/external-agent/zed_config.go. Two corresponding
behavior changes in api/cmd/settings-sync-daemon/main.go:

1. mergeSettings: skip user-side context_server entries whose name is
   in HELIX_OWNED_CONTEXT_SERVERS so Helix's hardcoded definition
   unconditionally wins. Also strip helix-owned names from the
   "user-only" branch so a stale on-disk entry can't survive even when
   the API temporarily emits no context_servers.

2. extractUserOverrides: never capture helix-owned names as user
   overrides (otherwise the stale entry would round-trip back to the
   API and force the next sync to re-write the OLD value to disk,
   permanently nullifying the force-overwrite from #1).

User-configured MCPs (e.g. drone-ci, github, custom servers from
project skills or app config) are NOT in the helix-owned set —
those legitimately can be edited by the user in their on-disk
settings.json and must round-trip.

Tests:

- TestMergeSettings_HelixOwnedContextServersWin (4 sub-tests):
  force-overwrite chrome-devtools and helix-session when user has
  stale entries, allow user-configured drone-ci to win, strip
  helix-owned names even when helix has no servers.
- TestExtractUserOverrides_SkipsHelixOwnedContextServers (2 sub-tests):
  stale on-disk helix-owned entries are not captured as user overrides;
  non-helix user overrides still round-trip.

All sub-tests verified to FAIL when both guards are commented out
(by replacing `if HELIX_OWNED_CONTEXT_SERVERS[name] {` with
`if false && HELIX_OWNED_CONTEXT_SERVERS[name] {` and re-running).

Full diagnosis: design/2026-05-13-mcp-cache-contention-and-duplicate-claude-spawn.md

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@lukemarsden
lukemarsden force-pushed the fix/settings-sync-helix-owned-mcps branch 2 times, most recently from 1b5a87b to c6a8ae7 Compare May 13, 2026 14:13
@lukemarsden
lukemarsden enabled auto-merge May 13, 2026 14:14
@lukemarsden
lukemarsden merged commit 00dcb20 into main May 13, 2026
1 check passed
@lukemarsden
lukemarsden deleted the fix/settings-sync-helix-owned-mcps branch May 13, 2026 14:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant