摘要
停止一個正在跑的 run(點擊團隊房間成員面板的「停止」鍵)會正確中斷該 agent 的執行(room trace 正確浮現「任务已被所有者停止」,agent 狀態立刻變回「空闲」,且原本正在跑的 sleep 90 && echo "sleep 90 done" 從未印出完成訊息,證實真的被中斷,不是等它自然跑完),但這個 run 建立/認領的工作板項目(work item)沒有如 backend/routes/runs.py::_pause_work_items 設計文件所述被標記成 paused——重新整理、直接呼叫 API 複查多次,item 的 status 全程停留在 in_progress。
_pause_work_items 的 docstring 明講這正是這個機制存在的核心原因:
Without this the stop is quietly undone: stopping a tree ends its RUNS, but the work board still lists the task as unfinished, so the Leader's next patrol sees "unfinished item, assignee idle", chases it, and starts a fresh run. The owner presses stop and watches new work appear...
也就是說,這次重現的正是這段註解描述的失效模式本身:使用者按下停止,工作板卻完全沒有反映這件事,項目繼續顯示「進行中」,之後 Leader patrol(若開啟)理論上會照常追殺這個「看起來還在跑、其實 run 已死」的項目。
重現步驟
環境:https://dev-agent.narra.nexus,xyzdev01,測試團隊 team_54f412ee0b4c(QA Work Board Test Team,已設 lead_agent_id)
- 在團隊房間對
qa-tcjob06-test(agent_df94a825fc13)下指令:
請依序做兩件事:1. 呼叫 work_add_item 新增一個工作項,標題「TC-TEAM-BOARD-11 停止run測試」,assignee_id 填你自己的 agent_id(讓它變成 in_progress)。2. 完成後,用 Bash 執行 sleep 90,等它跑完再回覆我。
- Agent 依序執行:
work_add_item 成功回傳 {"success":true,"item_id":"wi_9a62df9b","status":"in_progress"},接著開始執行 Bash: sleep 90 && echo "sleep 90 done"(透過過程面板即時觀察到工具呼叫)
- 確認
GET /api/teams/team_54f412ee0b4c/work-items 回傳 wi_9a62df9b 的 status: "in_progress", assignee_id: "agent_df94a825fc13"
- 在
sleep 90 執行途中(約第 25~30 秒),展開該成員的過程面板,點擊「停止」按鈕
- 觀察到:
POST /api/runs/evt_d14e3dc23b8c4c16/cancel 回傳 200
- 團隊聊天室立刻浮現系統訊息「qa-tcjob06-test 的任务已被所有者停止」
- 成員狀態面板立刻變回「空闲」,過程面板顯示「该轮次没有过程记录」
- 從未看到
sleep 90 done 的回覆——證實 run 真的被中斷,不是自然完成
- 停止後,重新呼叫
GET /api/teams/team_54f412ee0b4c/work-items 三次(間隔數秒到共 ~40 秒),wi_9a62df9b 的 status 全程都是 "in_progress",從未變成 "paused"
- UI 上的工作板面板也同步顯示「TC-TEAM-BOARD-11 停止run測試 · qa-tcjob06-test · 进行中」,不是預期的 paused 樣式
程式碼層可能根因(未確認,需 dev 端查證)
backend/routes/runs.py::cancel_run:root = row.get("root_run_id") or "",取的是這個 run 自己的 events.root_run_id 欄位;若非空則呼叫 _pause_work_items(db, root) → TeamWorkItemRepository(db).pause_by_root(root)。
pause_by_root docstring 明講「An empty root_run_id is a no-op, NOT a match-all」——如果這個 run 的 events.root_run_id 欄位是空的,_pause_work_items 會靜默 no-op(if not root_run_id: return 0,且外層有 try/except 吞掉例外,只留 logger.warning,前端完全看不到任何錯誤或提示)。
run_recorder.py:509:"root_run_id": self.inherited_root_run_id or run_id——理論上一個「非串聯」的獨立 run(沒有 inherited_root_run_id)應該會自我指向(root_run_id = run_id),所以照理不該是空的;但這條路徑我沒有 DB 存取權限可以直接驗證,不確定實際落地的值是否真的等於預期。
- 另一個可能:
work_add_item 呼叫 create_item(..., root_run_id=caller_root_run_id()),而 caller_root_run_id() 是從當下 turn 的 ambient headers/bearer identity 讀出來的(src/xyz_agent_context/module/_mcp_identity.py)。如果這個 header 在 Bash 工具呼叫鏈中的傳遞跟 cancel_run 讀到的 events.root_run_id 不是同一個值(例如兩者一個是 run_id 本身、一個是別的東西),pause_by_root(root) 查詢 WHERE root_run_id = %s 就永遠對不上,即使兩邊都非空。
- 這兩個可能性我都無法從瀏覽器測試本身區分,需要 dev 端直接查
events 表這個 run 的 root_run_id 欄位,以及 team_work_items 表 wi_9a62df9b 的 root_run_id 欄位,比對兩者是否一致、是否為空。
影響
- Functional,非安全問題(不涉及跨帳號存取或資料外洩)
- 但直接命中
_pause_work_items 自己文件宣稱要防範的核心情境:「owner 按停止,工作板卻沒反映,Leader patrol 下一輪又追殺同一個項目,使用者看到停掉的任務又冒出新的 run」
- Severity:中——功能未達成設計文件描述的行為,且是此功能(work board + leader patrol + stop-run 連動)存在的主要動機之一
測試環境
https://dev-agent.narra.nexus
- 對應測試計畫項目:
qa/features/collaboration-marketplace/test-plan-collaboration-marketplace.md TC-TEAM-BOARD-11
摘要
停止一個正在跑的 run(點擊團隊房間成員面板的「停止」鍵)會正確中斷該 agent 的執行(room trace 正確浮現「任务已被所有者停止」,agent 狀態立刻變回「空闲」,且原本正在跑的
sleep 90 && echo "sleep 90 done"從未印出完成訊息,證實真的被中斷,不是等它自然跑完),但這個 run 建立/認領的工作板項目(work item)沒有如backend/routes/runs.py::_pause_work_items設計文件所述被標記成paused——重新整理、直接呼叫 API 複查多次,item 的status全程停留在in_progress。_pause_work_items的 docstring 明講這正是這個機制存在的核心原因:也就是說,這次重現的正是這段註解描述的失效模式本身:使用者按下停止,工作板卻完全沒有反映這件事,項目繼續顯示「進行中」,之後 Leader patrol(若開啟)理論上會照常追殺這個「看起來還在跑、其實 run 已死」的項目。
重現步驟
環境:
https://dev-agent.narra.nexus,xyzdev01,測試團隊team_54f412ee0b4c(QA Work Board Test Team,已設lead_agent_id)qa-tcjob06-test(agent_df94a825fc13)下指令:work_add_item成功回傳{"success":true,"item_id":"wi_9a62df9b","status":"in_progress"},接著開始執行Bash: sleep 90 && echo "sleep 90 done"(透過過程面板即時觀察到工具呼叫)GET /api/teams/team_54f412ee0b4c/work-items回傳wi_9a62df9b的status: "in_progress",assignee_id: "agent_df94a825fc13"sleep 90執行途中(約第 25~30 秒),展開該成員的過程面板,點擊「停止」按鈕POST /api/runs/evt_d14e3dc23b8c4c16/cancel回傳200sleep 90 done的回覆——證實 run 真的被中斷,不是自然完成GET /api/teams/team_54f412ee0b4c/work-items三次(間隔數秒到共 ~40 秒),wi_9a62df9b的status全程都是"in_progress",從未變成"paused"程式碼層可能根因(未確認,需 dev 端查證)
backend/routes/runs.py::cancel_run:root = row.get("root_run_id") or "",取的是這個 run 自己的events.root_run_id欄位;若非空則呼叫_pause_work_items(db, root)→TeamWorkItemRepository(db).pause_by_root(root)。pause_by_rootdocstring 明講「An empty root_run_id is a no-op, NOT a match-all」——如果這個 run 的events.root_run_id欄位是空的,_pause_work_items會靜默 no-op(if not root_run_id: return 0,且外層有try/except吞掉例外,只留logger.warning,前端完全看不到任何錯誤或提示)。run_recorder.py:509:"root_run_id": self.inherited_root_run_id or run_id——理論上一個「非串聯」的獨立 run(沒有inherited_root_run_id)應該會自我指向(root_run_id = run_id),所以照理不該是空的;但這條路徑我沒有 DB 存取權限可以直接驗證,不確定實際落地的值是否真的等於預期。work_add_item呼叫create_item(..., root_run_id=caller_root_run_id()),而caller_root_run_id()是從當下 turn 的 ambient headers/bearer identity 讀出來的(src/xyz_agent_context/module/_mcp_identity.py)。如果這個 header 在 Bash 工具呼叫鏈中的傳遞跟cancel_run讀到的events.root_run_id不是同一個值(例如兩者一個是run_id本身、一個是別的東西),pause_by_root(root)查詢WHERE root_run_id = %s就永遠對不上,即使兩邊都非空。events表這個 run 的root_run_id欄位,以及team_work_items表wi_9a62df9b的root_run_id欄位,比對兩者是否一致、是否為空。影響
_pause_work_items自己文件宣稱要防範的核心情境:「owner 按停止,工作板卻沒反映,Leader patrol 下一輪又追殺同一個項目,使用者看到停掉的任務又冒出新的 run」測試環境
https://dev-agent.narra.nexusqa/features/collaboration-marketplace/test-plan-collaboration-marketplace.mdTC-TEAM-BOARD-11