🛂 fix: Skip Inherited / Mark Skill Files Read-Only in Code-Env Pipeline#12866
Merged
Conversation
When a bash/code-interpreter call lists or operates on inputs the user already owns (skill files primed via primeInvokedSkills, files inherited from a prior session), codeapi echoes those files back in the tool result with `inherited: true`. We were treating every entry as a generated artifact and calling processCodeOutput on each, which: 1. Hit `/api/files/code/download/<session_id>/<file_id>` with the user's session key. Skill files are uploaded under the skill's entity_id, so every download 403'd — producing dozens of "Unauthorized download" log lines per turn. 2. Surfaced those inputs as ghost file chips in the UI even though they were never generated by the run. 3. Wasted a download round-trip even when no auth boundary was crossed — the file is already persisted at its origin. Fix: skip files where `file.inherited === true` in all three artifact-files loops (`tools.js`, `createToolEndCallback`, and `createResponsesToolEndCallback`). Skill files remain available to subsequent calls via primeInvokedSkills / session inheritance — we just don't redundantly re-download them. Pairs with codeapi-side change that adds the `inherited` flag.
Contributor
There was a problem hiding this comment.
Pull request overview
This PR updates the server-side handling of execute_code tool results to avoid re-processing code-env file refs that are merely echoed back as unchanged inputs (e.g., skill files or session-inherited inputs) by honoring a new file.inherited flag from codeapi.
Changes:
- Skip
artifact.filesentries flagged asinheritedin the synchronous tool invocation controller. - Skip
output.artifact.filesentries flagged asinheritedin both streaming tool-end callbacks (classic + Responses API). - Add inline rationale comments explaining why inherited passthrough files should not be post-processed into attachments.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| api/server/controllers/tools.js | Skips inherited file refs during synchronous execute_code artifact processing. |
| api/server/controllers/agents/callbacks.js | Skips inherited file refs during streaming artifact processing for both standard and Responses API callbacks. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+189
to
193
| if (file.inherited) { | ||
| continue; | ||
| } | ||
| const { id, name } = file; | ||
| artifactPromises.push( |
Comment on lines
+549
to
+553
| * user's session key 403s when the file is entity-scoped, and the | ||
| * input is already persisted at its origin. They remain available | ||
| * to subsequent calls via primeInvokedSkills / session inheritance. */ | ||
| if (file.inherited) { | ||
| continue; |
Comment on lines
+775
to
+779
| * user's session key 403s when the file is entity-scoped, and the | ||
| * input is already persisted at its origin. They remain available | ||
| * to subsequent calls via primeInvokedSkills / session inheritance. */ | ||
| if (file.inherited) { | ||
| continue; |
Contributor
GitNexus: 🚀 deployedThe |
Pairs with codeapi `read_only` upload flag (ClickHouse/ai#1345). When LibreChat primes a skill into the code-env, every file in the batch (SKILL.md plus all bundled scripts/schemas/docs) is now uploaded with `read_only: true`. Codeapi seals these inputs at the filesystem layer (chmod 444) and the walker echoes the original refs as `inherited: true` regardless of whether sandboxed code modified the bytes on disk. Without this, the previous PR's `inherited` skip handled only the unchanged case. A modified skill file (pip writing pyc near a .py, a script accidentally truncating LICENSE.txt, etc.) still flowed through the modified-input branch on codeapi, got a fresh user-owned file_id, uploaded as a "generated" artifact, and surfaced in the UI as a chip the user couldn't actually authorize a download for. Changes: - `api/server/services/Files/Code/crud.js`: `batchUploadCodeEnvFiles({ ..., read_only })` forwards the flag as a multipart form field. Default `false` preserves existing behavior for user-attached files and prior-session inheritance. - `packages/api/src/agents/skillFiles.ts`: type signature gains `read_only?: boolean`; `primeSkillFiles` passes `true`. - `packages/api/src/agents/skillFiles.spec.ts`: assert the upload call carries `read_only: true`. The flag is intentionally not skill-specific. Any future infrastructure-input flow (system fixtures, cached datasets, etc.) can opt in the same way.
Contributor
GitNexus: 🚀 deployedThe |
Merged
5 tasks
fuuuzzy
pushed a commit
to fuuuzzy/LibreChat
that referenced
this pull request
May 3, 2026
…ne (danny-avila#12866) * 🛂 fix: Skip Re-Download of Inherited Code-Env Files (No More 403 Storms) When a bash/code-interpreter call lists or operates on inputs the user already owns (skill files primed via primeInvokedSkills, files inherited from a prior session), codeapi echoes those files back in the tool result with `inherited: true`. We were treating every entry as a generated artifact and calling processCodeOutput on each, which: 1. Hit `/api/files/code/download/<session_id>/<file_id>` with the user's session key. Skill files are uploaded under the skill's entity_id, so every download 403'd — producing dozens of "Unauthorized download" log lines per turn. 2. Surfaced those inputs as ghost file chips in the UI even though they were never generated by the run. 3. Wasted a download round-trip even when no auth boundary was crossed — the file is already persisted at its origin. Fix: skip files where `file.inherited === true` in all three artifact-files loops (`tools.js`, `createToolEndCallback`, and `createResponsesToolEndCallback`). Skill files remain available to subsequent calls via primeInvokedSkills / session inheritance — we just don't redundantly re-download them. Pairs with codeapi-side change that adds the `inherited` flag. * 🔒 feat: Mark Skill Files as `read_only` During Code-Env Priming Pairs with codeapi `read_only` upload flag (ClickHouse/ai#1345). When LibreChat primes a skill into the code-env, every file in the batch (SKILL.md plus all bundled scripts/schemas/docs) is now uploaded with `read_only: true`. Codeapi seals these inputs at the filesystem layer (chmod 444) and the walker echoes the original refs as `inherited: true` regardless of whether sandboxed code modified the bytes on disk. Without this, the previous PR's `inherited` skip handled only the unchanged case. A modified skill file (pip writing pyc near a .py, a script accidentally truncating LICENSE.txt, etc.) still flowed through the modified-input branch on codeapi, got a fresh user-owned file_id, uploaded as a "generated" artifact, and surfaced in the UI as a chip the user couldn't actually authorize a download for. Changes: - `api/server/services/Files/Code/crud.js`: `batchUploadCodeEnvFiles({ ..., read_only })` forwards the flag as a multipart form field. Default `false` preserves existing behavior for user-attached files and prior-session inheritance. - `packages/api/src/agents/skillFiles.ts`: type signature gains `read_only?: boolean`; `primeSkillFiles` passes `true`. - `packages/api/src/agents/skillFiles.spec.ts`: assert the upload call carries `read_only: true`. The flag is intentionally not skill-specific. Any future infrastructure-input flow (system fixtures, cached datasets, etc.) can opt in the same way.
jcbartle
pushed a commit
to jcbartle/LibreChat
that referenced
this pull request
May 11, 2026
…ne (danny-avila#12866) * 🛂 fix: Skip Re-Download of Inherited Code-Env Files (No More 403 Storms) When a bash/code-interpreter call lists or operates on inputs the user already owns (skill files primed via primeInvokedSkills, files inherited from a prior session), codeapi echoes those files back in the tool result with `inherited: true`. We were treating every entry as a generated artifact and calling processCodeOutput on each, which: 1. Hit `/api/files/code/download/<session_id>/<file_id>` with the user's session key. Skill files are uploaded under the skill's entity_id, so every download 403'd — producing dozens of "Unauthorized download" log lines per turn. 2. Surfaced those inputs as ghost file chips in the UI even though they were never generated by the run. 3. Wasted a download round-trip even when no auth boundary was crossed — the file is already persisted at its origin. Fix: skip files where `file.inherited === true` in all three artifact-files loops (`tools.js`, `createToolEndCallback`, and `createResponsesToolEndCallback`). Skill files remain available to subsequent calls via primeInvokedSkills / session inheritance — we just don't redundantly re-download them. Pairs with codeapi-side change that adds the `inherited` flag. * 🔒 feat: Mark Skill Files as `read_only` During Code-Env Priming Pairs with codeapi `read_only` upload flag (ClickHouse/ai#1345). When LibreChat primes a skill into the code-env, every file in the batch (SKILL.md plus all bundled scripts/schemas/docs) is now uploaded with `read_only: true`. Codeapi seals these inputs at the filesystem layer (chmod 444) and the walker echoes the original refs as `inherited: true` regardless of whether sandboxed code modified the bytes on disk. Without this, the previous PR's `inherited` skip handled only the unchanged case. A modified skill file (pip writing pyc near a .py, a script accidentally truncating LICENSE.txt, etc.) still flowed through the modified-input branch on codeapi, got a fresh user-owned file_id, uploaded as a "generated" artifact, and surfaced in the UI as a chip the user couldn't actually authorize a download for. Changes: - `api/server/services/Files/Code/crud.js`: `batchUploadCodeEnvFiles({ ..., read_only })` forwards the flag as a multipart form field. Default `false` preserves existing behavior for user-attached files and prior-session inheritance. - `packages/api/src/agents/skillFiles.ts`: type signature gains `read_only?: boolean`; `primeSkillFiles` passes `true`. - `packages/api/src/agents/skillFiles.spec.ts`: assert the upload call carries `read_only: true`. The flag is intentionally not skill-specific. Any future infrastructure-input flow (system fixtures, cached datasets, etc.) can opt in the same way.
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.
Summary
Two related fixes for how LibreChat handles files echoed back by the codeapi sandbox in
output.artifact.files.Problem
When a bash/code-interpreter call lists or imports inputs the user already owns (skill files primed via
primeInvokedSkills, files inherited from a prior session), codeapi echoes those files back in the tool result so subsequent calls retain visibility.We were treating every entry as a generated artifact and calling
processCodeOutputon each:entity_id, butprocessCodeOutputdownloads with the user's session key. Every skill file 403'd — a single bash call that lists 30+ files produces 30+Unauthorized downloadlog lines.inheritedflag, any sandboxed code path that mutates a skill file (pip writing pyc near a .py, accidental edits) flows through codeapi's modified-input branch, gets a fresh user-ownedfile_id, uploads as a "generated" output, and shows up as a chip.Fix
1. Honor codeapi's runtime
inheritedflag (3 loops)Skips files where
file.inherited === truein:api/server/controllers/tools.js(sync tool invocation)api/server/controllers/agents/callbacks.js—createToolEndCallback(streaming)api/server/controllers/agents/callbacks.js—createResponsesToolEndCallback(Responses API)Inherited files remain available to subsequent calls via
primeInvokedSkills/ session inheritance — they just don't get redundantly downloaded.2. Mark skill files
read_onlyat upload timeThe
inheritedskip only handles unchanged inputs. To prevent modified skill files from surfacing as ghosts, this PR also wires the upload-timeread_onlycontract added in the codeapi PR:api/server/services/Files/Code/crud.js—batchUploadCodeEnvFiles({ ..., read_only })forwards the flag as a multipart form field. Defaultfalsepreserves existing behavior for user-attached files.packages/api/src/agents/skillFiles.ts— type signature gainsread_only?: boolean;primeSkillFilespassestrue.packages/api/src/agents/skillFiles.spec.ts— asserts the upload call carriesread_only: true.Codeapi seals these inputs with
chmod 444in the sandbox and the walker echoes the original refs asinherited: trueregardless ofwasModified— modifications are dropped on the floor.The flag is intentionally not skill-specific. Any future infrastructure-input flow (system fixtures, cached datasets, etc.) can opt in the same way.
Test plan
node --checksyntax pass for both controllers.npx jest src/agents/skillFiles.spec.ts— 4/4 pass; new assertion confirmsread_only: trueis forwarded.Unauthorized downloaderrors.echo modified > /mnt/data/pptx/SKILL.md) — original ref still echoed, no new chip appears.