mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-06-09 17:12:01 +00:00
In Docker production deployments, LocalSandboxProvider runs inside the
deer-flow-gateway container, so any `sandbox.mounts[].host_path` from
config.yaml is resolved against the gateway container's filesystem — not
the host machine. When the path isn't also bind-mounted into the gateway
service, the mount was silently dropped with only a WARNING log, leaving
agents reading an empty directory in production while the same config
worked under `make dev`.
Escalate the missing-host_path branch to logger.error with explicit
guidance about Docker bind mounts and docker-compose, so the failure is
hard to miss in default log configurations. Skip behaviour is preserved
to avoid breaking existing deployments.
Also clarify the misleading `VolumeMountConfig.host_path` field
description so it documents reality for both providers:
- LocalSandboxProvider checks host_path from inside the gateway process
(host in `make dev`, container in `make up`).
- AioSandboxProvider (DooD) passes host_path straight to `docker -v`
for the sandbox container, where the host Docker daemon resolves it
from the host machine's perspective.
config.example.yaml's `sandbox.mounts` comment gets a Note: block
pointing operators at the docker-compose bind-mount requirement so the
Docker-mode gotcha is discoverable from the canonical template.
Adds a regression test that:
- confirms missing host_path is still skipped (no behaviour break);
- asserts an ERROR record is emitted referencing the offending paths;
- asserts the message contains actionable Docker/gateway/docker-compose
keywords so future refactors can't quietly downgrade it.
Refs: https://github.com/bytedance/deer-flow/issues/3244
This commit is contained in:
parent
16391e35ab
commit
ae9e8bc0bf
@ -4,7 +4,20 @@ from pydantic import BaseModel, ConfigDict, Field
|
||||
class VolumeMountConfig(BaseModel):
|
||||
"""Configuration for a volume mount."""
|
||||
|
||||
host_path: str = Field(..., description="Path on the host machine")
|
||||
host_path: str = Field(
|
||||
...,
|
||||
description=(
|
||||
"Source path for the mount. Resolution depends on the active provider: "
|
||||
"``LocalSandboxProvider`` checks this path from the gateway process — in "
|
||||
"``make dev`` that is the host machine, but in Docker deployments "
|
||||
"(``make up`` / docker-compose) it is the path *inside* the "
|
||||
"``deer-flow-gateway`` container, so the host directory must also be "
|
||||
"bind-mounted into the gateway service for the mount to take effect. "
|
||||
"``AioSandboxProvider`` (DooD) passes this value straight to ``docker -v`` "
|
||||
"for the sandbox container, where it is resolved by the host Docker daemon "
|
||||
"from the host machine's perspective."
|
||||
),
|
||||
)
|
||||
container_path: str = Field(..., description="Path inside the container")
|
||||
read_only: bool = Field(default=False, description="Whether the mount is read-only")
|
||||
|
||||
|
||||
@ -147,7 +147,17 @@ class LocalSandboxProvider(SandboxProvider):
|
||||
mount.container_path,
|
||||
)
|
||||
continue
|
||||
# Ensure the host path exists before adding mapping
|
||||
# Ensure the host path exists before adding mapping.
|
||||
#
|
||||
# ``host_path`` is resolved against the filesystem of the
|
||||
# process running this provider — for ``make dev`` that is
|
||||
# the host machine, but for ``make up`` it is the
|
||||
# ``deer-flow-gateway`` container, so any host path that
|
||||
# isn't bind-mounted into the gateway image will be missing
|
||||
# here. Skipping silently makes this a high-cost-to-debug
|
||||
# silent failure (sandbox skill / tool reads an empty dir
|
||||
# instead of the configured mount), so escalate to ERROR
|
||||
# and include actionable guidance. See #3244.
|
||||
if host_path.exists():
|
||||
mappings.append(
|
||||
PathMapping(
|
||||
@ -157,10 +167,16 @@ class LocalSandboxProvider(SandboxProvider):
|
||||
)
|
||||
)
|
||||
else:
|
||||
logger.warning(
|
||||
"Mount host_path does not exist, skipping: %s -> %s",
|
||||
logger.error(
|
||||
"sandbox.mounts entry %s -> %s ignored: host_path %s does not exist from the "
|
||||
"perspective of the gateway process. In Docker deployments (make up / docker-compose), "
|
||||
"this path must also be bind-mounted into the gateway container — add a matching "
|
||||
"volume entry under services.gateway.volumes in docker/docker-compose.yaml (and use "
|
||||
"the in-container path here), or run in local mode (make dev) where the gateway sees "
|
||||
"the host filesystem directly.",
|
||||
mount.host_path,
|
||||
mount.container_path,
|
||||
mount.host_path,
|
||||
)
|
||||
except Exception as e:
|
||||
# Log but don't fail if config loading fails
|
||||
|
||||
@ -612,6 +612,54 @@ class TestLocalSandboxProviderMounts:
|
||||
|
||||
assert [m.container_path for m in provider._path_mappings] == ["/mnt/skills"]
|
||||
|
||||
def test_setup_path_mappings_logs_actionable_error_for_missing_host_path(self, tmp_path, caplog):
|
||||
"""Regression for #3244.
|
||||
|
||||
When ``sandbox.mounts[].host_path`` is absent from the gateway process's
|
||||
filesystem (the typical symptom in Docker production mode: host_path is a
|
||||
host machine path that is not bind-mounted into the gateway container),
|
||||
the mount is still skipped — but the failure must be a hard-to-miss ERROR
|
||||
log with explicit, actionable guidance about Docker bind mounts, not the
|
||||
old DEBUG/WARNING that buried the silent failure.
|
||||
"""
|
||||
skills_dir = tmp_path / "skills"
|
||||
skills_dir.mkdir()
|
||||
missing_host_path = tmp_path / "does-not-exist"
|
||||
|
||||
from deerflow.config.sandbox_config import SandboxConfig, VolumeMountConfig
|
||||
|
||||
sandbox_config = SandboxConfig(
|
||||
use="deerflow.sandbox.local:LocalSandboxProvider",
|
||||
mounts=[
|
||||
VolumeMountConfig(host_path=str(missing_host_path), container_path="/mnt/knowledge", read_only=True),
|
||||
],
|
||||
)
|
||||
config = SimpleNamespace(
|
||||
skills=SimpleNamespace(container_path="/mnt/skills", get_skills_path=lambda: skills_dir, use="deerflow.skills.storage.local_skill_storage:LocalSkillStorage"),
|
||||
sandbox=sandbox_config,
|
||||
)
|
||||
|
||||
with caplog.at_level("ERROR", logger="deerflow.sandbox.local.local_sandbox_provider"):
|
||||
with patch("deerflow.config.get_app_config", return_value=config):
|
||||
provider = LocalSandboxProvider()
|
||||
|
||||
# Silent-skip behaviour is preserved (no breaking change for existing deployments).
|
||||
assert [m.container_path for m in provider._path_mappings] == ["/mnt/skills"]
|
||||
|
||||
# The failure must be observable at ERROR level and reference the offending paths.
|
||||
error_records = [r for r in caplog.records if r.levelname == "ERROR"]
|
||||
assert error_records, "expected an ERROR log when host_path is missing"
|
||||
message = "\n".join(r.getMessage() for r in error_records)
|
||||
assert str(missing_host_path) in message
|
||||
assert "/mnt/knowledge" in message
|
||||
|
||||
# And it must include actionable Docker guidance so users don't lose hours
|
||||
# to a silent empty-mount failure in production.
|
||||
lowered = message.lower()
|
||||
assert "docker" in lowered
|
||||
assert "gateway" in lowered
|
||||
assert "docker-compose" in lowered
|
||||
|
||||
def test_write_file_resolves_container_paths_in_content(self, tmp_path):
|
||||
"""write_file should replace container paths in file content with local paths."""
|
||||
data_dir = tmp_path / "data"
|
||||
|
||||
@ -768,8 +768,12 @@ sandbox:
|
||||
allow_host_bash: false
|
||||
# Optional: Mount additional host directories into the sandbox.
|
||||
# Each mount maps a host path to a virtual container path accessible by the agent.
|
||||
# Note: with LocalSandboxProvider under `make up` (docker-compose), host_path is
|
||||
# checked from inside the deer-flow-gateway container — you must also bind-mount
|
||||
# the same directory into services.gateway.volumes in docker/docker-compose.yaml
|
||||
# for this mount to take effect (see issue #3244).
|
||||
# mounts:
|
||||
# - host_path: /home/user/my-project # Absolute path on the host machine
|
||||
# - host_path: /home/user/my-project # Absolute path; see note above for Docker mode
|
||||
# container_path: /mnt/my-project # Virtual path inside the sandbox
|
||||
# read_only: true # Whether the mount is read-only (default: false)
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user