mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-10-26 13:16:24 +00:00
fix(dev): keep whole worktree paths when serve.sh builds DEERFLOW_ROOTS (#5856)
* fix(dev): keep whole worktree paths when serve.sh builds DEERFLOW_ROOTS
serve.sh collects every deer-flow worktree root so `make stop` / `make dev`
can reclaim the shared dev ports (8001/3000/2026) from a sibling checkout.
It parsed `git worktree list --porcelain` with `awk '{print $2}'`, but that
output does not quote paths, so a worktree at `.../deer flow two` was
recorded as `.../deer`. `_is_deerflow_pid` matches open files against
"<root>/", which the truncated root never satisfies, so a Gateway or
frontend started from that worktree was treated as a foreign process and
the start aborted with "port already in use".
Take the remainder of the line after the `worktree ` key instead. The
dedupe and longest-first ordering that follow are unchanged.
The new test builds a scratch repo with a spaced linked worktree and runs
the DEERFLOW_ROOTS block from serve.sh against it, from both the main
checkout and the spaced one.
* docs(changelog): reference #5856 in the worktree-roots entries
---------
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
This commit is contained in:
parent
bbab1e84d0
commit
74f6dce489
@ -2404,6 +2404,12 @@ This release closes that milestone with **772 merged pull requests**.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **dev:** `make stop` / `make dev` can now reclaim dev ports held by a sibling
|
||||
worktree whose path contains spaces. `serve.sh` built its worktree-root list
|
||||
with `awk '{print $2}'` over `git worktree list --porcelain`, whose paths are
|
||||
unquoted, so `.../deer flow two` was recorded as `.../deer`; a Gateway or
|
||||
frontend started from that worktree was never recognised as deer-flow's and
|
||||
the start aborted with "port already in use". The whole path is kept now. ([#5856])
|
||||
- **uploads:** A malformed `files[*].size` in a run's message metadata no
|
||||
longer fails the whole run. `UploadsMiddleware` validated every other field
|
||||
of a client-supplied file entry fail-soft but passed `size` straight to
|
||||
@ -6086,5 +6092,6 @@ with **180 merged pull requests** since the first 2.0 milestone tag.
|
||||
[#5845]: https://github.com/bytedance/deer-flow/pull/5845
|
||||
[#5848]: https://github.com/bytedance/deer-flow/pull/5848
|
||||
[#5855]: https://github.com/bytedance/deer-flow/pull/5855
|
||||
[#5856]: https://github.com/bytedance/deer-flow/pull/5856
|
||||
[#5859]: https://github.com/bytedance/deer-flow/pull/5859
|
||||
|
||||
|
||||
@ -2120,6 +2120,11 @@
|
||||
|
||||
### 修复
|
||||
|
||||
- **开发:** 当同级 worktree 的路径包含空格时,`make stop` / `make dev` 现在也能回收它占用的
|
||||
开发端口。`serve.sh` 用 `awk '{print $2}'` 解析 `git worktree list --porcelain` 来构建
|
||||
worktree 根目录列表,而该输出中的路径不加引号,因此 `.../deer flow two` 被记录成了
|
||||
`.../deer`;从那个 worktree 启动的 Gateway 或前端永远不会被识别为 deer-flow 的进程,
|
||||
启动会以“端口已被占用”中止。现在会保留整条路径。([#5856])
|
||||
- **上传:** 运行消息元数据中格式错误的 `files[*].size` 不再导致整个运行失败。
|
||||
`UploadsMiddleware` 对客户端提供的文件条目的其他字段都做了容错校验,唯独把 `size` 直接交给
|
||||
`int()`,因此 `"abc"` 或列表这样的值会在调用模型之前从 `before_agent` 抛出——而且由于该条目
|
||||
@ -5181,4 +5186,5 @@ DeerFlow 2.0 是围绕"超级智能体"框架的彻底重写,核心包含子
|
||||
[#5845]: https://github.com/bytedance/deer-flow/pull/5845
|
||||
[#5848]: https://github.com/bytedance/deer-flow/pull/5848
|
||||
[#5855]: https://github.com/bytedance/deer-flow/pull/5855
|
||||
[#5856]: https://github.com/bytedance/deer-flow/pull/5856
|
||||
[#5859]: https://github.com/bytedance/deer-flow/pull/5859
|
||||
|
||||
80
backend/tests/test_serve_worktree_roots.py
Normal file
80
backend/tests/test_serve_worktree_roots.py
Normal file
@ -0,0 +1,80 @@
|
||||
"""``scripts/serve.sh`` must discover every deer-flow worktree root verbatim.
|
||||
|
||||
``DEERFLOW_ROOTS`` is the set of checkout roots whose dev-port holders
|
||||
``make stop`` / ``make dev`` may reclaim. It is built from
|
||||
``git worktree list --porcelain``, whose ``worktree <path>`` lines are not
|
||||
quoted, so a root containing whitespace must be taken as the whole remainder
|
||||
of the line — ``awk '{print $2}'`` keeps only the first word, and a sibling
|
||||
worktree at ``.../deer flow two`` was recorded as ``.../deer``, which
|
||||
``_is_deerflow_pid`` can never match.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import shlex
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from support.shell import require_script_bash
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||
SERVE_SH = REPO_ROOT / "scripts" / "serve.sh"
|
||||
|
||||
pytestmark = pytest.mark.skipif(os.name == "nt", reason="exercises git worktree paths through a POSIX shell")
|
||||
|
||||
|
||||
def _extract_deerflow_roots_block() -> str:
|
||||
text = SERVE_SH.read_text(encoding="utf-8")
|
||||
start = text.index('DEERFLOW_ROOTS="$(')
|
||||
chunks: list[str] = []
|
||||
for line in text[start:].splitlines(keepends=True):
|
||||
chunks.append(line)
|
||||
if line.rstrip("\n") == ')"':
|
||||
return "".join(chunks)
|
||||
raise AssertionError("Could not extract the DEERFLOW_ROOTS block from serve.sh")
|
||||
|
||||
|
||||
def _git(*args: str, cwd: Path) -> None:
|
||||
subprocess.run(["git", *args], cwd=cwd, check=True, capture_output=True, env={**os.environ, "GIT_CONFIG_GLOBAL": os.devnull})
|
||||
|
||||
|
||||
def _repo_with_spaced_worktree(tmp_path: Path) -> tuple[Path, Path]:
|
||||
main = tmp_path / "deer-flow"
|
||||
main.mkdir()
|
||||
_git("init", "-q", cwd=main)
|
||||
_git("-c", "user.email=t@example.com", "-c", "user.name=t", "commit", "-q", "--allow-empty", "-m", "init", cwd=main)
|
||||
linked = tmp_path / "deer flow two"
|
||||
_git("worktree", "add", "-q", str(linked), "-b", "wt-two", cwd=main)
|
||||
return main.resolve(), linked.resolve()
|
||||
|
||||
|
||||
def _deerflow_roots(repo_root: Path) -> list[str]:
|
||||
bash = require_script_bash()
|
||||
script = f"""
|
||||
REPO_ROOT={shlex.quote(str(repo_root))}
|
||||
{_extract_deerflow_roots_block()}
|
||||
printf '%s\\n' "$DEERFLOW_ROOTS"
|
||||
"""
|
||||
result = subprocess.run([bash, "-c", script], check=True, capture_output=True, text=True)
|
||||
return [line for line in result.stdout.splitlines() if line]
|
||||
|
||||
|
||||
def test_worktree_root_with_spaces_is_kept_whole(tmp_path):
|
||||
main, linked = _repo_with_spaced_worktree(tmp_path)
|
||||
|
||||
roots = _deerflow_roots(main)
|
||||
|
||||
assert str(linked) in roots
|
||||
assert all(Path(root).is_dir() for root in roots), roots # no truncated fragment such as ".../deer"
|
||||
assert sorted(roots, key=len, reverse=True) == roots # most-specific (longest) root first
|
||||
|
||||
|
||||
def test_repo_root_with_spaces_is_listed_once(tmp_path):
|
||||
main, linked = _repo_with_spaced_worktree(tmp_path)
|
||||
|
||||
roots = _deerflow_roots(linked) # the checkout we run from is itself the spaced path
|
||||
|
||||
assert roots.count(str(linked)) == 1
|
||||
assert set(roots) == {str(main), str(linked)} # and no truncated fragment alongside them
|
||||
@ -98,7 +98,7 @@ DEERFLOW_ROOTS="$(
|
||||
{
|
||||
printf '%s\n' "$REPO_ROOT"
|
||||
git -C "$REPO_ROOT" worktree list --porcelain 2>/dev/null |
|
||||
awk '/^worktree /{print $2}'
|
||||
awk '/^worktree /{sub(/^worktree /, ""); print}' # whole line: paths are unquoted and may contain spaces
|
||||
} | awk 'NF && !seen[$0]++ {print length($0)"\t"$0}' | sort -rn | sed 's/^[0-9]*\t//'
|
||||
)"
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user