Cherry-pick three high-value upstream fixes - #2
Merged
Conversation
Cherry-pick of upstream e5b1490 (bytedance#1965) by Saber, adapted from backend/packages/harness/deerflow/subagents/executor.py to our backend/src/subagents/executor.py layout. When SubagentExecutor.execute() is called from within an already-running event loop (e.g. when the parent agent uses async/await), calling asyncio.run() creates a new event loop that conflicts with asyncio primitives bound to the parent loop (e.g. httpx.AsyncClient). Fix: detect a running event loop and submit the work to a dedicated thread pool (`get_isolated_loop_pool()`) with its own isolated loop. Local adaptation: our fork uses lazy thread-pool initialisation via get_*_pool() helpers, so the new isolated_loop_pool follows the same pattern (rather than upstream's eager module-level construction). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cherry-pick of upstream 898f4e8 (bytedance#2251) by DanielWalnut, adapted from upstream's split storage.py + updater.py to our consolidated backend/src/agents/memory/updater.py. Three bugs fixed: 1. Deep-copy current_memory before passing to _apply_updates() so a subsequent _save_memory_to_file() failure cannot leave a partially-mutated object in the storage cache. 2. Add _cache_lock (threading.Lock) and acquire it around every read/write of _memory_cache. Without it the background memory-update timer thread and HTTP reload calls race on the dict. 3. Replace in-place mutation memory_data["lastUpdated"] = ... with a shallow copy memory_data = {**memory_data, "lastUpdated": ...} so _save_memory_to_file() no longer silently modifies the caller's dict. Local adaptation: upstream split storage from updater into a separate storage.py which our fork doesn't have. The same three patterns appear in our updater.py at slightly different line numbers and were patched in place. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nt conversion Cherry-pick of upstream 80e210f (bytedance#2332) by Hinotobi, adapted from backend/app/gateway/routers/uploads.py + the now-extracted utils/file_conversion.py to our backend/src/gateway/routers/uploads.py where conversion is still inline. Without this gate, every uploaded PDF/PPT/Excel/Word file was fed through markitdown on the host process. markitdown depends on a stack of PDF/Office parsers; bugs in those parsers become sandbox-escape vectors when the input is attacker-controlled. Fix: gate auto-conversion on uploads.auto_convert_documents in config.yaml. Default is False. Operators on closed deployments who want the convenience can opt in explicitly. Local adaptation: our fork doesn't have a separate file_conversion.py module — convert_file_to_markdown is defined inline in uploads.py. The gate is applied at the call site in upload_files() with the same logic upstream uses. config.example.yaml documents the new option. The user-facing docstring updated to match. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Three targeted cherry-picks from
bytedance/deer-flowupstreammain(which we are 362 commits behind, with major restructure). All paths re-mapped from upstream'sbackend/packages/harness/deerflow/layout to ourbackend/src/layout. Each commit credits the original upstream author and PR.Commits
1.
[security] fix(uploads): require explicit opt-in for host-side document conversionUpstream:
80e210f5(bytedance#2332). CVE-class. Without this gate, every uploaded PDF/PPT/Excel/Word file is fed through markitdown on the host process. markitdown depends on a stack of PDF/Office parsers; bugs there are sandbox-escape vectors when the input is attacker-controlled.uploads.auto_convert_documents(defaultfalse)config.example.yamldocuments the option with the security rationaleupload_files()gated on the flag2.
fix(memory): cache corruption, thread-safety, and caller mutation bugsUpstream:
898f4e8a(bytedance#2251). Three bugs all present in our fork'sbackend/src/agents/memory/updater.py:_apply_updates(current_memory, ...)mutates in place, so a subsequent save failure leaves the cached object partially mutated. Fix:copy.deepcopy(current_memory)before passing._memory_cachereads/writes without a lock; the background memory-update timer thread races with HTTP reload calls. Fix: new_cache_lock(threading.Lock) wrapping every access._save_memory_to_file()didmemory_data["lastUpdated"] = ...on the caller's dict. Fix: shallow copy ({**memory_data, "lastUpdated": ...}).Local adaptation: upstream split storage from updater into a separate
storage.pywhich our fork doesn't have. Same three patterns appear in our consolidatedupdater.pyand were patched in place.3.
fix(subagent): event loop conflict in SubagentExecutor.execute()Upstream:
e5b14906(bytedance#1965). Whenexecute()is called from inside an already-running event loop (an async parent agent),asyncio.run()creates a new loop that conflicts with asyncio primitives bound to the parent loop (httpx clients, etc.).get_isolated_loop_pool()lazy thread poolexecute()detects a running loop and submits the work to that poolfinally(cancel pending tasks, shutdown asyncgens, restore previous loop)Local adaptation: our fork uses lazy pool init via
get_*_pool()helpers — the new isolated pool follows the same pattern.Why these three
From the 362-commit lag survey, these were the highest-value fixes that we certainly care about:
What this PR does NOT do
76803b82would conflict with most files in our fork).present_file_tool.py(upstreamf4c17c66) — partially overlaps with our open PR fix: middleware crashes when run is created without a context dict #1 (the middleware context fallback) and is better folded into that helper if we adopt the same pattern there.30d619de,ac04f270) or the LLM circuit breaker (4d4ddb3d) — those are nice but not urgent.Verified
virtaava/mainis one commit behind your localmain(the unpushedfeat: harness ACI toolscommit + yourconfig.yamlaren't here), but the deerflow-langgraph service starts cleanly with the cherry-picks once you have a validconfig.yamlin placeSuggested merge order
PR #1 (middleware context fallback) first, then this one — they don't conflict, but the order keeps the history readable.
🤖 Generated with Claude Code