fix(make): name what make clean deletes and refuse under a live gateway (#6351)

* fix(make): name what make clean deletes and refuse under a live gateway

`make help` described `make clean` as cleaning up "temporary files", but it
deletes backend/.deer-flow: the local database, users, threads, uploads,
memory, and secrets. Both Docker stacks mount that directory into the
deer-flow-gateway container, and `make stop` only stops local services, so
the data could be deleted under a running Gateway.

Say what is deleted in the help text and before deleting, and refuse while
the deer-flow-gateway container is running. The check runs before `make
stop`, which would otherwise stop the running stack's sandboxes first; a
missing or unreachable Docker passes.

* docs(changelog): reference #6351 for the make clean guard

* fix(make): share make clean's data list and pin stop before the delete

The help line listed "database, threads, uploads, secrets" while the
deletion notice also named users and memory. Define the list once in
RUNTIME_DATA_CONTENTS and use it in both.

Running sandbox containers left behind by `make down` are already stopped
by `make stop` (serve.sh --stop reaps deer-flow-sandbox*), which runs after
the guard and before the delete. Note that in the guard script, and pin
the ordering: the stub stop now records that the data still exists.

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
This commit is contained in:
Hyeonsang Cho 2026-10-06 16:32:46 +09:00 committed by GitHub
parent 928ef24eac
commit 2f833669bd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 205 additions and 6 deletions

View File

@ -162,7 +162,7 @@ Commit Message: {{git_commit_message}}
1. [ ] Review references/troubleshooting.md for common solutions 1. [ ] Review references/troubleshooting.md for common solutions
2. [ ] Check Docker logs: `make docker-logs` 2. [ ] Check Docker logs: `make docker-logs`
3. [ ] Verify configuration file format and content 3. [ ] Verify configuration file format and content
4. [ ] If needed, fully reset the environment: `make clean && make config && make docker-init && make docker-start` 4. [ ] If needed, fully reset the environment: `make docker-stop && make clean && make config && make docker-init && make docker-start`
--- ---

View File

@ -623,6 +623,15 @@ This release closes that milestone with **439 merged pull requests**.
### Fixed ### Fixed
- **make:** `make clean` now says what it deletes and refuses to run under a live
Docker Gateway. `make help` described it as cleaning up "temporary files", but
it deletes `backend/.deer-flow`: the local database, users, threads, uploads,
memory, and secrets. Both Docker stacks mount that directory into the
`deer-flow-gateway` container, which `make stop` leaves running, so the data
could be deleted under a live Gateway. The help text and a notice before
deletion now name the directory, and `make clean` stops with a hint to run
`make down` or `make docker-stop` while that container is running, before it
stops anything. ([#6351])
- **tui:** A new prompt no longer races an interrupted run in the same - **tui:** A new prompt no longer races an interrupted run in the same
conversation. After `Ctrl+C` the run's worker keeps going until its current conversation. After `Ctrl+C` the run's worker keeps going until its current
step returns, so a tool call already in progress (a long shell command, for step returns, so a tool call already in progress (a long shell command, for
@ -8818,3 +8827,4 @@ with **180 merged pull requests** since the first 2.0 milestone tag.
[#6344]: https://github.com/bytedance/deer-flow/pull/6344 [#6344]: https://github.com/bytedance/deer-flow/pull/6344
[#6347]: https://github.com/bytedance/deer-flow/pull/6347 [#6347]: https://github.com/bytedance/deer-flow/pull/6347
[#6350]: https://github.com/bytedance/deer-flow/pull/6350 [#6350]: https://github.com/bytedance/deer-flow/pull/6350
[#6351]: https://github.com/bytedance/deer-flow/pull/6351

View File

@ -558,6 +558,11 @@
### 修复 ### 修复
- **make:** `make clean` 现在会说明它删除的内容,并拒绝在运行中的 Docker Gateway 下执行。`make help`
此前称其清理"临时文件",但它实际删除 `backend/.deer-flow`:本地数据库、用户、线程、上传、记忆和密钥。
两套 Docker 栈都把该目录挂载进 `deer-flow-gateway` 容器,而 `make stop` 不会停止它,因此数据可能在
Gateway 运行时被删除。现在帮助文本和删除前的提示会写明该目录;当该容器仍在运行时,`make clean`
会在停止任何服务之前退出,并提示先执行 `make down` 或 `make docker-stop`。([#6351])
- **TUI:** 新的提示不再与同一会话中被中断的运行发生竞争。按下 `Ctrl+C` 后,该运行的工作线程会 - **TUI:** 新的提示不再与同一会话中被中断的运行发生竞争。按下 `Ctrl+C` 后,该运行的工作线程会
一直执行到当前步骤返回,因此正在进行的工具调用(例如耗时较长的 shell 命令)仍会完成并写入检查点, 一直执行到当前步骤返回,因此正在进行的工具调用(例如耗时较长的 shell 命令)仍会完成并写入检查点,
而下一条提示已在同一线程上运行;最后写入检查点的运行会成为会话历史,新的一轮可能因此悄然消失。 而下一条提示已在同一线程上运行;最后写入检查点的运行会成为会话历史,新的一轮可能因此悄然消失。
@ -7303,3 +7308,4 @@ DeerFlow 2.0 是围绕"超级智能体"框架的彻底重写,核心包含子
[#6343]: https://github.com/bytedance/deer-flow/pull/6343 [#6343]: https://github.com/bytedance/deer-flow/pull/6343
[#6344]: https://github.com/bytedance/deer-flow/pull/6344 [#6344]: https://github.com/bytedance/deer-flow/pull/6344
[#6350]: https://github.com/bytedance/deer-flow/pull/6350 [#6350]: https://github.com/bytedance/deer-flow/pull/6350
[#6351]: https://github.com/bytedance/deer-flow/pull/6351

View File

@ -20,6 +20,8 @@ else
endif endif
FRONTEND_PNPM = $(PYTHON) ../scripts/pnpm.py FRONTEND_PNPM = $(PYTHON) ../scripts/pnpm.py
# What `make clean` deletes; shared by its help line and deletion notice.
RUNTIME_DATA_CONTENTS = database, users, threads, uploads, memory, secrets
help: help:
@echo "DeerFlow Development Commands:" @echo "DeerFlow Development Commands:"
@ -46,7 +48,7 @@ help:
@echo " make start-daemon - Start prod services in background (daemon mode)" @echo " make start-daemon - Start prod services in background (daemon mode)"
@echo " make nginx - Start nginx alone in the foreground (local dev config)" @echo " make nginx - Start nginx alone in the foreground (local dev config)"
@echo " make stop - Stop all running services" @echo " make stop - Stop all running services"
@echo " make clean - Clean up processes and temporary files" @echo " make clean - Stop local services and DELETE local runtime data (backend/.deer-flow: $(RUNTIME_DATA_CONTENTS)) and logs"
@echo "" @echo ""
@echo "Docker Production Commands:" @echo "Docker Production Commands:"
@echo " make up - Build and start production Docker services (localhost:2026)" @echo " make up - Build and start production Docker services (localhost:2026)"
@ -172,9 +174,12 @@ nginx:
stop: stop:
@$(RUN_SHELL_SCRIPT) ./scripts/serve.sh --stop @$(RUN_SHELL_SCRIPT) ./scripts/serve.sh --stop
# Clean up # Clean up: deletes local runtime data, not just temporary files. The guard
clean: stop # runs before stop, which would otherwise stop a running stack's sandboxes.
@echo "Cleaning up..." clean:
@$(RUN_SHELL_SCRIPT) ./scripts/check-data-not-in-use.sh
@$(MAKE) --no-print-directory stop
@echo "Deleting local runtime data in backend/.deer-flow ($(RUNTIME_DATA_CONTENTS)) and logs/*.log..."
@-rm -rf backend/.deer-flow 2>/dev/null || true @-rm -rf backend/.deer-flow 2>/dev/null || true
@-rm -rf logs/*.log 2>/dev/null || true @-rm -rf logs/*.log 2>/dev/null || true
@echo "✓ Cleanup complete" @echo "✓ Cleanup complete"

View File

@ -170,7 +170,7 @@ The project includes a unified cleanup script that handles both runtimes:
All cleanup commands in `Makefile` automatically handle both runtimes: All cleanup commands in `Makefile` automatically handle both runtimes:
```bash ```bash
make stop # Stops all services and cleans up containers make stop # Stops all services and cleans up containers
make clean # Full cleanup including logs make clean # Also deletes local runtime data (backend/.deer-flow) and logs
``` ```
## Testing ## Testing

View File

@ -0,0 +1,132 @@
"""`make clean` says what it deletes and refuses while the Gateway container runs.
The target deletes ``backend/.deer-flow`` -- the database, users, threads,
uploads, and secrets -- but ``make help`` described it as cleaning up
"temporary files". Both Docker stacks mount that directory into the
``deer-flow-gateway`` container, and ``make stop`` only stops local services,
so ``make clean`` used to delete the data under a running Gateway.
These tests run the real ``make clean`` from the real Makefile and guard script
in a scratch checkout. ``scripts/serve.sh`` is a stub that records the stop
call, so nothing touches the host's processes or containers, and ``docker`` is
a fake on a PATH limited to the tools the recipe needs.
"""
from __future__ import annotations
import os
import shutil
import subprocess
from pathlib import Path
import pytest
REPO_ROOT = Path(__file__).resolve().parents[2]
pytestmark = pytest.mark.skipif(
os.name == "nt" or shutil.which("make") is None or shutil.which("bash") is None,
reason="runs the POSIX make recipe",
)
_TOOLS = ("make", "bash", "sh", "rm", "grep", "tr")
def _checkout(tmp_path: Path) -> Path:
root = tmp_path / "checkout"
(root / "scripts").mkdir(parents=True)
shutil.copy2(REPO_ROOT / "Makefile", root / "Makefile")
shutil.copy2(REPO_ROOT / "scripts" / "check-data-not-in-use.sh", root / "scripts" / "check-data-not-in-use.sh")
# Records each stop call and whether the data still existed at that point:
# the real serve.sh --stop also stops deer-flow-sandbox* containers, which
# bind-mount thread directories under backend/.deer-flow.
(root / "scripts" / "serve.sh").write_text(
'#!/usr/bin/env bash\nif [ -e backend/.deer-flow ]; then state=data-present; else state=data-gone; fi\necho "$* $state" >> stop-calls\n',
encoding="utf-8",
)
data = root / "backend" / ".deer-flow" / "data"
data.mkdir(parents=True)
(data / "deerflow.db").write_text("db", encoding="utf-8")
(root / "logs").mkdir()
(root / "logs" / "gateway.log").write_text("log", encoding="utf-8")
return root
def _bin(tmp_path: Path, docker: str | None) -> Path:
bin_dir = tmp_path / "bin"
bin_dir.mkdir()
for tool in _TOOLS:
found = shutil.which(tool)
assert found, tool
(bin_dir / tool).symlink_to(found)
if docker is not None:
fake = bin_dir / "docker"
fake.write_text(f"#!/bin/sh\n{docker}\n", encoding="utf-8")
fake.chmod(0o755)
return bin_dir
def _make(root: Path, bin_dir: Path, target: str = "clean") -> subprocess.CompletedProcess[str]:
env = {"PATH": str(bin_dir), "HOME": str(root)}
return subprocess.run(["make", "-s", target], cwd=root, env=env, capture_output=True, text=True, timeout=60)
def test_help_says_clean_deletes_runtime_data(tmp_path: Path) -> None:
root = _checkout(tmp_path)
help_line = next(line for line in _make(root, _bin(tmp_path, None), "help").stdout.splitlines() if "make clean" in line)
assert "temporary files" not in help_line
assert "DELETE" in help_line and "backend/.deer-flow" in help_line
@pytest.mark.parametrize(
"names",
["deer-flow-gateway", "deer-flow-redis\ndeer-flow-gateway", "deer-flow-gateway\r"],
ids=["gateway", "whole-stack", "crlf"],
)
def test_clean_refuses_while_the_gateway_container_runs(tmp_path: Path, names: str) -> None:
root = _checkout(tmp_path)
listing = names.replace("\n", "\\n").replace("\r", "\\r")
result = _make(root, _bin(tmp_path, f"printf '{listing}\\n'"))
assert result.returncode != 0
assert "deer-flow-gateway container is running" in result.stderr
assert (root / "backend" / ".deer-flow" / "data" / "deerflow.db").exists()
assert (root / "logs" / "gateway.log").exists()
# The guard runs before stop, which would stop the stack's sandboxes.
assert not (root / "stop-calls").exists()
@pytest.mark.parametrize(
"docker",
[None, "exit 1", "printf 'deer-flow-gateway-old\\nmy-deer-flow-gateway\\ndeer-flow-sandbox-1\\n'"],
ids=["no-docker", "daemon-down", "other-containers"],
)
def test_clean_deletes_runtime_data_when_no_gateway_container_runs(tmp_path: Path, docker: str | None) -> None:
root = _checkout(tmp_path)
result = _make(root, _bin(tmp_path, docker))
assert result.returncode == 0, result.stderr
assert "Deleting local runtime data in backend/.deer-flow" in result.stdout
assert not (root / "backend" / ".deer-flow").exists()
assert not (root / "logs" / "gateway.log").exists()
# stop (and with it the sandbox-container cleanup) runs before the deletion.
assert (root / "stop-calls").read_text(encoding="utf-8").split() == ["--stop", "data-present"]
def _listed_contents(line: str, prefix: str) -> list[str]:
return line.split(prefix, 1)[1].split(")", 1)[0].split(", ")
def test_help_and_deletion_notice_list_the_same_data(tmp_path: Path) -> None:
root = _checkout(tmp_path)
bin_dir = _bin(tmp_path, None)
help_line = next(line for line in _make(root, bin_dir, "help").stdout.splitlines() if "make clean" in line)
notice = next(line for line in _make(root, bin_dir).stdout.splitlines() if line.startswith("Deleting local runtime data"))
listed = _listed_contents(help_line, "backend/.deer-flow: ")
assert listed == _listed_contents(notice, "backend/.deer-flow (")
assert {"database", "users", "threads", "uploads", "memory", "secrets"} <= set(listed)

View File

@ -90,6 +90,14 @@ likewise prefix the target with `bash`. This keeps documented `make` commands
working when a source archive, `core.fileMode=false`, or a non-POSIX filesystem working when a source archive, `core.fileMode=false`, or a non-POSIX filesystem
does not preserve executable bits. does not preserve executable bits.
`make clean` deletes `backend/.deer-flow` (database, users, threads, uploads,
secrets), which both compose stacks mount into `deer-flow-gateway`. Its recipe
runs `check-data-not-in-use.sh` before `make stop` (which would stop a live
stack's sandboxes) and refuses while that container runs; an absent or
unreachable Docker passes. `make stop` must still run before the delete: it
stops `deer-flow-sandbox*` containers, whose thread mounts live in that tree.
`RUNTIME_DATA_CONTENTS` feeds both the help line and the deletion notice.
Host-side pnpm calls must go through `scripts/pnpm.py`. With native Windows Host-side pnpm calls must go through `scripts/pnpm.py`. With native Windows
Python (`os.name == "nt"`), it checks `pnpm.cmd` before the generic `pnpm` Python (`os.name == "nt"`), it checks `pnpm.cmd` before the generic `pnpm`
lookup, which uses `PATH`/`PATHEXT` and may select an `.exe` or `.bat` in the lookup, which uses `PATH`/`PATHEXT` and may select an `.exe` or `.bat` in the

View File

@ -0,0 +1,38 @@
#!/usr/bin/env bash
#
# check-data-not-in-use.sh - Refuse while a DeerFlow Gateway container is running
#
# `make clean` deletes backend/.deer-flow, the runtime data directory (database,
# users, threads, uploads, secrets). Both Docker stacks mount it into the
# Gateway container: docker-compose.yaml mounts ${DEER_FLOW_HOME} (default
# backend/.deer-flow) and docker-compose-dev.yaml mounts ../backend/. `make stop`
# only stops local services, so deleting the directory under a running Gateway
# would pull its open database out from under it.
#
# Exits 0 when Docker is missing or unreachable: no container can then be
# holding this checkout's data.
#
# Sandbox containers are not checked here: AIO/DooD sandboxes bind-mount thread
# directories under the same tree, but `make clean` runs `make stop` after this
# check and before deleting, and `serve.sh --stop` stops every
# deer-flow-sandbox* container (as `make docker-stop` does). Checking them here
# would refuse the ordinary local AIO case that stop already cleans up.
set -e
GATEWAY_CONTAINER="deer-flow-gateway"
if ! command -v docker >/dev/null 2>&1; then
exit 0
fi
# Match the exact name: `docker ps --filter name=` is a substring match. Docker
# on Windows may end lines with CRLF.
if ! names=$(docker ps --format '{{.Names}}' 2>/dev/null); then
exit 0
fi
if printf '%s\n' "$names" | tr -d '\r' | grep -qx "$GATEWAY_CONTAINER"; then
echo "Refusing to delete DeerFlow runtime data: the $GATEWAY_CONTAINER container is running, and both Docker stacks mount backend/.deer-flow into it." >&2
echo "Stop the Docker stack first (make down, or make docker-stop for the dev stack), then run make clean again." >&2
exit 1
fi