diff --git a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts index 1add2822a01e9..b0ee5ca7df00a 100644 --- a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts @@ -830,6 +830,22 @@ describe("ClaudeAdapterV2 session permissions", () => { assert.equal(result.decisionClassification, "user_reject"); assert.equal(result.message, "User declined tool execution."); assert.equal(result.interrupt, undefined); + + // regression: #27854 bare feedback read to Claude as a tool error, not the user's words + const withFeedback = ClaudeAdapterV2.permissionResultFromDecision({ + toolName: "Bash", + decision: "decline", + toolInput: { command: "make" }, + toolUseID: "denied-build", + feedback: " Use the dry-run flag first. ", + }); + assert.equal(withFeedback.behavior, "deny"); + if (withFeedback.behavior !== "deny") return; + assert.equal(withFeedback.decisionClassification, "user_reject"); + assert.equal( + withFeedback.message, + "The user declined this tool call and said:\nUse the dry-run flag first.", + ); }); it("forces suggested permission updates to session scope", () => { diff --git a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts index 7b63b7efc0b65..9f0ff05f4d6a4 100644 --- a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts +++ b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts @@ -3776,13 +3776,14 @@ export function permissionResultFromDecision(input: { }; } + const feedback = input.feedback?.trim(); return { behavior: "deny", - message: - input.feedback?.trim() || - (input.decision === "cancel" + message: feedback + ? `The user declined this tool call and said:\n${feedback}` + : input.decision === "cancel" ? "User cancelled tool execution." - : "User declined tool execution."), + : "User declined tool execution.", toolUseID: input.toolUseID, decisionClassification: "user_reject", ...(input.decision === "cancel" ? { interrupt: true } : {}),