Skip to content

🛂 fix: Skip Inherited / Mark Skill Files Read-Only in Code-Env Pipeline#12866

Merged
danny-avila merged 2 commits into
devfrom
fix/skip-inherited-code-files
Apr 28, 2026
Merged

🛂 fix: Skip Inherited / Mark Skill Files Read-Only in Code-Env Pipeline#12866
danny-avila merged 2 commits into
devfrom
fix/skip-inherited-code-files

Conversation

@danny-avila

@danny-avila danny-avila commented Apr 28, 2026

Copy link
Copy Markdown
Owner

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 processCodeOutput on each:

  1. 403 storms. Skill files are uploaded under the skill's entity_id, but processCodeOutput downloads with the user's session key. Every skill file 403'd — a single bash call that lists 30+ files produces 30+ Unauthorized download log lines.
  2. Phantom chips in the UI. Inputs the agent merely referenced show up as if they were generated outputs (~18 ghost chips from a single skill-priming run, see linked issue).
  3. Wasted round-trips. Even when no auth boundary is crossed, the file is already persisted at its origin — re-downloading achieves nothing.
  4. Modifications surface as ghosts. Even after gating on the runtime inherited flag, 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-owned file_id, uploads as a "generated" output, and shows up as a chip.

Fix

1. Honor codeapi's runtime inherited flag (3 loops)

Skips files where file.inherited === true in:

  • api/server/controllers/tools.js (sync tool invocation)
  • api/server/controllers/agents/callbacks.jscreateToolEndCallback (streaming)
  • api/server/controllers/agents/callbacks.jscreateResponsesToolEndCallback (Responses API)
for (const file of artifact.files) {
  if (file.inherited) {
    continue;
  }
  // ...processCodeOutput
}

Inherited files remain available to subsequent calls via primeInvokedSkills / session inheritance — they just don't get redundantly downloaded.

2. Mark skill files read_only at upload time

The inherited skip only handles unchanged inputs. To prevent modified skill files from surfacing as ghosts, this PR also wires the upload-time read_only contract added in the codeapi PR:

  • api/server/services/Files/Code/crud.jsbatchUploadCodeEnvFiles({ ..., read_only }) forwards the flag as a multipart form field. Default false preserves existing behavior for user-attached files.
  • packages/api/src/agents/skillFiles.ts — type signature gains read_only?: boolean; primeSkillFiles passes true.
  • packages/api/src/agents/skillFiles.spec.ts — asserts the upload call carries read_only: true.

Codeapi seals these inputs with chmod 444 in the sandbox and the walker echoes the original refs as inherited: true regardless of wasModified — 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 --check syntax pass for both controllers.
  • Lint-staged pre-commit hooks pass (eslint clean).
  • npx jest src/agents/skillFiles.spec.ts — 4/4 pass; new assertion confirms read_only: true is forwarded.
  • Local: invoke a skill that uploads files (e.g. pptx). After the agent's first bash call lists the skill files, log file should be free of Unauthorized download errors.
  • Local: UI shows no phantom chips for skill files.
  • Local: subsequent bash calls in the same conversation still see all skill files.
  • Local: agent edits a skill file via bash (e.g. echo modified > /mnt/data/pptx/SKILL.md) — original ref still echoed, no new chip appears.
  • Local: user-attached input edited by agent — modified output still surfaces correctly (regression guard).

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.
Copilot AI review requested due to automatic review settings April 28, 2026 20:48

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.files entries flagged as inherited in the synchronous tool invocation controller.
  • Skip output.artifact.files entries flagged as inherited in 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;
@github-actions

Copy link
Copy Markdown
Contributor

GitNexus: 🚀 deployed

The LibreChat-pr-12866 index is now live on the MCP server.
Deploy run

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.
@danny-avila danny-avila changed the title 🛂 fix: Skip Re-Download of Inherited Code-Env Files 🛂 fix: Skip Inherited / Mark Skill Files Read-Only in Code-Env Pipeline Apr 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

GitNexus: 🚀 deployed

The LibreChat-pr-12866 index is now live on the MCP server.
Deploy run

@danny-avila danny-avila merged commit 46a86d8 into dev Apr 28, 2026
11 checks passed
@danny-avila danny-avila deleted the fix/skip-inherited-code-files branch April 28, 2026 23:26
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants