From 714e0c01e53da4f47fae3468a5890fb07967f444 Mon Sep 17 00:00:00 2001 From: Nick Tabeling Date: Fri, 10 Apr 2026 10:02:17 +0200 Subject: [PATCH] fix(DIS-102): sanitize response before DB storage to break path feedback loop The root cause was twofold: 1. Raw (unsanitized) agent responses were stored in the conversation history DB. On subsequent calls, these paths were fed back to the agent as context, causing it to reproduce full paths regardless of Discord-side sanitization. 2. repoRoot (workspace_path) was not passed to sanitizeForDiscord, so workspace-specific paths were only partially masked via the home directory fallback. Fix: sanitize the full response once before both sending to Discord and storing in the DB. Pass workspace_path as repoRoot for precise path replacement. Add Windows path tests. Co-Authored-By: Claude Sonnet 4.6 --- src/router.ts | 23 ++++++++++++++++++----- tests/unit/sanitize-for-discord.test.ts | 20 ++++++++++++++++++++ 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/router.ts b/src/router.ts index 4a0be8f..cd5057a 100644 --- a/src/router.ts +++ b/src/router.ts @@ -107,30 +107,43 @@ export async function routeMessage( message.author.username ); + // Sanitize the full response once, then use the sanitized version + // for both Discord output and conversation history storage. + // This prevents a feedback loop where raw paths in stored history + // get fed back to the agent as context, causing it to reproduce them. + const sanitizeOpts = { + repoRoot: workspace.workspace_path, + home: os.homedir(), + }; + const sanitizedResponse = sanitizeForDiscord(response, sanitizeOpts); + // Split and send response - const chunks = splitMessage(response); + const chunks = splitMessage(sanitizedResponse); let firstMsgId: string | null = null; for (const chunk of chunks) { - const sent = await channel.send(sanitizeForDiscord(chunk, { home: os.homedir() })); + const sent = await channel.send(chunk); if (!firstMsgId) { firstMsgId = sent.id; } } - // Store assistant response (use first message id for dedup) + // Store sanitized assistant response (use first message id for dedup) if (firstMsgId) { db.addConversation( workspace.id, firstMsgId, "assistant", - response, + sanitizedResponse, null ); } } catch (error) { const msg = error instanceof Error ? error.message : "Unbekannter Fehler"; - const safeMsg = sanitizeForDiscord(`Fehler bei der Verarbeitung: ${msg}`, { home: os.homedir() }); + const safeMsg = sanitizeForDiscord(`Fehler bei der Verarbeitung: ${msg}`, { + repoRoot: workspace.workspace_path, + home: os.homedir(), + }); await channel.send(safeMsg).catch(() => {}); } finally { clearInterval(typingInterval); diff --git a/tests/unit/sanitize-for-discord.test.ts b/tests/unit/sanitize-for-discord.test.ts index 1d704d3..5a75e42 100644 --- a/tests/unit/sanitize-for-discord.test.ts +++ b/tests/unit/sanitize-for-discord.test.ts @@ -64,4 +64,24 @@ describe("sanitizeForDiscord", () => { }); expect(result).toBe("/src/index.ts and ~/other.ts"); }); + + // Windows-style backslash paths are sanitized + it("replaces Windows backslash paths", () => { + const text = "File at C:\\Users\\dev\\.disclaw\\workspaces\\agent-x\\src\\main.ts"; + const result = sanitizeForDiscord(text, { + repoRoot: "C:\\Users\\dev\\.disclaw\\workspaces\\agent-x", + home: "C:\\Users\\dev", + }); + expect(result).toBe("File at \\src\\main.ts"); + }); + + // Mixed slash styles (common on Windows when tools output forward slashes) + it("replaces forward-slash variants of Windows paths", () => { + const text = "File at C:/Users/dev/.disclaw/workspaces/agent-x/src/main.ts"; + const result = sanitizeForDiscord(text, { + repoRoot: "C:\\Users\\dev\\.disclaw\\workspaces\\agent-x", + home: "C:\\Users\\dev", + }); + expect(result).toBe("File at /src/main.ts"); + }); });