Skip to content

fix(eve): never persist an unresolved attachment - #4626

Closed
AndrewBarba wants to merge 2 commits into
barba/attachments-researchfrom
barba/attachments-never-unresolved
Closed

AndrewBarba wants to merge 2 commits into
barba/attachments-researchfrom
barba/attachments-never-unresolved

Conversation

@AndrewBarba

@AndrewBarba AndrewBarba commented Oct 10, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

A turn cancelled before its first model call saved its attachment as a raw eve-url: marker. Every later model call then failed with AI_DownloadError, and the session stayed broken until reset (#3419). The cause is that the cancelled-turn epilogue ran before the framework providers existed, so staging found no sandbox and kept the unresolved part. The epilogue now stages inside the framework providers, so the photo survives. Staging also never lets an unstaged file into history. Without a sandbox, each file becomes a note. Data that is not bytes, base64, a data: URL, or a URL also becomes a note, instead of reaching the provider. So does a link whose scheme no channel resolves, such as myapp-file:abc. If staging fails while a cancel settles, the cancel still settles and the attachments become notes.

The same rule closes three related gaps:

This PR also removes the unused eve-attachment: ref codec. Part of the stack from the research plan in #4625.

Closes #3419. Related to #497.

Validation

  • The new turn-step.test.ts regression fails without the epilogue change and passes with it.
  • A new integration case covers an unresolvable custom-scheme link becoming a note.
  • pnpm --filter eve exec vitest run --config vitest.unit.config.ts: 9438 passed, 1 skipped.
  • pnpm --filter eve exec vitest run --config vitest.integration.config.ts src/harness src/execution: 338 passed.
  • pnpm --filter eve run typecheck, pnpm lint, pnpm fmt, pnpm guard:invariants, and pnpm docs:check pass.

Checklist

  • I added tests and documentation where relevant
  • I added a changeset if this touches the published eve package
  • DCO sign-off passes for every commit (git commit --signoff)

@AndrewBarba
AndrewBarba requested a review from a team as a code owner October 10, 2026 16:15
@vercel

vercel Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
eve-docs Ready Ready Preview, v0 Oct 10, 2026 4:57pm UTC
eve-pkg Ready Ready Preview, v0 Oct 10, 2026 4:57pm UTC

@AndrewBarba
AndrewBarba added this pull request to stack #4628 October 10, 2026 16:15
@vercel-security-reviewer

Copy link
Copy Markdown

Security review details

@github-actions

github-actions Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Bundle + Package Summary: apps/fixtures/weather-agent

Key takeaways

  • No notable deltas vs barba/attachments-research (54246c6).

Delta vs barba/attachments-research (54246c6)

Area Metric Baseline Current Delta
Package Packed tarball 7.58 MB 7.58 MB -170 B ✅
Package Unpacked publish size 26.69 MB 26.69 MB -1.7 kB ✅
Package Installed footprint 74.49 MB 74.49 MB -1.7 kB ✅
Package Published files 4115 4113 -2
Package Installed files 8120 8118 -2
Package Installed package instances 34 34 0
Package Distinct installed package names 32 32 0
Package Installed dependency edges 51 51 0
Package Installed optional peer edges 9 9 0
Runtime Unique function payloads 2 2 0
Runtime Total function bytes 20.38 MB 20.38 MB +156 B ⚠️
Runtime Public routes 18 18 0
Changed function payloads vs barba/attachments-research (54246c6) (2)
Function Status Baseline Current Delta Route changes
functions/__server.func changed 10.19 MB 10.19 MB +78 B ⚠️ none
functions/.well-known/workflow/v1/flow.func changed 10.19 MB 10.19 MB +78 B ⚠️ none

eve init install

Metric Baseline Current Delta
Installed footprint 112.79 MB 112.79 MB -1.7 kB ✅
Installed packages 97 97 0
dependencies 4 4 0
devDependencies 2 2 0
Dependency package bytes 44.78 MB 44.77 MB -1.7 kB ✅
devDependency package bytes 5.11 MB 5.11 MB 0 B ➖
Build Metadata
  • Preset: vercel
  • Nitro: nitro@3.0.260903-beta
  • Output directory: apps/fixtures/weather-agent/.vercel/output
  • Build metadata timestamp: 2026-10-10T16:57:59.901Z
  • Route aliases: 18 public, 1 internal (19 total aliases)
  • Vercel routes in config: 21
  • Severity legend: 🔴 dominant/large, 🟠 notable, 🟡 watch, ⚪ small
Package Drill-Down

Package Details

  • Package: eve@0.76.2
  • Package directory: packages/eve
  • Tarball: 7.58 MB (eve-0.76.2.tgz)
  • Unpacked payload: 26.69 MB across 4113 published files
  • Installed footprint: 74.49 MB across 8118 installed files
  • Installed root package: 26.69 MB
  • Installed dependencies: 47.80 MB
  • Installed package instances: 34
  • Distinct installed package names: 32
  • Installed dependency edges: 51
  • Installed optional peer edges: 9
  • Runtime dependencies: 2
  • Peer dependencies: 7 (6 optional)

Installed footprint is measured from an isolated temporary npm install of the packed tarball.
Graph metrics read only package.json files in package directories directly beneath a node_modules boundary, including nested boundaries. Each directory is one package instance; distinct names come from those manifests. Dependency edges count each unique name in dependencies or optionalDependencies per instance; optional peer edges count peerDependencies marked optional.

Heavy installed dependencies

  • eve: 26.69 MB (35.8%)
  • @rolldown/binding-linux-x64-gnu: 19.62 MB (26.3%)
  • ai: 8.14 MB (10.9%)
  • zod: 6.14 MB (8.2%)
  • undici: 3.56 MB (4.8%)
Publish payload breakdown
Published file size
🟠 dist/src/compiled/shadcn-registry/index.js       [#####...................] 3.85 MB 14.4%
🟠 dist/src/compiled/@photon-ai/chat-adapter-ime... [###.....................] 2.29 MB 8.6%
🟠 dist/src/compiled/@ai-sdk/code-mode/index.js     [#.......................] 1.03 MB 3.8%
🟡 dist/src/compiled/@vercel/blob/index.js          [#.......................] 604.9 kB 2.3%
🟡 dist/src/compiled/_chunks/workflow/undici-D-I... [#.......................] 521.9 kB 2.0%
🔴 Other published files                            [########################] 18.40 MB 68.9%
Installed footprint breakdown
Installed package size
🔴 eve                             [########################] 26.69 MB 35.8%
🔴 @rolldown/binding-linux-x64-gnu [##################......] 19.62 MB 26.3%
🔴 ai                              [#######.................] 8.14 MB 10.9%
🔴 zod                             [######..................] 6.14 MB 8.2%
🟠 undici                          [###.....................] 3.56 MB 4.8%
🟠 nitro                           [##......................] 1.89 MB 2.5%
🔴 Other installed packages        [########................] 8.44 MB 11.3%
Runtime dependencies (2)
Package Range Notes
nitro 3.0.260903-beta
undici 8.10.2
Peer dependencies (7)
Package Range Notes
@opentelemetry/api ^1.0.0 optional peer
ai catalog:
braintrust ^3.0.0 optional peer
chat ^4.41.0 optional peer
dd-trace ^6.13.0 optional peer
just-bash ^3.1.0 optional peer
microsandbox ^0.5.0 optional peer
eve init install drill-down

eve init install details

  • Command: eve init my-agent
  • Package manager: npm
  • Installed footprint: 112.79 MB across 10013 installed files
  • Installed packages: 97 total (91 transitive-only)
  • dependencies: 4 direct packages totaling 44.77 MB
  • devDependencies: 2 direct packages totaling 5.11 MB
  • Other transitive package files: 62.90 MB

Installed footprint is measured from an isolated temporary eve init my-agent using the current packed eve tarball.

Heavy installed dependencies

  • @typescript/typescript-linux-x64: 27.95 MB (24.8%)
  • eve: 26.69 MB (23.7%)
  • @rolldown/binding-linux-x64-gnu: 19.62 MB (17.4%)
  • zod: 9.76 MB (8.6%)
  • ai: 8.14 MB (7.2%)
Installed footprint breakdown
Installed package size
🔴 @typescript/typescript-linux-x64 [########################] 27.95 MB 24.8%
🔴 eve                              [#######################.] 26.69 MB 23.7%
🔴 @rolldown/binding-linux-x64-gnu  [#################.......] 19.62 MB 17.4%
🔴 zod                              [########................] 9.76 MB 8.6%
🔴 ai                               [#######.................] 8.14 MB 7.2%
🟠 undici                           [###.....................] 3.56 MB 3.2%
🔴 Other installed packages         [###############.........] 17.07 MB 15.1%
dependencies (4)
Package Range Installed size Share
@vercel/connect 2.2.0 188.7 kB 0.2%
ai ^7.0.128 8.14 MB 7.2%
eve file:eve-0.76.2.tgz 26.69 MB 23.7%
zod 4.5.4 9.76 MB 8.6%
devDependencies (2)
Package Range Installed size Share
@types/node 24.x 2.61 MB 2.3%
typescript 7.0.2 2.50 MB 2.2%
Function Drill-Down

Payload Size Graph

Unique function payload size and share of total
🔴 functions/.well-known/workflow/v1/flow.func     [########################] 10.19 MB 50.0%
🔴 functions/__server.func                         [########################] 10.19 MB 50.0%

Top Function Payloads

🟠 functions/.well-known/workflow/v1/flow.func • 1 public route • 10.19 MB
Metric Value
Public routes /.well-known/workflow/v1/flow
Runtime nodejs24.x
Handler index.mjs
Payload 10.19 MB
Function files 10.19 MB across 116 files
Traced dependencies 0 B
Signal 🟠 Bundled file _chunks/vercel.web.mjs is 2.25 MB (22.1%)

🟠 🔎 Dependency Analysis

📦 Bundled files:

Bundled file size
🟠 _chunks/vercel.web.mjs                         [############............] 2.25 MB 22.1%
🟠 _libs/undici.mjs                               [#####...................] 1.01 MB 9.9%
🟡 _chunks/sandbox3.mjs                           [####....................] 815.4 kB 8.0%
🟡 _chunks/compiled-artifacts-instrumentation.mjs [####....................] 757.3 kB 7.4%
🟡 _chunks/token-util-Bla5Z0G9.mjs                [####....................] 704.7 kB 6.9%
🔴 Other bundled files                            [########################] 4.66 MB 45.7%

🧾 Vercel Config

{
  "handler": "index.mjs",
  "launcherType": "Nodejs",
  "shouldAddHelpers": false,
  "supportsResponseStreaming": true,
  "runtime": "nodejs24.x",
  "maxDuration": "max",
  "experimentalTriggers": [
    {
      "type": "queue/v2beta",
      "topic": "__eve776561746865722d6167656e74_wkf_workflow_*",
      "consumer": "default",
      "retryAfterSeconds": 5,
      "initialDelaySeconds": 0
    }
  ],
  "environment": {
    "WORKFLOW_PRECONDITION_GUARD": "1"
  }
}

🟠 functions/__server.func • 17 public routes, 1 internal alias • 10.19 MB
Metric Value
Public routes /
/.well-known/workflow/v1/webhook/[token]
/eve/v1/callback/[token]
/eve/v1/connections/[name]/callback/[attemptId]/[token]
/eve/v1/connections/[name]/callback/[token]
/eve/v1/health
/eve/v1/info
/eve/v1/session
/eve/v1/session/[parentSessionId]/subagents/[callId]/[childSessionId]/stream
/eve/v1/session/[sessionId]
/eve/v1/session/[sessionId]/cancel
/eve/v1/session/[sessionId]/clear
/eve/v1/session/[sessionId]/compact
/eve/v1/session/[sessionId]/reset
/eve/v1/session/[sessionId]/stream
/eve/v1/session/[sessionId]/stubs
/eve/v1/task-input/[token]
Internal aliases /__server
Runtime nodejs24.x
Handler index.mjs
Payload 10.19 MB
Function files 10.19 MB across 116 files
Traced dependencies 0 B
Signal 🟠 Bundled file _chunks/vercel.web.mjs is 2.25 MB (22.1%)

🟠 🔎 Dependency Analysis

📦 Bundled files:

Bundled file size
🟠 _chunks/vercel.web.mjs                         [############............] 2.25 MB 22.1%
🟠 _libs/undici.mjs                               [#####...................] 1.01 MB 9.9%
🟡 _chunks/sandbox3.mjs                           [####....................] 815.4 kB 8.0%
🟡 _chunks/compiled-artifacts-instrumentation.mjs [####....................] 757.3 kB 7.4%
🟡 _chunks/token-util-Bla5Z0G9.mjs                [####....................] 704.7 kB 6.9%
🔴 Other bundled files                            [########################] 4.66 MB 45.7%

🧾 Vercel Config

{
  "handler": "index.mjs",
  "launcherType": "Nodejs",
  "shouldAddHelpers": false,
  "supportsResponseStreaming": true,
  "runtime": "nodejs24.x"
}

Build Timing: e2e/fixtures/agent-tools-sandbox

This is an informational timing measurement inside eve build, from preflight through publication. Output-size measurement and profile writing are excluded.

Build mode: deployable Vercel build with sandbox template prewarm included.

  • Build pipeline: 3.59 s -> 3.61 s (+18.8 ms) vs barba/attachments-research (54246c6).
  • Timing is informational: shared GitHub runners are too variable for a hard timing budget.
Detailed phase timings vs `barba/attachments-research (54246c6)`
Phase Baseline Current Delta
extension.check 0.5 ms 16.6 ms +16.1 ms
project.resolve 0.4 ms 0.4 ms 0.0 ms
workspace.create 0.6 ms 0.6 ms 0.0 ms
host.prepare 817.9 ms 793.2 ms -24.7 ms
vercel.service-prefix.resolve 2.2 ms 2.1 ms -0.1 ms
nitro.create 675.7 ms 650.8 ms -24.9 ms
sandbox.prewarm 233.3 ms 207.7 ms -25.6 ms
nitro.cache.prepare 0.2 ms 0.3 ms +0.1 ms
nitro.prepare 0.8 ms 0.8 ms 0.0 ms
nitro.public-assets 0.8 ms 0.8 ms 0.0 ms
nitro.prerender 0.6 ms 0.6 ms 0.0 ms
nitro.bundle 1.71 s 1.80 s +83.9 ms
nitro.cache.write 0.4 ms 0.4 ms 0.0 ms
vercel.workflow-function.materialize 54.8 ms 55.5 ms +0.7 ms
agent-summary.emit 0.6 ms 0.7 ms +0.1 ms
connect-manifest.emit 0.3 ms 0.3 ms 0.0 ms
nitro.close 0.2 ms 0.1 ms -0.1 ms
output.publish 3.5 ms 3.5 ms 0.0 ms
workspace.remove 2.8 ms 2.9 ms +0.1 ms

return decodeDataUrl(data);
}
if (SCHEME_PREFIX.test(data)) {
if (hasInternalRefScheme(data)) return UNREADABLE;

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.

hasInternalRefScheme uses a case-sensitive startsWith, but URL.parse lowercases the scheme. So EVE-SANDBOX:?path=/x&size=1&type=image/png gets past this check (and past the same guard in eve-channel/request.ts:484), then parses to a URL with protocol eve-sandbox:. The eve channel has no fetchFile, so staging persists that URL (attachment-staging.ts:479-480). On the next model call, hydration treats it as a real sandbox ref (isSandboxRefFilePart -> decodeSandboxRef -> readSandboxRefBytes), so the path the caller supplied gets read. A malformed one throws inside hydrateMessageContent instead. Before this PR, such a string was decoded as base64 and never turned into a URL.

Suggest checking the protocol after parsing (for example url.protocol === SANDBOX_URL_SCHEME || url.protocol === "eve-url:" -> unreadable), or making hasInternalRefScheme case-insensitive so the HTTP boundary rejects these too.

}
return stageResolvedBytes(part, { bytes }, sandbox);
if (resolved === null) {
return { ...part, data: data.url };

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.

This still persists an unresolved attachment, which is what the PR is meant to stop. Now that readFileData classifies any scheme as a link, a myapp-file:abc string with no fetchFile (or a fetchFile that returns null for it, the usual "not mine" answer in the Slack, Teams, and Telegram resolvers) lands in history as URL("myapp-file:abc"). Providers can't download that, so every later model call fails, which is the #3419 failure mode again. The new test only covers a resolver that succeeds.

Keep the pass-through for http:/https: only, which is what the comment at line 60 already says, and return attachmentNote(part, "could not be retrieved.") for any other scheme.

const scoped = await withContextScope(ctx, session, async (current) => ({
result: undefined,
session: await preserveCancelledTurnMessage(current, preserved),
}));

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.

Cancellation used to be side-effect free. Now it can start a sandbox, and nothing here catches a failure. If ensureSandboxAccess or writeSandboxRef's sandbox.writeBinaryFile rejects, the error escapes cancelledWithoutCheckpoint, so the step throws instead of returning action: "cancelled". withContextScope only rolls back providers that define rollback, and sandboxProvider doesn't define one (context/providers/sandbox.ts:10-43). That means a failure after the sandbox starts also skips commit, and the started sandbox's state never reaches the session.

Suggest catching here and falling back to the no-sandbox path (preserveCancelledTurnMessage under plain contextStorage.run, which now turns each file into a note), so a cancel always resolves as a cancel.

@vercel vercel Bot 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.

Additional Suggestion:

Case-sensitive hasInternalRefScheme lets a caller-supplied EVE-SANDBOX: URL bypass the channel boundary guard, enabling arbitrary sandbox file reads / path traversal.

Fix on Vercel

Comment thread packages/eve/src/harness/attachment-staging.ts Outdated
Comment thread packages/eve/src/execution/cancelled-model-call-batch.ts Outdated
Signed-off-by: Andrew Barba <barba@hey.com>
…taging fails

Signed-off-by: Andrew Barba <barba@hey.com>
@AndrewBarba

Copy link
Copy Markdown
Collaborator Author

Collapsed into #4635.

This branch was successfully deployed

2 active deployments
Preview – eve-pkg — 6f4a0556 Deployed Oct 10, 2026 by vercel[bot]
Preview – eve-docs — 6f4a0556 Deployed Oct 10, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant