diff --git a/plugins/kanban/dashboard/plugin_api.py b/plugins/kanban/dashboard/plugin_api.py index 5ed0e6144d..f13755dfb7 100644 --- a/plugins/kanban/dashboard/plugin_api.py +++ b/plugins/kanban/dashboard/plugin_api.py @@ -913,7 +913,17 @@ def bulk_update(payload: BulkTaskBody, board: Optional[str] = Query(None)): ok = kanban_db.unblock_task(conn, tid) else: ok = _set_status_direct(conn, tid, "ready") - elif s in {"todo", "running", "triage"}: + elif s == "running": + entry.update( + ok=False, + error=( + "Cannot set status to 'running' directly; " + "use the dispatcher/claim path" + ), + ) + results.append(entry) + continue + elif s in {"todo", "triage"}: ok = _set_status_direct(conn, tid, s) else: entry.update(ok=False, error=f"unknown status {s!r}") diff --git a/tests/plugins/test_kanban_dashboard_plugin.py b/tests/plugins/test_kanban_dashboard_plugin.py index 50da5071e3..fbb111ff78 100644 --- a/tests/plugins/test_kanban_dashboard_plugin.py +++ b/tests/plugins/test_kanban_dashboard_plugin.py @@ -880,6 +880,31 @@ def test_bulk_status_done_forwards_completion_summary(client): conn.close() +def test_bulk_status_running_rejected(client): + """Bulk updates must match single-task PATCH: direct 'running' is invalid.""" + t = client.post("/api/plugins/kanban/tasks", json={"title": "x"}).json()["task"] + + r = client.post( + "/api/plugins/kanban/tasks/bulk", + json={"ids": [t["id"]], "status": "running"}, + ) + + assert r.status_code == 200 + results = r.json()["results"] + assert len(results) == 1 + assert results[0]["id"] == t["id"] + assert results[0]["ok"] is False + assert "running" in results[0]["error"] + + board = client.get("/api/plugins/kanban/board").json() + statuses = { + tt["id"]: col["name"] + for col in board["columns"] + for tt in col["tasks"] + } + assert statuses.get(t["id"]) != "running" + + def test_dashboard_done_actions_prompt_for_completion_summary(): repo_root = Path(__file__).resolve().parents[2] bundle = (