背景
HITL(Human-in-the-Loop)寫入核准流程已串通(PR #51、commit `8203ac03`),但錯誤路徑的 lifecycle 有幾處破口:當 `POST /api/v1/ai/runs/{id}/approval/` 或後續 Celery dispatch 失敗時,run 可能卡在前後端無法 recover 的狀態。
呼叫路徑
`submitApproval` → `resumeAgent` → `chatbotRepository.submitRunApproval()` → backend `POST /approval/` → `resume_approval_run`:
```python
backend/apps/ai/services/run_runtime.py:136-150
run.kind = RESUME
run.resume_decision = decision
run.status = RUNNING
run.save(...) # ① DB 更新
dispatch_run(run) # ② Celery enqueue
```
四種錯誤情境的當前行為
| 情境 |
前端行為 |
run 狀態 |
後果 |
| A. DB 斷 / backend crash(在 ① 前 500) |
HITLCard 消失、Toast error |
留在 `AWAITING_APPROVAL` |
使用者沒 UI 可重試:`pendingApproval` 已被 `submitApproval` 清成 `null`,只能 refresh 或 cancel |
| B. dispatch ② 失敗(Celery/Redis down) |
同上 |
已 `RUNNING` 但沒 task |
殭屍 run,永遠沒進度 |
| C. Celery 成功,ai-service fail / 連線斷 |
下次 SSE 訂閱收到 `run_failed` → `onError` + 移除 activeRun |
`FAILED` |
✓ 正常 |
| D. 使用者按 cancel |
`cancelRun` 200、activeRun 移除 |
`CANCELLED` |
✓ 正常(`request_run_cancel` 處理了 awaiting 狀態) |
主要破口
- `pendingApproval` 過早清空 — `useChatbot.ts:624` 在呼叫 `resumeAgent` 前就 `setPendingApproval(null)`,approval request 失敗後使用者沒 HITLCard 可按。
- `resume_approval_run` 非 atomic — ①+② 不在同一個 transaction,且 ② 的 `dispatch_run.delay()` 是 broker I/O 本來就不能 atomic。失敗時留下 `RUNNING` 但沒 task 的 zombie。
- 沒有殭屍 run 清理機制 — stuck 的 `RUNNING` / `AWAITING_APPROVAL` 無 auto-recover。
- `/approval/` 非 idempotent — retry 安全性差,重複呼叫可能進不一致狀態。
建議修法
短期(~30 分鐘,立刻補)
中期(1~2 小時)
長期
- 接 durable execution(Temporal / Inngest / DBOS)把 HITL signal-wait 從 app server 卸下,人類 pending 期間不占任何資源。可延伸討論。
相關檔案
- `frontend/src/features/chatbot/hooks/useChatbot.ts`(`submitApproval`, `resumeAgent`)
- `backend/apps/ai/services/run_runtime.py`(`resume_approval_run`, `dispatch_run`)
- `backend/apps/ai/views.py`(`approval` action)
- `backend/apps/ai/tasks.py`(若要加殭屍掃描)
背景
HITL(Human-in-the-Loop)寫入核准流程已串通(PR #51、commit `8203ac03`),但錯誤路徑的 lifecycle 有幾處破口:當 `POST /api/v1/ai/runs/{id}/approval/` 或後續 Celery dispatch 失敗時,run 可能卡在前後端無法 recover 的狀態。
呼叫路徑
`submitApproval` → `resumeAgent` → `chatbotRepository.submitRunApproval()` → backend `POST /approval/` → `resume_approval_run`:
```python
backend/apps/ai/services/run_runtime.py:136-150
run.kind = RESUME
run.resume_decision = decision
run.status = RUNNING
run.save(...) # ① DB 更新
dispatch_run(run) # ② Celery enqueue
```
四種錯誤情境的當前行為
主要破口
建議修法
短期(~30 分鐘,立刻補)
```ts
const savedRequest = pendingApproval;
setPendingApproval(null);
try { await resumeAgent(decision); }
catch (err) { setPendingApproval(savedRequest); throw err; }
```
```python
run.save(...)
try:
dispatch_run(run)
except Exception:
run.status = AWAITING_APPROVAL
run.save(update_fields=["status", "updated_at"])
raise
```
中期(1~2 小時)
長期
相關檔案