Skip to content

[Bug] Xoá agent thất bại — unique index uq_vault_docs_agent_team_scope_path va chạm khi orphan vault docs sang scope shared #1550

Description

@aaron-tsar

[Bug] Xoá agent thất bại — unique index uq_vault_docs_agent_team_scope_path va chạm khi orphan vault docs sang scope shared

Mô tả vấn đề

Khi xoá một agent có vault documents, RPC agents.delete trả về lỗi 500:

Error deleting agent: gateway error (500): failed to delete agent: internal error

Log gateway:

level=ERROR msg=agents.delete id=019e5e1c-…-18d288d10167
  error="ERROR: duplicate key value violates unique constraint
         \"uq_vault_docs_agent_team_scope_path\" (SQLSTATE 23505)"

Agent không thể xoá được bằng bất kỳ đường nào (Web UI, CLI goclaw agent delete --force, RPC). Toàn bộ transaction bị rollback.

Đây là hệ quả trực tiếp của cách fix #1077 (orphan vault docs sang scope='shared' thay vì personal). Fix đó giải quyết được CHECK constraint 23514, nhưng đẩy mọi doc mồ côi vào chung một không gian khoá và tạo ra va chạm UNIQUE 23505.

Nguyên nhân gốc

Ba thay đổi trong migrations/000046_vault_nullable_agent_id.up.sql kết hợp lại tạo ra va chạm không thể tránh:

DELETE FROM agents WHERE id = $1
  ↓
(2) FK: vault_documents.agent_id … ON DELETE SET NULL
  → PostgreSQL SET agent_id = NULL
  ↓
(4) Trigger trg_vault_docs_agent_null_scope:
  → NEW.scope := 'shared'
  ↓
(3) UNIQUE INDEX uq_vault_docs_agent_team_scope_path:
  (tenant_id, COALESCE(agent_id,'000…0'), COALESCE(team_id,'000…0'), scope, path)
  → agent_id NULL bị COALESCE về CÙNG một sentinel
  → mọi doc mồ côi của MỌI agent đã xoá đều rơi vào cùng key space
  ↓
❌ Trùng (tenant, sentinel, sentinel, 'shared', path) → SQLSTATE 23505

Chi tiết code

migrations/000046_vault_nullable_agent_id.up.sql — cả ba mảnh nằm trong cùng một migration:

-- (2) FK: agent deletion preserves docs
ALTER TABLE vault_documents ADD CONSTRAINT vault_documents_agent_id_fkey
    FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE SET NULL;

-- (3) Unique index COALESCE cả hai cột nullable về cùng một sentinel
CREATE UNIQUE INDEX uq_vault_docs_agent_team_scope_path
    ON vault_documents (
        tenant_id,
        COALESCE(agent_id, '00000000-0000-0000-0000-000000000000'),
        COALESCE(team_id,  '00000000-0000-0000-0000-000000000000'),
        scope,
        path
    );

-- (4) Trigger đẩy scope về 'shared'
CREATE OR REPLACE FUNCTION vault_docs_agent_null_scope_fix()
RETURNS TRIGGER AS $$
BEGIN
    IF NEW.agent_id IS NULL AND OLD.agent_id IS NOT NULL AND NEW.team_id IS NULL THEN
        NEW.scope := 'shared';
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

internal/gateway/methods/agents_delete.go gọi thẳng store, không xử lý vault docs trước, nên lỗi DB nổi lên nguyên si thành ErrInternal:

if err := m.agentStore.Delete(ctx, ag.ID); err != nil {
    client.SendResponse(protocol.NewErrorResponse(req.ID, protocol.ErrInternal,
        i18n.T(locale, i18n.MsgFailedToDelete, "agent", fmt.Sprintf("%v", err))))
    return
}

Vấn đề cốt lõi

scope='shared' + sentinel UUID biến path thành khoá duy nhất toàn tenant. Hai agent bất kỳ cùng lưu một đường dẫn (rất phổ biến: codebase/README.md, codebase/docs/…) thì agent thứ hai vĩnh viễn không xoá được, vì agent thứ nhất đã chiếm chỗ khi bị xoá trước đó.

Trạng thái agent_id team_id scope Khoá unique hiệu dụng
Doc của agent A (còn sống) A NULL personal (T, A, sentinel, personal, path)
Sau khi xoá A NULL NULL shared (T, sentinel, sentinel, shared, path)
Doc của agent B, cùng path B NULL personal (T, B, sentinel, personal, path)
Khi xoá B NULL NULL shared (T, sentinel, sentinel, shared, path) → ❌ trùng với A

Lỗi mang tính tích luỹ: mỗi agent bị xoá lại gài thêm mìn cho các agent sau.

Các bước tái hiện

  1. Tạo agent A và agent B trong cùng tenant.
  2. Cho cả hai tạo vault document ở cùng một path (ví dụ codebase/README.md), scope personal.
  3. Xoá agent A → thành công. Doc của A trở thành agent_id=NULL, scope='shared'.
  4. Xoá agent B → ❌ SQLSTATE 23505 duplicate key value violates unique constraint "uq_vault_docs_agent_team_scope_path".

Truy vấn xác nhận trên hệ thống thật, trước khi xoá agent:

SELECT count(*) AS colliding_paths
FROM vault_documents w
JOIN vault_documents o
  ON o.agent_id IS NULL AND o.team_id IS NULL AND o.scope = 'shared'
 AND o.tenant_id = w.tenant_id AND o.path = w.path
WHERE w.agent_id = '<agent-id>';
-- → 56 (trên tổng 57 doc của agent), khớp chính xác với lỗi 23505

Đề xuất giải pháp

Phương án 1 — giữ nguyên ý định "bảo toàn dữ liệu" của #1077, khử va chạm bằng namespace (khuyến nghị).
Khi orphan, đẩy path vào một namespace riêng theo agent cũ thay vì để nguyên. Cần lưu lại agent id trước khi FK set NULL, ví dụ thêm cột orphaned_from_agent_id và mở rộng trigger:

IF NEW.agent_id IS NULL AND OLD.agent_id IS NOT NULL AND NEW.team_id IS NULL THEN
    NEW.scope := 'shared';
    NEW.orphaned_from_agent_id := OLD.agent_id;
    NEW.path := '_archive/' || OLD.agent_id || '/' || OLD.path;
END IF;

Kèm đó nên đưa orphaned_from_agent_id vào unique index để hai agent khác nhau không bao giờ đụng nhau.

Phương án 2 — dọn vault docs trong agents.delete trước khi xoá agent.
Xử lý ở tầng ứng dụng, trong cùng transaction: xoá (hoặc re-path) vault_documents của agent rồi mới DELETE FROM agents. Ưu điểm là tôn trọng tham số deleteFiles đã có sẵn trong handler — deleteFiles=true thì xoá luôn doc, false thì mới orphan.

Phương án 3 — đổi FK sang ON DELETE CASCADE.
Đơn giản nhất nhưng xoá vĩnh viễn vault documents của agent, đi ngược lại chủ đích "agent deletion preserves docs" ghi trong migration 000046.

Ngoài ra, độc lập với phương án nào được chọn: agents.delete nên map lỗi 23505 thành thông báo có ý nghĩa thay vì internal error, để người dùng biết nguyên nhân là vault documents.

Cách khắc phục tạm thời

Xoá vault documents của agent trước, rồi xoá agent như bình thường:

DELETE FROM vault_documents WHERE agent_id = '<agent-id>';
goclaw agent delete <agent-id> --force

Lưu ý vault_links sẽ bị CASCADE theo. Nếu muốn giữ dữ liệu thì đổi path sang tiền tố riêng thay vì xoá.

Môi trường

  • GoClaw v3.15.0-beta.204 (protocol 3), image ghcr.io/nextlevelbuilder/goclaw:beta-full
  • Schema version 97
  • PostgreSQL pgvector/pgvector:pg18
  • Ubuntu 24.04.2 LTS, Docker Compose, network_mode host

Liên quan

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2-mediumFunctional bug, UX broken — prioritized backlogbugSomething isn't workingmaintain:bug-confirmedBug confirmed by github-maintain automationmaintain:triagedTriaged by maintain workflow

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions