From 0e9bd18b31d6bdb9d05f7f7dc99b0b23c701f221 Mon Sep 17 00:00:00 2001 From: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com> Date: Sun, 3 May 2026 01:35:51 +0000 Subject: [PATCH] fix(gateway): preserve err.stack when chat.send/agent attachment parsing fails Co-authored-by: keen0206 <233564226+keen0206@users.noreply.github.com> --- .../chat.directive-tags.test.ts | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/gateway/server-methods/chat.directive-tags.test.ts b/src/gateway/server-methods/chat.directive-tags.test.ts index 20f4089c8bc..fe26ee538f2 100644 --- a/src/gateway/server-methods/chat.directive-tags.test.ts +++ b/src/gateway/server-methods/chat.directive-tags.test.ts @@ -2827,6 +2827,53 @@ describe("chat directive tag stripping for non-streaming final payloads", () => expect(mockState.deleteMediaBufferCalls).toEqual([{ id: "saved-media", subdir: "inbound" }]); }); + it("logs chat.send attachment parse failures with stack details", async () => { + createTranscriptFixture("openclaw-chat-send-attachment-parse-stack-"); + const respond = vi.fn(); + const context = createChatContext(); + + await runNonStreamingChatSend({ + context, + respond, + idempotencyKey: "idem-chat-send-attachment-parse-stack", + message: "inspect this", + requestParams: { + attachments: [ + { + type: "file", + mimeType: "image/png", + fileName: "broken.png", + content: "not-base64", + }, + ], + }, + expectBroadcast: false, + waitFor: "none", + }); + + expect(mockState.lastDispatchCtx).toBeUndefined(); + expect(respond).toHaveBeenCalledWith( + false, + undefined, + expect.objectContaining({ + code: ErrorCodes.INVALID_REQUEST, + message: expect.stringContaining("attachment broken.png: invalid base64 content"), + }), + ); + expect(context.logGateway.error).toHaveBeenCalledWith( + "chat.send attachment parse/stage failed", + expect.objectContaining({ + consoleMessage: expect.stringContaining( + "chat.send attachment parse/stage failed: Error: attachment broken.png", + ), + error: expect.stringContaining("Error: attachment broken.png: invalid base64 content"), + }), + ); + const logMeta = (context.logGateway.error as unknown as ReturnType).mock + .calls[0]?.[1] as { error?: string } | undefined; + expect(logMeta?.error).toContain("\n at "); + }); + it("surfaces partial non-image staging failures as 5xx UNAVAILABLE", async () => { // Regression: stageSandboxMedia keeps unstaged entries as their original // absolute path, so a simple `stagedPaths.length === nonImage.length`