Read a message with a file attached as its words and the file, not as [object Object] - #506
Merged
davidmckayv merged 2 commits intoSep 12, 2026
Conversation
kevin9327
requested review from
MikeRyanDev,
davidmckayv,
guidovizoso,
mxmzb and
tylerslaton
as code owners
September 12, 2026 21:46
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
kevin9327
force-pushed
the
fix/bot-reads-attachment-content
branch
2 times, most recently
from
September 12, 2026 22:07
3e28fa8 to
bd5b14a
Compare
… [object Object] With a file attached, the composer sends a message as a list of parts, and the server resolves each file into a text or image part before the run reaches a remote Bot. agent-bot and agent-langgraph both read the message with String(), so the model was sent "[object Object],[object Object]" in place of the question and the file. Both Bots now read the parts through one shared function: text parts verbatim, an image with a data source as an image_url data URL, and any other part named rather than dropped. A string message is unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
kevin9327
force-pushed
the
fix/bot-reads-attachment-content
branch
from
September 12, 2026 22:08
bd5b14a to
698dcab
Compare
# Conflicts: # CHANGELOG.md
davidmckayv
approved these changes
Sep 12, 2026
davidmckayv
left a comment
Contributor
There was a problem hiding this comment.
Deep-reviewed clean (validation, no secret leak, fail-closed, agrees with existing layers). CI green.
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.
What happens
Attach a file to a message in a channel whose coworker is the Bot in the box (
agent-bot) or the LangGraph Bot (agent-langgraph), and ask about it:The Bot answers as though it was asked nothing, because what its model was sent is:
That string is in place of the question and the file. An image goes the same way.
Why
With no file attached, the composer sends a message as a string. With one attached, it sends a list of parts: what the person typed, then the file (
channel-chat.tsx). The server resolves each file before the run leaves it.inlineAttachmentsincopilot.tsturns a text file into a text part carrying its contents, and an image into animagepart with adatasource. That comment says the reason it is done in the remote Bot's middleware is so a file does not reach the endpoint as a URL it cannot fetch.Both Bots then read the message like this:
String()of an array of objects is[object Object],[object Object]. So the file arrives intact at the Bot, and the Bot's last step throws it away along with the words beside it.Run against
main, with the exact content shapeinlineAttachmentsproduces (typed text, a CSV's text, a PNG):Built-in Bots are not affected. The runtime converts their content itself.
The change
One function,
shared/user-content.ts, read by both Bots, for the reasonNO_ANSWER_CAMElives inshared/: two copies of this would drift. It maps each part to the two content blocks both providers read:{ type: "text", text }, verbatim;datasource →{ type: "image_url", image_url: { url: "data:<mime>;base64,<bytes>" } };[audio]. It is named rather than dropped, for the reasonresultTextinplugins/mcp.tsnames a tool part it cannot read. A model told something was attached can say it cannot see it. A model handed nothing answers as though nothing was attached.Those two block shapes are what OpenAI chat completions take. They are also what
@langchain/openai,@langchain/anthropicand@langchain/google-genaieach convert (each has animage_urlbranch that accepts a base64 data URL), so the LangGraph Bot works on all three of its providers.A message that is a string, which is nearly every message, is returned exactly as before.
Where it runs
Boundary and audit
Untouched. No acting call, refusal or audit row is involved. This is the last step of turning a run's input into the model's prompt.
Changelog
A line under
Unreleased, because a Bot that could not read an attached file now can.Proof
bun test tests/history.test.tsin each Bot, onmainwith only this PR's tests applied (absolute paths shortened to the repository root, nothing else edited):agent-bot: 10 pass, 3 failagent-langgraph: 6 pass, 3 fail (the same three, against LangChain's message)Each failure's received value is
"[object Object],[object Object]", identical to theagent-botoutput above.With the change:
agent-bot13 pass, 0 fail,agent-langgraph9 pass, 0 fail.Not a widening. In each file the fourth test,
sends a message that is only text exactly as it was typed, passes both before and after. It pins that a string message is returned verbatim, so the only messages this changes are the ones carrying parts. Every test that was already in both files (tool-call pairing, out-of-order results, restored calls, A2UI context) passes before and after.Whole packages (
bun testin each Bot directory):mainagent-botagent-langgraphThe delta is exactly the four tests added to each.
bunx @biomejs/biome checkandbunx prettier --checkare clean on the five TypeScript files.prettier --check CHANGELOG.mdreports style issues, but it does onmaintoo: every suggestion is a missing blank line further down the file, none in the lines added here, so I have left that file's existing formatting alone.Note on the CHANGELOG
The entry goes at the top of
## Unreleased, the one line every entry goes at, so it will conflict with any other PR open against that anchor. Happy to rebase whenever it suits you.🤖 Generated with Claude Code