fix(sdk): guard empty binary reads with empty-content warning (#3675)

Fixes #3664

---

Empty binary file reads now return the same `"System reminder: File
exists but has empty contents"` warning that empty text reads already
produce, instead of silently emitting a degenerate `{"type": "file",
"base64": "", ...}` content block with `status="success"`.

Previously the `check_empty_content` guard only ran on the text branch
of `_handle_read_result`, so empty binary reads fell through to a
degenerate content block. The fix lifts the guard above the
encoding/type routing into a single check, so empty files return the
warning uniformly whether the extension is a known binary type (e.g.
`.pdf`), unmapped (e.g. `.docx`), or text.

## Social handles (optional)
LinkedIn: https://linkedin.com/in/ninaadrao

---------

Co-authored-by: Mason Daugherty <mason@langchain.dev>
Co-authored-by: Mason Daugherty <github@mdrxy.com>
This commit is contained in:
Ninaad R. Rao
2026-06-03 08:08:18 -07:00
committed by GitHub
parent d9e4976826
commit 2c2cec8747
2 changed files with 73 additions and 10 deletions
@@ -949,9 +949,21 @@ class FilesystemMiddleware(AgentMiddleware[FilesystemState, ContextT, ResponseT]
encoding = read_result.file_data.get("encoding", "utf-8")
content = read_result.file_data["content"]
# Empty files get a uniform warning regardless of encoding/type, so
# check before routing to avoid a degenerate empty content block for
# binary reads.
empty_msg = check_empty_content(content)
if empty_msg:
return ToolMessage(
content=empty_msg,
name="read_file",
tool_call_id=tool_call_id,
status="success",
)
# Route on the backend-declared encoding first: `"base64"` means the
# content is binary and must never be line-numbered as text, even
# when the extension is absent from `_EXTENSION_TO_FILE_TYPE` (#3657).
# when the extension is absent from `_EXTENSION_TO_FILE_TYPE`.
# The extension map is only consulted to pick the multimodal block
# type; unknown binary extensions fall back to the generic `"file"`.
if encoding == "base64" or file_type != "text":
@@ -965,15 +977,6 @@ class FilesystemMiddleware(AgentMiddleware[FilesystemState, ContextT, ResponseT]
status="success",
)
empty_msg = check_empty_content(content)
if empty_msg:
return ToolMessage(
content=empty_msg,
name="read_file",
tool_call_id=tool_call_id,
status="success",
)
content = format_content_with_line_numbers(content, start_line=offset + 1)
# `limit` already bounded raw source lines at the backend; do not
# re-truncate by row count here, or wrapped continuation rows would
@@ -1295,6 +1295,66 @@ class TestFilesystemMiddleware:
expected_mime = mimetypes.guess_type("file.docx")[0] or "application/octet-stream"
assert result.content[0]["mime_type"] == expected_mime
def test_read_file_empty_mapped_binary_returns_empty_content_warning(self):
"""Empty reads of mapped binary extensions (e.g. .pdf) return the empty-file warning (#3664)."""
class EmptyPdfBackend(StateBackend):
def read(self, path, *, offset=0, limit=100):
return ReadResult(
file_data={
"content": "",
"encoding": "base64",
}
)
middleware = FilesystemMiddleware(backend=EmptyPdfBackend())
state = FilesystemState(messages=[], files={})
runtime = ToolRuntime(
state=state,
context=None,
tool_call_id="empty-pdf-1",
store=None,
stream_writer=lambda _: None,
config={},
)
read_file_tool = next(tool for tool in middleware.tools if tool.name == "read_file")
result = read_file_tool.invoke({"file_path": "/docs/report.pdf", "runtime": runtime})
assert isinstance(result, ToolMessage)
assert result.status == "success"
assert result.content == EMPTY_CONTENT_WARNING
def test_read_file_empty_unmapped_binary_returns_empty_content_warning(self):
"""Empty reads of unmapped binary extensions (e.g. .docx) return the empty-file warning (#3664)."""
class EmptyDocxBackend(StateBackend):
def read(self, path, *, offset=0, limit=100):
return ReadResult(
file_data={
"content": "",
"encoding": "base64",
}
)
middleware = FilesystemMiddleware(backend=EmptyDocxBackend())
state = FilesystemState(messages=[], files={})
runtime = ToolRuntime(
state=state,
context=None,
tool_call_id="empty-docx-1",
store=None,
stream_writer=lambda _: None,
config={},
)
read_file_tool = next(tool for tool in middleware.tools if tool.name == "read_file")
result = read_file_tool.invoke({"file_path": "/docs/report.docx", "runtime": runtime})
assert isinstance(result, ToolMessage)
assert result.status == "success"
assert result.content == EMPTY_CONTENT_WARNING
def test_read_file_image_returns_error_when_download_fails(self):
"""Image reads should return a clear backend error string."""