[BUG] Claude in Chrome `file_upload` rejects all scheduled-task sessions with misleading error (real cause: INVALID_SESSION)
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
mcp__Claude_in_Chrome__file_upload rejects every file path when called from a Claude Code Desktop scheduled task, with a misleading error message:
Cannot upload "/path/to/file": only files the user has shared with this session can be uploaded.
The real cause (from main.log) is INVALID_SESSION, not a permission/sharing issue. Claude Desktop has two parallel session managers — CCD (used by scheduled tasks) and cowork (used by interactive chat) — and the Chrome MCP file_upload validator (Gv in app.asar) only consults the cowork manager (Ci). For CCD session IDs, Ci.getSession() returns null, so the validator throws [INVALID_SESSION] Unknown session: ... before ever reading the allowlist. The catch wrapper (Bcr / chromeFileUpload) re-emits this as the generic "shared with this session" message the user sees.
This makes file_upload structurally unusable from any CCD scheduled task. No permission/folder/sharing config the user can set will fix it — the session is rejected before the allowlist is consulted.
What Should Happen?
Either:
(a) file_upload should succeed for CCD scheduled-task sessions the same way it does for interactive cowork sessions — by having Gv consult both session managers (ln and Ci), and sourcing the allowlist for CCD sessions from the scheduled-task record's userSelectedFolders field (which is already preserved by the JSON writer but never read);
OR at minimum
(b) the error should surface the real INVALID_SESSION reason instead of masking it as "only files the user has shared". The current message sent me down hours of dead-end debugging trying to "share" the file in every conceivable way (which is impossible for CCD sessions — the validator throws before the allowlist is even checked).
Error Messages/Logs
From ~/Library/Logs/Claude/main.log (test run 2026-05-28):
2026-05-28 11:31:32 [info] Starting local session local_2baaad08-b19c-4a36-8565-fbf84e649d70 in /Users/mmhw/Downloads/MO
2026-05-28 11:31:33 [info] Mapping internal session local_2baaad08-... to CLI session 24635ca6-7c44-4fcd-afe6-2dc61c073d1f
2026-05-28 11:31:36 [info] [CCD start-timing] local_2baaad08-... preflight=801ms ... total_to_assistant=4343ms
...
2026-05-28 11:33:20 [warn] uploadLocalFile: unknown local agent mode session local_2baaad08-b19c-4a36-8565-fbf84e649d70
2026-05-28 11:33:20 [warn] [chromeFileUpload] rejected path for session local_2baaad08-b19c-4a36-8565-fbf84e649d70: /Users/mmhw/Downloads/MO/Standard Audio - Heidi.m4a ([INVALID_SESSION] Unknown session: local_2baaad08-b19c-4a36-8565-fbf84e649d70)
The [CCD start-timing] line confirms the session was spawned via the CCD path. The [chromeFileUpload] line shows the real INVALID_SESSION error that's then re-wrapped as the misleading user-facing message.
User-facing error returned to the SKILL:
> Cannot upload "/Users/mmhw/Downloads/MO/Standard Audio - Heidi.m4a": only files the user has shared with this session can be uploaded.
Steps to Reproduce
- Have Claude Desktop running on macOS with the Claude in Chrome extension installed + connected + authenticated.
- Create a CCD scheduled task — either via the /schedule skill, or by adding an entry to ~/Library/Application Support/Claude/claude-code-sessions/<workspace>/scheduled-tasks.json with cwd, cronExpression, and filePath pointing to a SKILL.md.
- The SKILL.md should: open any web page containing an <input type="file"> element via mcp__Claude_in_Chrome__navigate, find the input via mcp__Claude_in_Chrome__find, then call:
mcp__Claude_in_Chrome__file_upload({
paths: ["/Users/<you>/Downloads/anyfile.m4a"],
ref: "<ref from find>",
tabId: <tabId>
})
- Wait for the cron to fire. file_upload returns the rejection error 100% of the time, regardless of file location.
Things I tried that did NOT fix it (all empirically verified):
- Adding userSelectedFolders: ["/Users/.../Downloads/MO"] to the task record in scheduled-tasks.json (preserved on disk; never propagated to the spawned CCD session JSON; validator throws INVALID_SESSION before reading allowlist anyway)
- Moving the file to /tmp, ~/.claude/, or the task's cwd
- @/folder mention in chat
- Restarting Claude Desktop
I traced the root cause in /Applications/Claude.app/Contents/Resources/app.asar → .vite/build/index.js: Gv only calls Ci.getSession() (cowork manager); CCD session IDs return null; Bcr wraps the thrown error as the generic catch-all message. The CCD buildSessionStartEvent schema (OXt) also explicitly drops userSelectedFolders / userSelectedFiles even when set on the scheduled-task record.
Claude Model
Opus
Is this a regression?
Yes, this worked in a previous version
Last Working Version
2.1.149
Claude Code Version
Claude Code running inside Claude Desktop 1.9255.2
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
Terminal.app (macOS)
Additional Information
Workaround in use: the affected task (tb-prep---heidi, daily 5 AM Heidi audio prep) now creates per-patient Heidi session shells with names pre-filled, then sends a desktop notification telling the user how many shells need manual audio upload + template application. The user opens each in their browser and does the file pick + template by hand.
Three suggested fixes ranked smallest → largest scope:
- Surface the real INVALID_SESSION error to the user instead of masking it as "only files the user has shared". Even a one-line addition like "(INVALID_SESSION: this session manager isn't recognized by file_upload; this may be a CCD scheduled task)" would have saved hours of dead-end debugging.
- Have Gv consult both session managers (ln and Ci). For CCD sessions, source the allowlist from the scheduled-task record's userSelectedFolders field (already preserved by the JSON writer; just never read on the spawn path).
- Unify the spawn paths so CCD buildSessionStartEvent carries userSelectedFolders / userSelectedFiles through the same as cowork's, registers CCD sessions in a unified registry, and lets Gv work uniformly.
Relevant files for engineering:
- /Applications/Claude.app/Contents/Resources/app.asar → .vite/build/index.js (functions Gv, Bcr, OXt, Ci, ln, CCD vs cowork buildSessionStartEvent)
- ~/Library/Logs/Claude/main.log (the [chromeFileUpload] rejected path warnings)
- ~/Library/Application Support/Claude/claude-code-sessions/<workspace>/scheduled-tasks.json (CCD task records)
- ~/Library/Application Support/Claude/claude-code-sessions/<workspace>/local_<id>.json (CCD session JSON — note userSelectedFolders key absent)
- ~/Library/Application Support/Claude/local-agent-mode-sessions/<workspace>/local_<id>.json (cowork session JSON — userSelectedFolders: [] present as explicit empty array)
Showing cached comments. Read the full discussion on GitHub ↗
8 Comments
I can confirm this with another local repro, and it looks like the issue is not Chrome file access or the target website's file input. It is the session boundary used by the Desktop/Chrome bridge.
Environment:
What I observed:
mcp__Claude_in_Chrome__file_uploadis visible/callable, but every local path I tried failed with:This included files that were otherwise readable/attached in the Claude Code session.
The successful session was a Cowork/local-agent-mode session titled
File upload to ChatGPT. Its session id waslocal_<uuid>. The user-attached PDF was copied into that session's own:~/Library/Application Support/Claude/local-agent-mode-sessions/.../local_<uuid>/uploads/<file>.pdfThen this call succeeded:
The returned result was:
Notably,
userSelectedFolderswas empty in the Cowork session metadata, so the success appears to come from the built-in allowlist for the current local session'suploadsdirectory, not from a connected folder.This matches the code-level diagnosis in this issue: the bridge is not simply checking whether the model can read the file. It is checking whether the current session is a recognized local/Cowork session and whether the file is under that session's allowed roots. When the session is not recognized by that manager, the user-facing error is flattened into the misleading "only files the user has shared" message.
This also clarifies the relationship to #31210 / #25871: those were about
file_uploadnot being exposed. This is the next layer down: the tool is exposed and callable, but local path validation rejects sessions that are not registered in the same local-agent/Cowork session registry.cc @bhosmer-ant since you confirmed the related MCP tool exposure fix in #12164. Could you route this to the Chrome/Desktop owner? The fix likely needs to either:
file_uploadvalidate against all Desktop/Claude Code session managers that can expose the tool, orINVALID_SESSIONreason in the user-facing error instead of masking it as a file-sharing problem.Closing for now — inactive for too long. Please open a new issue if this is still relevant.
Hit this exact failure today (2026-08-07) on a scheduled task session. file_upload rejected a path inside the session's own outputs folder with "only files this session is allowed to read can be uploaded" — matches the INVALID_SESSION root cause described above exactly. Session was spawned via the scheduler at 07:18 UTC; the rejected upload attempt was at 10:53. Can confirm the changelog fix for "Claude in Chrome file uploads failing for files in the session's shared folders and outputs" (shipped by v1.26832.0, 2026-08-06) did not resolve this — same error, one day later, same session type. Re-opening or at minimum surfacing the real INVALID_SESSION reason (suggestion 1 in the original report) would save others the same dead-end debugging. Happy to share more session logs if useful.
Also happening to me on mac now, it was working fine a week ago.
Landed here from the same tool failing a different way. On a normal interactive session
mcp__claude-in-chrome__file_uploadrejects thepathsargument withexpected array, received undefinedon every call shape, which traces back to array arguments being serialized as strings (#22394, closed as not planned). Related CDP failure is documented in #32561.Both of those are locked now, so leaving the working alternative here for anyone who arrives from a search.
Serve the file over loopback, fetch it inside the page, attach it through
DataTransfer. That gives the input real bytes from disk rather than a synthetic in-memory file.The non-obvious part is the server side. Chrome refuses a request from an https page to 127.0.0.1 unless the response carries
Access-Control-Allow-Private-Network: true. Without that header the fetch neither resolves nor rejects, it just hangs with nothing in the console.Server and page-side helper packaged here, MIT. https://github.com/kasparovabi/claude-file-bridge
Caveats. The change event is synthetic so
isTrustedis false, and sites that verify event trust will refuse it, though ordinary upload forms accept it. A single longawait fetchinside the javascript tool can also exceed the 45 second CDP evaluate timeout, so park the result onwindowand read it on the next call.Used it for seven images in one session after every
file_uploadcall failed.Thanks for the workaround. Will try it within the next day or so.
Same root cause, but also reproducible from an interactive Cowork session (not just scheduled tasks) — and a newer client build now masks it as a schema error
I hit what looks like this exact bug, but from a normal interactive Cowork chat (no scheduled task involved), with a folder that was connected to the session. Sharing a clean reproduction in case it widens the scope here.
Environment
mcp__claude-in-chrome__file_upload.Two symptoms, split purely by whether the file exists
file_upload({ tabId, ref, paths: [<one path>] }), connected folder, single file:|
pathsvalue | file exists on disk? | result ||---|---|---|
|
["test-dummy-value"]| no |only files this session is allowed to read can be uploaded||
["/tmp/nope.png"]| no | same "allowed to read" message ||
["/Users/<user>/…/img.png"](real file) | yes |-32602 Invalid arguments … "path": ["paths"], "message": "Invalid input: expected array, received undefined"|| byte-identical copy of that file, renamed | yes | identical
paths: undefinederror || a freshly-created 69-byte 1×1 PNG in the same folder | yes | identical
paths: undefinederror |Ruled out as causes by isolating one variable at a time: filename, file size (69 bytes fails), image content (byte-identical copy fails the same), extension (.png/.jpg), the folder, and home-directory path (a short
/Users/<user>/test.pngfor a non-existent file passes). The only determinant is whether the file exists.Why this looks like the same bug
paths: expected array, received undefined. On a later call the tool itself returned: "file_upload can't accept pre-readfilesin this session; it only takespaths… the client converted them before they reached the host and file_upload is unavailable here — tell the user instead of retrying." So a newer client build appears to pre-read the file and convertpaths→ pre-readfilesbefore the host sees it; the host rejects the pre-readfiles, which surfaces aspathsbeing undefined. Because only existing files get read, only existing files hit this — the "exists vs not" split above.Net: same underlying session/validator failure this issue identifies, now surfacing one layer later (as a schema error) once the client started pre-reading files. The scheduled-task framing may be narrower than the actual blast radius — this reproduced in a plain interactive Cowork session.
Regression / persistence
Worked earlier in the same conversation (several successful uploads), then broke mid-session and did not recover after: page reloads, deleting and recreating the target draft, and a full quit-and-reopen of the desktop app, the Chrome extension, and Chrome. Every other Chrome MCP tool (navigate, javascript_tool, find, screenshot) kept working — only
file_uploadwas affected.Suggested diagnostic
If someone can check
main.logaround a failing call forINVALID_SESSION, that would confirm whether interactive Cowork sessions are being routed to the unrecognized manager the same way the scheduled-task sessions are.Workarounds confirmed (don't touch
file_upload)upload_imageis not a substitute — it only sources from a prior screenshot or a chat-uploaded image, not arbitrary local paths.<img width="1200" height="628" alt="Image" src="https://github.com/user-attachments/assets/75a6fc30-7021-41c4-8287-9b2ab58ab8de" />