Repository navigation
fix(mcp): insert bare-filename rewrites literally - #5522
Merged
WillemJiang merged 1 commit intoSep 18, 2026
Merged
WillemJiang merged 1 commit into
WillemJiang merged 1 commit into
Conversation
_rewrite_unique_bare_filenames handed the correlated /mnt/user-data virtual path to Pattern.subn as a replacement template. That path is built from the file's relative path, and a backslash is an ordinary character in a POSIX filename, so a file written literally as "screenshots\q3.png" -- the shape a model produces by passing a Windows-style path to a stdio server on a POSIX host -- turned \q into an unknown template escape. Pattern.subn compiles the template eagerly, so re.error escaped _convert_call_tool_result and failed the whole tool call even though the server had already written the file, and the agent never saw the path. When the backslash does start a known escape (\r, \t, \b ...), the bare-filename pass substituted that byte into the returned text instead, so "screenshots\raw.png" came back as a path with a raw CR in the middle of it. Insert the correlated path through a callable replacement, matching what _rewrite_local_paths_in_text already does, so it is never parsed as a template.
WillemJiang
approved these changes
Sep 17, 2026
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.
Why
_rewrite_unique_bare_filenamescorrelates a bare filename in a stdio server's free text with the files this tool call created, then substitutes the correlated/mnt/user-data/...virtual path. It handed that path toPattern.subnas a replacement template (backend/packages/harness/deerflow/mcp/tools.py:280).The replacement is built from the real file's relative path (
_local_uri_to_virtual_path,tools.py:169), and a backslash is an ordinary character in a POSIX filename. A model that passes a Windows-style path to a stdio MCP server on a POSIX host gets a file literally namedscreenshots\q3.png, so the template contained\q— an unknown escape.Pattern.subncompiles the template before it looks for a match, so this raised no matter what the text said:re.error: bad escape \q at position 36escaped_convert_call_tool_resultand failed the whole MCP tool call, even though the server had already written the file. The agent saw an error instead of the path.redoes accept, the pass substituted that byte into the returned text:screenshots\raw.pngcame back with a raw CR in the middle of the path, which then rides into the transcript and the checkpoint.changed_filesis the workspace snapshot diff (_changed_workspace_files,tools.py:194), so the offending file does not have to be one this MCP call wrote — any file in the thread workspace with such a name trips the next tool call that returns text content.This is the only
.sub/.subncall in the harness that passes a built string as a template;_rewrite_local_paths_in_text(tools.py:326) andinput_sanitization_middleware.py:160both insert through a callable.What changed
_rewrite_unique_bare_filenamesnow substitutes through a callable replacement, so the correlated virtual path is inserted verbatim and a backslash in it is never parsed as a regex escape.backend/tests/test_mcp_file_migration.pycover both halves: a backslash that starts a live escape (\r) must survive as two characters, and onererejects (\q) must not raise.No change to which filenames are correlated, to the pattern, or to the
\r-free cases. They are skipped on Windows, where a literal backslash cannot be part of a filename.Surface area
None of the listed boxes apply — this is the MCP harness tool-result layer (
backend/packages/harness/deerflow/mcp/tools.py), not an endpoint underbackend/app, an agent node/graph/prompt change, or a sandbox/skills/dependency change.Bug fix verification
backend/tests/test_mcp_file_migration.py::TestRewriteUniqueBareFilenames(2 tests)mainand green on this branch? yesRed on
main(tests applied, source fix reverted):with
AssertionError: assert 'Saved as /mn...shots\raw.png' == 'Saved as /mn...hots\\raw.png'(the\rbecame a CR) andre.error: bad escape \q at position 36.Also confirmed end-to-end through
_convert_call_tool_result: onmainit raiseserror bad escape \q at position 36; on this branch it returnsSaved as /mnt/user-data/workspace/screenshots\q3.png.Validation
AI assistance
Tool(s) used: Claude Code
How you used it: Claude Code drafted the change, the regression tests and the commit message against a spec I wrote; I ran the red/green cycle myself (including reverting the source fix to confirm the tests fail on
main), ran the full backend suite andmake lint, and reviewed every line of the final diff.