bytedance/deer-flow · error · HTTPException
Agent '{name}' only exists in the legacy shared layout and i
Error message
Agent '{name}' only exists in the legacy shared layout and is not scoped to a user. Run scripts/migrate_user_isolation.py to move legacy agents into the per-user layout before updating. What it means
409 from PUT `/agents/{name}`: the agent exists only in the legacy shared layout — `user_agent_dir/user/config.yaml` is missing while the shared `agent_dir/config.yaml` exists. Note the guard deliberately checks the config file, not directory existence, because per-user dirs can hold only `memory.json` from chatting with a legacy agent; a bare `.exists()` would silently fork a fresh config into a memory-only dir (issue #3390). Updates are blocked until migration; the db backend has no legacy layout so this is file-backend-only.
Source
Thrown at backend/app/gateway/routers/agents.py:395
raise HTTPException(status_code=404, detail=f"Agent '{name}' not found")
def _is_legacy_only_layout() -> bool:
# Require config.yaml, not bare directory existence — a per-user agent
# directory can exist containing only memory.json (written the first
# time this user chats with a legacy shared agent, before this route
# is ever called). Bare .exists() would miss that case and let this
# fall through to a silent fork of a brand-new config.yaml/SOUL.md
# into the memory-only directory instead of blocking (mirrors
# resolve_agent_dir's guard, see #3390). The db backend has no legacy
# shared layout, so this file-only guard is a no-op there. The .exists()
# probes are filesystem IO, so they run off the event loop.
paths = get_paths()
agent_dir = paths.user_agent_dir(user_id, name)
legacy_dir = paths.agent_dir(name)
return not (agent_dir / "config.yaml").exists() and (legacy_dir / "config.yaml").exists()
if await asyncio.to_thread(_is_legacy_only_layout):
raise HTTPException(
status_code=409,
detail=(f"Agent '{name}' only exists in the legacy shared layout and is not scoped to a user. Run scripts/migrate_user_isolation.py to move legacy agents into the per-user layout before updating."),
)
if "model" in request.model_fields_set:
_validate_model_exists(request.model)
try:
# Update config if any config fields changed
# Use model_fields_set to distinguish "field omitted" from "explicitly set to null".
# This is critical for skills where None means "inherit all" (not "don't change").
fields_set = request.model_fields_set
config_changed = bool(fields_set & ({"description", "tool_groups", "skills"} | set(_MODEL_BEHAVIOR_FIELDS)))
updated: dict | None = None
if config_changed:
updated = {
"name": agent_cfg.name,View on GitHub (pinned to 1dd6ba1acb)
Solutions
- Run `scripts/migrate_user_isolation.py` to move legacy agents into the per-user layout, then retry the PUT
- Verify migration succeeded by confirming `user_agent_dir/<user>/<name>/config.yaml` now exists
- Do not hand-copy config.yaml into the memory-only dir — that recreates the silent-fork bug the guard prevents
Example fix
# before PUT /agents/old-shared-agent # 409 legacy layout # after python scripts/migrate_user_isolation.py PUT /agents/old-shared-agent # 200
Defensive patterns
Strategy: try-catch
Try / catch
try { return await api.updateAgent(name, body); }
catch (e) {
if (e.status === 409 && /legacy shared layout/.test(e.detail)) {
showAdminNotice('Run scripts/migrate_user_isolation.py, then retry');
return;
}
throw e;
} Prevention
- Run migrate_user_isolation.py as a mandatory step of any upgrade that introduces per-user agents
- Never hand-copy config.yaml into a memory-only per-user agent dir — it triggers the silent-fork bug
- Test update flows against the oldest deployment data you support
When it happens
Trigger: Upgrading a deployment that predates per-user agent isolation, then editing one of the old shared agents; a user who has chatted with a legacy agent (memory.json written) attempting to update it.
Common situations: Post-upgrade administrations hitting 409 on all pre-existing agents; mixed fleets where only some nodes ran the migration script.
Related errors
- Agent '{name}' only exists in the legacy shared layout and i
- Agent '{name}' not found
- Agent '{normalized_name}' already exists
- Directory for '{name}' contains memory data but is not a cus
- Failed to update agent: ${res.statusText}
AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14).
Data as JSON: /api/errors/a8b1a97a8166fbe9.
Report an issue: GitHub.