From 397d8eae21f3c76ac7f12be666f8464bcc303c30 Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Tue, 14 Apr 2026 16:43:14 -0400 Subject: [PATCH] fix: keep sandbox reply fallback narrow --- .../reply/reply-media-paths.test.ts | 28 +++++++++++++++++++ src/auto-reply/reply/reply-media-paths.ts | 12 ++++---- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/src/auto-reply/reply/reply-media-paths.test.ts b/src/auto-reply/reply/reply-media-paths.test.ts index 6fe9fca32fd..05b0cbb5621 100644 --- a/src/auto-reply/reply/reply-media-paths.test.ts +++ b/src/auto-reply/reply/reply-media-paths.test.ts @@ -94,6 +94,34 @@ describe("createReplyMediaPathNormalizer", () => { ); }); + it("drops sandbox-mapped media when staging fails instead of retrying the workspace fallback", async () => { + ensureSandboxWorkspaceForSession.mockResolvedValue({ + workspaceDir: "/tmp/sandboxes/session-1", + containerWorkdir: "/workspace", + }); + resolveOutboundAttachmentFromUrl.mockRejectedValueOnce(new Error("media too large")); + const normalize = createReplyMediaPathNormalizer({ + cfg: {}, + sessionKey: "session-key", + workspaceDir: "/tmp/agent-workspace", + }); + + const result = await normalize({ + mediaUrls: ["./out/photo.png"], + }); + + expect(result).toMatchObject({ + mediaUrl: undefined, + mediaUrls: undefined, + }); + expect(resolveOutboundAttachmentFromUrl).toHaveBeenCalledTimes(1); + expect(resolveOutboundAttachmentFromUrl).toHaveBeenCalledWith( + path.join("/tmp/sandboxes/session-1", "out", "photo.png"), + 5 * 1024 * 1024, + expect.any(Object), + ); + }); + it("drops host file URLs when no sandbox mapping applies", async () => { const normalize = createReplyMediaPathNormalizer({ cfg: {}, diff --git a/src/auto-reply/reply/reply-media-paths.ts b/src/auto-reply/reply/reply-media-paths.ts index f533814695f..43de02ac6a6 100644 --- a/src/auto-reply/reply/reply-media-paths.ts +++ b/src/auto-reply/reply/reply-media-paths.ts @@ -180,13 +180,12 @@ export function createReplyMediaPathNormalizer(params: { !WINDOWS_DRIVE_RE.test(media); const sandboxRoot = await resolveSandboxRoot(); if (sandboxRoot) { + let sandboxResolvedMedia: string; try { - return await persistLocalReplyMedia( - await resolveSandboxedMediaSource({ - media, - sandboxRoot, - }), - ); + sandboxResolvedMedia = await resolveSandboxedMediaSource({ + media, + sandboxRoot, + }); } catch (err) { if (!isLikelyLocalMediaSource(media)) { throw err; @@ -202,6 +201,7 @@ export function createReplyMediaPathNormalizer(params: { } return await persistLocalReplyMedia(media); } + return await persistLocalReplyMedia(sandboxResolvedMedia); } if (isRelativeLocalMedia) { return await persistLocalReplyMedia(resolveWorkspaceRelativeMedia(media));