deerflow2/backend/app/gateway/routers
Lucy Shen 37451500eb
fix(gateway): split stream_existing_run into per-method routes for unique OpenAPI operationIds (#3228)
* fix(gateway): split stream_existing_run into per-method routes for unique OpenAPI operationIds

`@router.api_route("/.../stream", methods=["GET", "POST"])` registers a
single FastAPI route that holds both methods. FastAPI's auto-generated
`operationId` is computed once per route from a single method picked out
of `route.methods`, so when OpenAPI generation iterates over every method
on that route both end up sharing the same `operationId`. That triggers
`UserWarning: Duplicate Operation ID stream_existing_run_..._stream_(get|post) for function stream_existing_run`
during `app.openapi()` and produces an invalid OpenAPI spec for SDK /
codegen consumers.

Register GET and POST as two separate routes on the same handler so each
method gets a distinct auto-generated `operationId` ("..._stream_get" and
"..._stream_post"). Behavior is otherwise unchanged: same handler, same
`require_permission` decoration, same response.

Add `tests/test_openapi_operation_ids.py` to lock in the invariant:
no duplicate-operationId warnings during spec generation, globally unique
operationIds across the spec, and distinct GET / POST operationIds on the
stream endpoint specifically. Reverted the source change locally and
confirmed all three tests fail before the fix.

* test(runtime): widen CancelledError catch in _ScriptedAgent to fix cancel-race flake

`_ScriptedAgent.astream()` previously only caught `asyncio.CancelledError`
inside the inner `if self.block_after_first_chunk:` while-loop. Cancellation
arriving during any earlier `await` in the same body
(`self.model.ainvoke`, `_write_checkpoint`, the `yield`) would propagate
without setting `controller.cancelled`, so callers waiting on
`controller.cancelled.wait(5)` after `POST /cancel` returned 204 could race
and time out.

`test_cancel_interrupt_stops_running_background_run` waits only for the
`started` event (set on the first line of `astream`) before issuing cancel,
so its race window spans all three pre-loop `await`s. On a clean `main`
checkout, stress-running the test 20× reproduces the failure 6/20
(~30%). `test_cancel_rollback_restores_pre_run_checkpoint`, which waits
for the later `checkpoint_written` event, passes 20/20 — confirming the
race lives entirely in the gap between `started.set()` and the
cancellation-aware block.

Widen the try/except to cover the entire `astream` body so any
`CancelledError` sets the controller event; the non-cancel path is
unchanged (no exception means no event set). After this change the
previously flaky test passes 50/50, the rollback test still passes 30/30,
and the full backend suite remains at 3649 passed / 19 skipped.

Test-only change — `backend/tests/test_runtime_lifecycle_e2e.py` is the
only file touched; the production cancel pipeline is unaffected.
2026-05-28 08:20:52 +08:00
..
__init__.py feat(gateway): implement LangGraph Platform API in Gateway, replace langgraph-cli (#1403) 2026-03-30 16:02:23 +08:00
agents.py feat(agent): add custom-agent self-updates with user isolation (#2713) 2026-05-05 23:17:42 +08:00
artifacts.py fix(gateway): cap skill artifact preview size (#2963) 2026-05-15 22:15:58 +08:00
assistants_compat.py feat(gateway): implement LangGraph Platform API in Gateway, replace langgraph-cli (#1403) 2026-03-30 16:02:23 +08:00
auth.py fix(auth): replace setup-status 429 rate limit with cached response (#2915) 2026-05-18 22:07:01 +08:00
channels.py refactor: split backend into harness (deerflow.*) and app (app.*) (#1131) 2026-03-14 22:55:52 +08:00
feedback.py feat(persistence):Unified persistence layer with event store, feedback, and rebase cleanup (#2134) 2026-04-26 11:09:55 +08:00
mcp.py fix(security): mask sensitive values in MCP config API responses (#2667) 2026-05-21 10:28:57 +08:00
memory.py feat(persistence): per-user filesystem isolation, run-scoped APIs, and state/history simplification (#2153) 2026-04-26 11:13:01 +08:00
models.py refactor: thread release config through lead path (#2612) 2026-04-28 14:53:18 +08:00
runs.py fix(gateway): honour on_disconnect on /wait endpoints (#3267) 2026-05-28 07:22:39 +08:00
skills.py refactor(skills): Unified skill storage capability (#2613) 2026-05-01 13:23:26 +08:00
suggestions.py refactor: thread release config through lead path (#2612) 2026-04-28 14:53:18 +08:00
thread_runs.py fix(gateway): split stream_existing_run into per-method routes for unique OpenAPI operationIds (#3228) 2026-05-28 08:20:52 +08:00
threads.py perf(harness): push thread metadata filters into SQL (#2865) 2026-05-12 23:21:22 +08:00
uploads.py fix(sandbox): add group/other read permissions to uploaded files for Docker sandbox (#3127) (#3134) 2026-05-25 09:26:18 +08:00