---
date: 2026-05-18
type: error
tags: [agent-pool, prisma, postgres, citext, npm, slot-reuse]
---

# Agent-pool slot 重 claim 后的"双重漂移"：DB schema 残留 + 新依赖未预装

## 现象

claim 一个**之前被别的分支占过、随后释放回池**的 slot，准备给新 task 用，连续踩三层坑：

1. `npm run db:push` 报 `column "..." of relation "..." is a generated column`
   `HINT: Use ALTER TABLE ... ALTER COLUMN ... DROP EXPRESSION instead.`
2. DROP+CREATE 数据库后再 push，报 `type "citext" does not exist`
3. 装回扩展、push 成功、`db:seed` + `init:itadmin` 都 OK，但 `start:dev` 启动崩在
   `Cannot find module '@modelcontextprotocol/sdk/client/index.js'`（agent 模块的 `mcp-manager.service.ts` import）

## 根因

- **DB schema 残留**：slot 释放回池时，`agent-release.sh` 只擦 lock 和 git 状态，**不擦数据库**。
  下一个 claim 它的分支跑 `prisma db push` 时，新 schema 试图改老 schema 留下的 generated
  column / unique constraint，prisma 直接拒绝（generated column 不能直接 alter type）。
- **DROP DATABASE 把扩展也带走**：`citext` / `pg_trgm` / `uuid-ossp` 是 per-database
  的（不是 per-cluster）。DROP+CREATE 后 PG 容器还在，但新建的 DB 是裸的——回到
  ERR-20260509-004 的"缺扩展"原状。
- **npm 依赖在池预热时锁定**：`pool-init.sh` 在初始化时按当时 develop HEAD 的
  `package.json` 跑了一次 `npm install`。预热**之后** develop 上新合的 PR 引入新依赖
  （比如 `@modelcontextprotocol/sdk` 在 PR0.5→PR10.6 落地里），slot 不会自动 reinstall。
  claim 出来切到新分支，依赖就缺。

## 解法（slot 复用清场标准动作）

发现 slot 是"复用"的（不是池初始化后第一次 claim），跑这串：

```bash
# 1. 重置 DB（清残留 schema）
docker exec -e PGPASSWORD=dev_password ffws-wt-pg-${POSTGRES_PORT} \
  psql -h localhost -U ffoa_dev -d postgres \
  -c "DROP DATABASE IF EXISTS ${DB_NAME} WITH (FORCE);"
docker exec -e PGPASSWORD=dev_password ffws-wt-pg-${POSTGRES_PORT} \
  psql -h localhost -U ffoa_dev -d postgres \
  -c "CREATE DATABASE ${DB_NAME};"

# 2. 装回扩展（DROP DATABASE 会带走）
docker exec -e PGPASSWORD=dev_password ffws-wt-pg-${POSTGRES_PORT} \
  psql -h localhost -U ffoa_dev -d ${DB_NAME} \
  -c 'CREATE EXTENSION IF NOT EXISTS citext;
      CREATE EXTENSION IF NOT EXISTS pg_trgm;
      CREATE EXTENSION IF NOT EXISTS "uuid-ossp";'

# 3. 同步依赖（池可能落后 develop）
( cd backend && npm install )
( cd frontend && npm install )

# 4. 推 schema（空 DB 上的 destructive 警告是假阳性）
( cd backend && npm run db:push -- --accept-data-loss )
( cd backend && npm run db:seed )
( cd backend && npm run init:itadmin )
```

`${POSTGRES_PORT}` 从 `.env` 取，`${DB_NAME}` 是 `DATABASE_URL` 里 `/` 后那段
（默认 `ffws_wt_slot_${FFOA_AGENT_SLOT}`）。

## 工程化保险（应做但还没做）

应该写到 `scripts/dev/agent-pool/agent-claim.sh` 或一个新的
`agent-claim-fresh.sh` 子命令，在 claim 时检测"slot 之前是否被别人占用过"
（用上次 release 时落地的标记），自动跑上面 1-4 步。否则每次 claim 都要 AI 重新踩一遍。

候选实现：
- `agent-release.sh` 落 `.agent-pool/slot-N/.last-branch` 文件
- `agent-claim.sh` 比对，发现 `${.last-branch}` ≠ 新分支 → 提示或自动清场
- 同时把 `CREATE EXTENSION` 提到 `setup-worktree.sh` 的 PG bootstrap 之外、改成 per-DB
  bootstrap，DROP+CREATE 后能再次重复

## 教训

1. **slot 不是 stateless**：池里看着是 free，其实 DB 和 node_modules 都有上次的尾巴。
   claim 出来不等于"干净环境"。
2. **DROP DATABASE 不等于"重置环境"**：扩展会跟着丢，要重新建。
3. **池预热的依赖是 snapshot**：develop 每合一个引入新依赖的 PR，所有空闲 slot 的
   node_modules 就过期一点。**任何分支首次跑都要先 `npm install` 一遍**，别假设池
   是最新的。
4. **`npm run db:push -- --accept-data-loss` 在空 DB 上是安全的**：警告是 prisma 看到
   要 ADD UNIQUE 之类就预防性提示，空表上没数据丢。AI 节奏下别死磕交互式确认。

## 关联

- ERR-20260509-004（首次 pool-init 缺 citext，根因相同但场景不同：那是"从来没装过"，
  这是"DROP DATABASE 把已装的弄丢了"）
- ERR-20260509-002（agent-pool flock FD 继承）、ERR-20260509-003（pool 自指）：同
  一组 agent-pool 健壮性坑
- CLAUDE.md「Agent Pool」段：当前的 claim/release 流程定义
