{"slug": "my-agent-filled-in-every-field-on-the-form-it-could-not-attach-the-three-images", "title": "My agent filled in every field on the form. It could not attach the three images.", "summary": "An engineer found that Claude Code's AI agent could fill every text field in a web form but could not attach PNG images, because file-upload capability is gated differently depending on the surface. The agent's file_upload tool rejected files even when they were in the session's own allowed directory, and the desktop automation server refused to interact with the native file picker because Chrome is restricted to read-only tier.", "body_md": "I had an AI agent filling out a listing form on a web service. Every text field went in. It clicked Save Draft. All that was left was attaching three PNGs.\n\nThat part never worked.\n\nI assumed that if it could fill the form, it could attach the files. Those turned out to be two different capabilities, gated in two different places, and the gate behaves differently depending on how you're running Claude Code. Same machine, same settings, same browser extension, different answer.\n\nMy setup: the Claude Code desktop app on Windows 11, the Claude in Chrome extension, and the computer-use desktop automation server.\n\nThe extension hands the agent a tool called `file_upload`\n\n. Its description tells you exactly how to use it, and what not to do:\n\nUpload one or multiple files to a file input element on the page. Do not click on file upload buttons or file inputs — clicking opens a native file picker dialog that you cannot see or interact with. Instead, use read_page or find to locate the file input element, then use this tool with its ref to upload files directly. Only files the user has shared with this session (attachments, the session's outputs/uploads folders, or folders the user has connected) can be uploaded; other paths will be rejected. The combined size of all files in a single call must stay under 10 MB.\n\nThe first three sentences are the procedure, and the procedure worked immediately: locate the input, pass the ref and a path. The fourth sentence is where everything stopped.\n\n```\nCannot upload \"...\": only files the user has shared with this session can be uploaded.\n```\n\nI tried four ways of \"sharing\" a file. A directory that was in the session's allowed list from the moment it started. The session's own temp folder. A folder approved through the directory-connect tool. A folder added to `additionalDirectories`\n\nin `settings.local.json`\n\n. All four came back with the same sentence.\n\nThe first one deserves a second look. That allowed directory is where the session reads and writes; I had the agent generate the test PNG there. **A session cannot upload a file it wrote itself, thirty seconds earlier, in a directory it is explicitly allowed to write to.**\n\nAttaching the images to the chat doesn't help either, because on this surface a chat attachment never becomes a file. The model receives the image data, but nothing lands on disk. I scanned the temp tree, the config tree, and the app's data folder: zero new PNGs. There's a separate image-upload tool, but it requires an `imageId`\n\n, and chat attachments don't carry one.\n\nNone of this is a bug. The parenthetical in that description limits \"shared\" to exactly three things: chat attachments, the session's outputs/uploads folders, and folders the user connected.\n\nFine — then put the file in one of the three. Except the session directory has no `outputs`\n\nor `uploads`\n\nfolder in it. So I created them, dropped a PNG in each, and tried again:\n\n```\nCannot upload \"...\\outputs\\test-out.png\": only files the user has shared with this session can be uploaded.\nCannot upload \"...\\uploads\\test-up.png\": only files the user has shared with this session can be uploaded.\n```\n\nMatching the names changes nothing. And \"folders the user has connected\" can't mean the directory-connect tool, because that one is already on the rejected list. On this surface, all three of the listed escape hatches are decorative.\n\nThe obvious next thought: the button opens a file picker, so drive the file picker.\n\nThat window isn't File Explorer. It's the common file dialog, owned by `chrome.exe`\n\n. Desktop automation decides permissions per owning process of the frontmost window, so as far as it's concerned, that dialog *is* Chrome.\n\nAnd browsers are pinned to the \"read\" tier in computer-use. Screenshots yes, clicks and keystrokes no. Request access (it returns `granted tier:\"read\"`\n\n), bring Chrome to the front, try a click:\n\n```\n\"Chrome\" is granted at tier \"read\" — visible in screenshots only, no clicks or typing.\n... never use AppleScript, System Events, shell commands, or any other method\nto send clicks or keystrokes to this app.\n```\n\nThe first half is the state. The second half enumerates the workarounds you were about to think of and then closes the list with \"or any other method,\" which also kills route three: File Explorer *is* controllable at full tier, so dragging a file from Explorer onto the browser's drop zone looks viable, right up until you notice it's a way of sending input to Chrome.\n\nBonus trap: if you do click the button, the dialog you opened is now unclosable. Chrome is read-tier, so you can't even send it an Escape. It sits there being modal until a human walks over and dismisses it.\n\nWith all three routes dead I went to the docs, and found a version requirement: local file upload landed in Claude Code v2.1.211.1 Three restrictions come with it — the session must be allowed to read the file, 10 MB total per call, and files with multiple hard links are refused.\n\nSo I ran the same task from the CLI (`claude --chrome`\n\n) on a version past that line. The rejection message is different:\n\n```\nonly files this session is allowed to read ... add its folder with /add-dir\n```\n\nThe test moved from \"was it shared\" to \"can you read it.\" I ran `/add-dir`\n\non my Downloads folder and `file_upload`\n\nwent straight through. Three PNGs, 731 KB total, page JavaScript rendered the previews, all three slots consumed.\n\nMeanwhile, on the same machine, the desktop app still refuses. It sits in the same project, reads the same settings file that the CLI just used, and returns the old \"only files the user has shared with this session.\" Typing the fix into the app gets you this:\n\n```\n/add-dir isn't available in this environment.\n```\n\nThe extension is identical in both cases (Claude in Chrome 1.0.81), so the gate isn't in the extension. It's in Claude Code, implemented per surface.\n\nNow the part that is documentation rather than measurement. The published permission rule reads like my case should work:\n\nPermissions: Claude can upload a file only when the session is allowed to read it\n\nRead it, upload it. My measurements say otherwise. But before calling that a spec violation — that page opens by scoping itself to browser automation \"from the CLI or the [VS Code extension](https://code.claude.com/docs/en/vs-code).\" The desktop app is never mentioned. I had been reading the manual for the surface next to mine.\n\nThe desktop app does have its own manual.2 It even explains the `/add-dir`\n\nresult: commands that open an interactive terminal panel behave differently in the Code tab, and the ones with no argument form answer `isn't available in this environment`\n\n. The message I'd been squinting at was documented behavior all along.\n\nThat manual has a browser section too — but it describes the Browser pane, the app's own built-in browser, which is not the Chrome extension. The Browser pane has no file attachment capability at all. The word \"upload\" does not appear on the page.\n\nAnd then the same page sends you here:\n\nWhen you want Claude to act as you in your logged-in sessions, use the Claude in Chrome extension instead, which shares your browser's login state.\n\nFollow that advice and you arrive at the page that opened by telling you it's about the CLI and the VS Code extension. Two help desks, each politely pointing at the other, neither of them saying anything false.\n\nSo: not a documented condition being violated. An undocumented surface. It took reading both manuals to see that.\n\nOne footnote to save you an hour: if you're in the desktop app and you ask the agent what version you're on, it will shell out to `claude --version`\n\nand report your npm CLI. I got fooled by this. The shell said 2.1.215; the app was actually running the 2.1.219 it ships inside `AppData\\Roaming\\Claude\\claude-code\\`\n\n. There's no desktop-side version command, so the only reliable answer is the executable path of the running process.\n\nThe goal was still reachable, just not through the front door. OS clipboard plus a trusted paste event:\n\n`Set-Clipboard -LiteralPath <file>`\n\nin PowerShell (this puts CF_HDROP on the clipboard)`paste`\n\nlistener on the page`Ctrl+V`\n\nthrough browser automation`clipboardData.files`\n\n, put it into `input[type=file].files`\n\nvia `DataTransfer`\n\n, dispatch `change`\n\nI ran this against the real form's image slot. A 307 KB PNG arrived in `clipboardData.files`\n\nwith name, size, and MIME intact, the page's JavaScript accepted it, the preview rendered, and the save went through. No question was asked about where the file came from.\n\nTwo things cost me time here. Specifying the modifier as a separate parameter does nothing useful — you get a literal `v`\n\ntyped into the page; the key has to be passed as the string `ctrl+v`\n\n. And coordinate clicks land nowhere unless you take a screenshot first, because the coordinate space is defined by the screenshot.\n\nWorking and recommendable are different things, and this route has four problems. It routes around the origin restriction on `file_upload`\n\n— a restriction that presumably exists to stop an agent from pushing local files onto the web without anyone looking, which is not the kind of thing you disable because it's in your way. It clobbers the clipboard, so whatever the user had copied is gone, and when an agent does that silently it reads as a malfunction. It depends on how the page is built: fine if the page watches `input.files`\n\n, dead if a custom uploader only listens for `drop`\n\n. And putting CF_HDROP on the clipboard is Windows-specific. On top of all that, it isn't a supported path, so an update could close it without notice.\n\nWhich brings me to a question. If you know the sanctioned route on the desktop app, or a way to do this without the four problems above, I'd like to hear it. Two candidates I haven't tested: running `claude --chrome`\n\nin the app's built-in terminal, and using the VS Code extension. Both should land on the CLI side of the gate, but I haven't confirmed either.\n\nOne tool name, `file_upload`\n\n, two behaviors: the CLI accepts anything the session can read, the desktop app interrogates where the file came from and says no. When I design work to hand off to an agent, I'd been treating permissions as a property of the settings file. They're also a property of which binary is running.\n\nPractically, that leaves three options for form work that involves attachments: run it from the CLI, keep a clipboard procedure on hand for the desktop app, or leave the attachment step to a human. My own runbook already said the reliable route was manual. It took half a day of building a fourth route to believe it. A known two-minute manual step beats an unknown fully-automated one, and apparently that's a lesson with a tuition fee.\n\n`fetch`\n\nfrom an HTTPS page to `http://127.0.0.1`\n\nneither succeeds nor errors — it just hangs forever. I suspect the browser's local network access check swallowed it, but I didn't confirm that.And the big caveat: everything here is bound to a version and a surface. The CLI already passes cleanly. There's a good chance this whole post is a historical document within six months.\n\n*Verified July 2026. Environment: Windows 11, Claude Code desktop app (bundled version 2.1.219), npm CLI 2.1.215, Claude in Chrome 1.0.81, computer-use. Desktop-app re-measurement and the clipboard route were both run on 2026-07-26.*\n\n[Use Claude Code with Chrome](https://code.claude.com/docs/en/chrome), \"Upload files to web pages.\" ↩\n\n[Desktop application](https://code.claude.com/docs/en/desktop), sections \"Browse external sites,\" \"Choose between the Browser and the Chrome extension,\" and \"What's not available in Desktop.\" ↩", "url": "https://wpnews.pro/news/my-agent-filled-in-every-field-on-the-form-it-could-not-attach-the-three-images", "canonical_source": "https://dev.to/mxhlix/my-agent-filled-in-every-field-on-the-form-it-could-not-attach-the-three-images-23kh", "published_at": "2026-07-29 01:02:08+00:00", "updated_at": "2026-07-29 02:00:21.239648+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools", "ai-products"], "entities": ["Claude Code", "Anthropic", "Chrome", "Windows 11"], "alternates": {"html": "https://wpnews.pro/news/my-agent-filled-in-every-field-on-the-form-it-could-not-attach-the-three-images", "markdown": "https://wpnews.pro/news/my-agent-filled-in-every-field-on-the-form-it-could-not-attach-the-three-images.md", "text": "https://wpnews.pro/news/my-agent-filled-in-every-field-on-the-form-it-could-not-attach-the-three-images.txt", "jsonld": "https://wpnews.pro/news/my-agent-filled-in-every-field-on-the-form-it-could-not-attach-the-three-images.jsonld"}}