Skip to content

registry: ReplaceByTag rewrites mcpServerTools + in-flight gate (audit #15) #656

Description

@dennisonbertram

Intent

Fix Registry.ReplaceByTag desyncing the mcpServerTools map on MCP-tagged tool hot-reload, which leaks tool entries and their captured mcpReg closures (keeping the *mcp.ClientManager subprocess alive) and leaves the named MCP server stuck "already connected" until runner restart.

Plan reference

Source finding

Registry.ReplaceByTag desyncs mcpServerTools map → tool entry leak and stuck "already connected" (internal/harness/registry.go:326-381). Hot-reloading an MCP-tagged tool doesn't update mcpServerTools, so a later UnregisterMCPServer for that name can't find the new entries; they and their captured mcpReg closures (which keep the *mcp.ClientManager → subprocess alive) leak forever. After one stuck run, the named MCP server becomes unusable until runner restart.

Scope

  • Allowed files (test): internal/harness/registry_mcp_test.go (extend)
  • Allowed files (impl): internal/harness/registry.go
  • Dependencies: none
  • Complexity: medium — Risk: medium (concurrent hot-swap of a tool while a call is in flight)

Acceptance criteria

Use slice T15 in the plan as detailed acceptance criteria. Summary:

  • Failing tests written first, compile and fail on main.
  • Register an mcp_*-tagged tool via RegisterMCPTools, then ReplaceByTag with a new handler. Assert mcpServerTools no longer contains the old entry's references and contains the new ones (or none, if the new tool isn't MCP-tagged). Then call UnregisterMCPServer(serverName); assert it removes the correct set.
  • Start a slow handler, then ReplaceByTag the same name; assert ReplaceByTag waits for the in-flight call to return (use a per-tool in-flight counter / WaitGroup) before swapping.
  • Impl: per-tool inflight sync.WaitGroup; ReplaceByTag calls tool.swapWg.Wait() on the old tool before deleting it. Recompute r.mcpServerTools in ReplaceByTag: enumerate r.tools, rebuild mcpServerTools[serverName] = set-of-tool-names from the survivors' tags.
  • go test ./internal/harness/... -race green; ./scripts/test-regression.sh green.

Guardrails

  • Strict TDD; failing regression test must compile and fail on main first.
  • Worktree flow; own branch, merge via verify-and-merge.sh.
  • Update .context/harness-reliability/tracker.md on pick-up and merge.
  • Deterministic tests only — fake MCP tools/handlers.
  • Append engineering-log entry on merge.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingmcpMCP server/clientmediumMedium effort: 1-3 daysreliabilityReliability and resilience concernswell-specifiedIssue is clear, scoped, and ready to implement

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions