fix: strip deleted MCP IDs from chats on delete (#25763)

Adds a database migration that reconciles existing stale chat MCP server
IDs, then installs a `BEFORE DELETE` trigger on `mcp_server_configs` to
remove the deleted ID from `chats.mcp_server_ids`. This keeps chat
continuation from failing with `400 One or more MCP server IDs are
invalid` after an MCP server config is deleted.

This matches the existing repo precedent in
`coderd/database/migrations/000241_delete_user_roles.up.sql`, where
deleting a custom role cleans `organization_members.roles`, a similarly
structured array of references that cannot be protected by a normal
foreign key.

Closes CODAGT-505
This commit is contained in:
Ethan
2026-05-29 16:49:25 +10:00
committed by GitHub
parent a801d996e7
commit eb2c2799ca
4 changed files with 73 additions and 6 deletions
+15
View File
@@ -1216,6 +1216,17 @@ BEGIN
END;
$$;
CREATE FUNCTION remove_mcp_server_config_id_from_chats() RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
UPDATE chats
SET mcp_server_ids = array_remove(mcp_server_ids, OLD.id)
WHERE OLD.id = ANY(mcp_server_ids);
RETURN OLD;
END;
$$;
CREATE FUNCTION remove_organization_member_role() RETURNS trigger
LANGUAGE plpgsql
AS $$
@@ -4435,6 +4446,10 @@ CREATE TRIGGER inhibit_enqueue_if_disabled BEFORE INSERT ON notification_message
CREATE TRIGGER protect_deleting_organizations BEFORE UPDATE ON organizations FOR EACH ROW WHEN (((new.deleted = true) AND (old.deleted = false))) EXECUTE FUNCTION protect_deleting_organizations();
CREATE TRIGGER remove_chat_mcp_server_config_id BEFORE DELETE ON mcp_server_configs FOR EACH ROW EXECUTE FUNCTION remove_mcp_server_config_id_from_chats();
COMMENT ON TRIGGER remove_chat_mcp_server_config_id ON mcp_server_configs IS 'When an MCP server config is deleted, this trigger removes its ID from all chats.';
CREATE TRIGGER remove_organization_member_custom_role BEFORE DELETE ON custom_roles FOR EACH ROW EXECUTE FUNCTION remove_organization_member_role();
COMMENT ON TRIGGER remove_organization_member_custom_role ON custom_roles IS 'When a custom_role is deleted, this trigger removes the role from all organization members.';
@@ -0,0 +1,2 @@
DROP TRIGGER IF EXISTS remove_chat_mcp_server_config_id ON mcp_server_configs;
DROP FUNCTION IF EXISTS remove_mcp_server_config_id_from_chats;
@@ -0,0 +1,41 @@
-- Remove already-stale MCP server references before future deletes are
-- handled by the trigger below.
UPDATE chats
SET mcp_server_ids = (
SELECT COALESCE(array_agg(ids.mcp_server_id ORDER BY ids.position), '{}'::uuid[])
FROM unnest(chats.mcp_server_ids) WITH ORDINALITY AS ids(mcp_server_id, position)
WHERE EXISTS (
SELECT 1
FROM mcp_server_configs
WHERE mcp_server_configs.id = ids.mcp_server_id
)
)
WHERE EXISTS (
SELECT 1
FROM unnest(chats.mcp_server_ids) AS ids(mcp_server_id)
WHERE NOT EXISTS (
SELECT 1
FROM mcp_server_configs
WHERE mcp_server_configs.id = ids.mcp_server_id
)
);
CREATE OR REPLACE FUNCTION remove_mcp_server_config_id_from_chats()
RETURNS TRIGGER AS
$$
BEGIN
UPDATE chats
SET mcp_server_ids = array_remove(mcp_server_ids, OLD.id)
WHERE OLD.id = ANY(mcp_server_ids);
RETURN OLD;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER remove_chat_mcp_server_config_id
BEFORE DELETE ON mcp_server_configs FOR EACH ROW
EXECUTE PROCEDURE remove_mcp_server_config_id_from_chats();
COMMENT ON TRIGGER
remove_chat_mcp_server_config_id
ON mcp_server_configs IS
'When an MCP server config is deleted, this trigger removes its ID from all chats.';