{"record":{"id":"d669597aad9d8aff","repo":"charmbracelet/crush","slug":"failed-to-disable-docker-mcp-w","errorCode":null,"errorMessage":"failed to disable docker MCP: %w","messagePattern":"failed to disable docker MCP: %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/backend/config.go","lineNumber":257,"sourceCode":"\t\tdisableErr := mcptools.DisableSingle(ws.Cfg, config.DockerMCPName)\n\t\tws.Cfg.RemoveDockerMCPInMemory()\n\t\treturn fmt.Errorf(\"docker MCP started but failed to persist configuration: %w\", errors.Join(err, disableErr))\n\t}\n\n\tpublishConfigChanged(ws)\n\treturn nil\n}\n\n// DisableDockerMCP closes the Docker MCP client, removes the\n// configuration, and persists the change.\nfunc (b *Backend) DisableDockerMCP(workspaceID string) error {\n\tws, err := b.GetWorkspace(workspaceID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif err := mcptools.DisableSingle(ws.Cfg, config.DockerMCPName); err != nil {\n\t\treturn fmt.Errorf(\"failed to disable docker MCP: %w\", err)\n\t}\n\n\tif err := ws.Cfg.DisableDockerMCP(); err != nil {\n\t\treturn err\n\t}\n\n\tpublishConfigChanged(ws)\n\treturn nil\n}\n\n// RefreshMCPTools refreshes the tools for a named MCP server.\nfunc (b *Backend) RefreshMCPTools(ctx context.Context, workspaceID, name string) error {\n\tws, err := b.GetWorkspace(workspaceID)\n\tif err != nil {\n\t\treturn err\n\t}\n\tmcptools.RefreshTools(ctx, ws.Cfg, name)\n\treturn nil","sourceCodeStart":239,"sourceCodeEnd":275,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/backend/config.go#L239-L275","documentation":"DisableDockerMCP stops the Docker MCP tool via mcptools.DisableSingle and this error wraps any failure from that shutdown step. It means the Docker MCP tool could not be cleanly disabled from the workspace's tool set.","triggerScenarios":"Calling DisableDockerMCP(workspaceID) when mcptools.DisableSingle(ws.Cfg, config.DockerMCPName) returns an error — e.g. the docker MCP tool was never enabled, the running MCP process refuses to terminate (shutdown timeout), or the workspace config is missing/corrupt.","commonSituations":"Double-disable (EnableDockerMCP rollback already disabled it); MCP process hung so teardown times out; concurrent modification of the workspace config between read and disable.","solutions":["Check whether Docker MCP is actually enabled before calling DisableDockerMCP (idempotency guard).","Inspect the wrapped inner error for the concrete DisableSingle cause (timeout vs not-found).","If the process is hung, kill the MCP process manually and retry the disable.","If DisableSingle succeeded but DisableDockerMCP() (config disable) then fails, re-check config file permissions."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"if !backend.DockerMCPEnabled(wsID) {\n    return nil // nothing to disable; avoid failed DisableSingle\n}","typeGuard":"func IsDisableMCP_err(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"failed to disable docker MCP\")\n}","tryCatchPattern":"if err := backend.DisableDockerMCP(ctx, wsID); err != nil {\n    if strings.Contains(err.Error(), \"failed to disable\") {\n        log.Printf(\"docker MCP disable failed, may need manual kill: %v\", err)\n    }\n    return err\n}","preventionTips":["Make disable flows idempotent: check enabled state first.","Add shutdown timeouts to MCP teardown so hung processes surface clearly.","Avoid double-disable races from rollback paths (EnableDockerMCP already disables on failure).","Persist enable/disable state atomically."],"tags":["mcp","docker","lifecycle","go"],"backgroundTag":"mcp-server-shutdown-failed","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}