diff --git a/CHANGELOG.md b/CHANGELOG.md index f934774ff..2de8373bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -941,6 +941,19 @@ This release closes that milestone with **765 merged pull requests**. ### Fixed +- **nginx:** Extend the 600-second read timeout to the two remaining locations + whose routes wait on the Gateway, both left on nginx's 60-second default by + the thread-route fix. Behind the `/api/` catch-all, the stateless + `POST /api/runs/wait` blocks on the same run-completion wait and cancels its + run when the client disconnects, so an API consumer waiting on a run longer + than 60 seconds got a 504 *and* a cancelled run, and the composer's + `POST /api/input-polish` waits for a one-shot model call. Behind + `/api/skills`, installing a `.skill` archive runs one LLM security scan per + file in it, and a custom-skill edit or rollback runs one more; none of them + sets its own timeout, and only the sibling `/api/skills/install/upload` + endpoint had been given the longer timeout, so the same install through + `POST /api/skills/install` failed at 60 seconds. Applied to the Docker, + local, and Helm configs. ([#5524]) - **nginx:** Stop thread routes that wait on a model call from failing at 60 seconds. The browser calls `/api/threads/*` directly, and that location had no `proxy_read_timeout`, so nginx's 60-second default applied while @@ -4255,3 +4268,4 @@ with **180 merged pull requests** since the first 2.0 milestone tag. [#5501]: https://github.com/bytedance/deer-flow/pull/5501 [#5504]: https://github.com/bytedance/deer-flow/pull/5504 [#5505]: https://github.com/bytedance/deer-flow/pull/5505 +[#5524]: https://github.com/bytedance/deer-flow/pull/5524 diff --git a/CHANGELOG_zh.md b/CHANGELOG_zh.md index 5bffa1443..871a73083 100644 --- a/CHANGELOG_zh.md +++ b/CHANGELOG_zh.md @@ -727,6 +727,14 @@ ### 修复 +- **nginx:** 把 600 秒读取超时扩展到其余两个会等待 Gateway 的 location,它们在线程路由的修复 + 之后仍沿用 nginx 默认的 60 秒。`/api/` 兜底 location 之后:无状态的 `POST /api/runs/wait` + 阻塞在同一套运行完成等待上,并在客户端断开时取消该运行,因此等待超过 60 秒的 API 调用方会 + 同时收到 504 **并且**运行被取消;输入框的 `POST /api/input-polish` 则等待一次性模型调用。 + `/api/skills` 之后:安装 `.skill` 压缩包会对其中每个文件各做一次 LLM 安全扫描,自定义技能的 + 编辑与回滚各再做一次,它们都没有自己的超时;此前只有同级的 `/api/skills/install/upload` + 拿到了更长的超时,因此同样的安装经由 `POST /api/skills/install` 会在 60 秒失败。 + Docker、本地开发与 Helm 配置均已应用。([#5524]) - **nginx:** 需要等待模型调用的线程路由不再在 60 秒时失败。浏览器直接调用 `/api/threads/*`, 而该 location 没有设置 `proxy_read_timeout`,因此沿用 nginx 默认的 60 秒,而 `/api/langgraph/` 允许 600 秒。较慢的 `/compact` 会返回 504,但 Gateway 仍会继续执行并保存压缩结果,于是 UI @@ -3483,3 +3491,4 @@ DeerFlow 2.0 是围绕"超级智能体"框架的彻底重写,核心包含子 [#5501]: https://github.com/bytedance/deer-flow/pull/5501 [#5504]: https://github.com/bytedance/deer-flow/pull/5504 [#5505]: https://github.com/bytedance/deer-flow/pull/5505 +[#5524]: https://github.com/bytedance/deer-flow/pull/5524 diff --git a/backend/tests/test_nginx_langgraph_body_size.py b/backend/tests/test_nginx_langgraph_body_size.py index 1e133e7ea..add08766d 100644 --- a/backend/tests/test_nginx_langgraph_body_size.py +++ b/backend/tests/test_nginx_langgraph_body_size.py @@ -159,6 +159,22 @@ def test_threads_route_outlasts_blocking_gateway_calls(path): assert timeout_seconds >= _MIN_BLOCKING_READ_TIMEOUT_SECONDS, f"{path}: the generic /api/threads location allows {timeout_seconds}s, expected at least {_MIN_BLOCKING_READ_TIMEOUT_SECONDS}s like /api/langgraph/" +@pytest.mark.parametrize("path", NGINX_CONFIGS) +def test_api_catchall_outlasts_blocking_gateway_calls(path): + """The routes that wait on Gateway are not all under ``/api/threads``. + The stateless ``POST /api/runs/wait`` blocks on the same + ``wait_for_run_completion`` and cancels its run when the client + disconnects, and the composer's ``POST /api/input-polish`` waits for a + one-shot model call. Both fall through to this catch-all, so it needs the + same read timeout as the thread routes.""" + content = _read(path) + block = _extract_location_block(content, "/api/") + + timeout_seconds = _parse_read_timeout_seconds(block) + + assert timeout_seconds >= _MIN_BLOCKING_READ_TIMEOUT_SECONDS, f"{path}: the /api/ catch-all allows {timeout_seconds}s, expected at least {_MIN_BLOCKING_READ_TIMEOUT_SECONDS}s like /api/langgraph/ and /api/threads" + + @pytest.mark.parametrize("path", NGINX_CONFIGS) def test_skills_upload_route_allows_archive_plus_multipart_framing(path): """The upload route must stream archives and allow slow validation.""" @@ -170,6 +186,21 @@ def test_skills_upload_route_allows_archive_plus_multipart_framing(path): assert "proxy_read_timeout 600s;" in block +@pytest.mark.parametrize("path", NGINX_CONFIGS) +def test_skills_prefix_outlasts_the_llm_security_scan(path): + """Installing a ``.skill`` archive runs one LLM security scan per file in + it, and editing or rolling back a custom skill runs one more; none of them + sets an application-level timeout. The upload endpoint above already gets + 600s, but ``POST /api/skills/install`` and the custom-skill writes land + here, so this prefix needs it too.""" + content = _read(path) + block = _extract_location_block(content, "/api/skills") + + timeout_seconds = _parse_read_timeout_seconds(block) + + assert timeout_seconds >= _MIN_BLOCKING_READ_TIMEOUT_SECONDS, f"{path}: /api/skills allows {timeout_seconds}s, expected at least {_MIN_BLOCKING_READ_TIMEOUT_SECONDS}s like its own /api/skills/install/upload endpoint" + + @pytest.mark.parametrize("path", NGINX_CONFIGS) def test_skills_prefix_keeps_default_request_body_policy(path): """Large bodies must be allowed only on the admin upload endpoint.""" diff --git a/deploy/helm/deer-flow/README.md b/deploy/helm/deer-flow/README.md index 9185ac96f..692463b09 100644 --- a/deploy/helm/deer-flow/README.md +++ b/deploy/helm/deer-flow/README.md @@ -121,12 +121,14 @@ secrets: The default ingress annotations permit a 100 MiB local `.skill` archive plus multipart framing, stream request bodies without ingress buffering, and allow -up to 600 seconds for a response, which skill validation and thread requests -that wait on a model call (such as `/compact`) both need. If you replace -`ingress.annotations`, preserve equivalent size, streaming, and -response-timeout settings for your ingress controller, or local skill uploads -may fail before DeerFlow completes the installation and those thread requests -may time out while Gateway is still working. +up to 600 seconds for a response, which the API requests that wait on a model +call or a whole run all need — skill install and custom-skill edits (each file +is scanned by an LLM), `/api/threads/{id}/compact`, `/api/input-polish`, and +`/api/runs/wait`. If you replace `ingress.annotations`, preserve equivalent +size, streaming, and response-timeout settings for your ingress controller, or +local skill uploads may fail before DeerFlow completes the installation and +those requests may time out while Gateway is still working — for +`/api/runs/wait` the disconnect also cancels the run. Provide your model config under `config` (keep secrets as `$VAR` references — they resolve from the `secrets` map): diff --git a/deploy/helm/deer-flow/templates/configmap-nginx.yaml b/deploy/helm/deer-flow/templates/configmap-nginx.yaml index a6ff886cc..35ea3a0a0 100644 --- a/deploy/helm/deer-flow/templates/configmap-nginx.yaml +++ b/deploy/helm/deer-flow/templates/configmap-nginx.yaml @@ -133,6 +133,10 @@ data: proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $forwarded_proto; + # Installing a .skill archive runs one LLM security scan per file, and a + # custom-skill edit or rollback runs one more; none sets its own timeout. + # The upload endpoint above already allows 600s for the same work. + proxy_read_timeout 600s; } location /api/agents { @@ -227,6 +231,10 @@ data: proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $forwarded_proto; + # /api/runs/wait blocks on a whole run and /api/input-polish on a model + # call. nginx's 60s default would 504 them mid-work, and the waited run is + # cancelled on the disconnect. + proxy_read_timeout 600s; } # Everything else -> frontend (with WebSocket upgrade for HMR/sockets). diff --git a/docker/nginx/nginx.conf b/docker/nginx/nginx.conf index 8c0780b00..516e05dfd 100644 --- a/docker/nginx/nginx.conf +++ b/docker/nginx/nginx.conf @@ -167,6 +167,11 @@ http { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $forwarded_proto; + + # Installing a .skill archive runs one LLM security scan per file, and a + # custom-skill edit or rollback runs one more; none sets its own timeout. + # The upload endpoint above already allows 600s for the same work. + proxy_read_timeout 600s; } # Custom API: Agents endpoint @@ -301,6 +306,11 @@ http { # Disable buffering to avoid permission errors when nginx # runs as a non-root user (e.g. local development). + + # /api/runs/wait blocks on a whole run and /api/input-polish on a model + # call. nginx's 60s default would 504 them mid-work, and the waited run is + # cancelled on the disconnect. + proxy_read_timeout 600s; } # All other requests go to frontend diff --git a/docker/nginx/nginx.local.conf b/docker/nginx/nginx.local.conf index 59f11df1c..0f596682e 100644 --- a/docker/nginx/nginx.local.conf +++ b/docker/nginx/nginx.local.conf @@ -168,6 +168,11 @@ http { proxy_set_header X-Forwarded-Proto $scheme; proxy_buffering off; proxy_cache off; + + # Installing a .skill archive runs one LLM security scan per file, and a + # custom-skill edit or rollback runs one more; none sets its own timeout. + # The upload endpoint above already allows 600s for the same work. + proxy_read_timeout 600s; } # Custom API: Agents endpoint @@ -315,6 +320,11 @@ http { # runs as a non-root user (e.g. local development). proxy_buffering off; proxy_cache off; + + # /api/runs/wait blocks on a whole run and /api/input-polish on a model + # call. nginx's 60s default would 504 them mid-work, and the waited run is + # cancelled on the disconnect. + proxy_read_timeout 600s; } # All other requests go to frontend