From ae922c96f97d7dd5b12ed5a3d8ba32373e117964 Mon Sep 17 00:00:00 2001 From: jesse-merhi <79823012+jesse-merhi@users.noreply.github.com> Date: Wed, 6 May 2026 10:49:37 +1000 Subject: [PATCH] fix: redact hook-blocked CLI raw traces --- .../agent-runner.misc.runreplyagent.test.ts | 87 +++++++++++++++++++ src/auto-reply/reply/agent-runner.ts | 21 +++-- 2 files changed, 99 insertions(+), 9 deletions(-) diff --git a/src/auto-reply/reply/agent-runner.misc.runreplyagent.test.ts b/src/auto-reply/reply/agent-runner.misc.runreplyagent.test.ts index c1d0ba7cfd6..d8028a87dc2 100644 --- a/src/auto-reply/reply/agent-runner.misc.runreplyagent.test.ts +++ b/src/auto-reply/reply/agent-runner.misc.runreplyagent.test.ts @@ -1690,6 +1690,93 @@ describe("runReplyAgent claude-cli routing", () => { expect(result).toMatchObject({ text: "ok" }); }); + it("does not leak hook-blocked CLI input in raw trace payloads", async () => { + runCliAgentMock.mockResolvedValueOnce({ + payloads: [{ text: "The agent cannot read this message.", isError: true }], + meta: { + error: { kind: "hook_block", message: "The agent cannot read this message." }, + agentMeta: { + provider: "claude-cli", + model: "opus-4.5", + }, + }, + }); + + const typing = createMockTypingController(); + const sessionCtx = { + Provider: "webchat", + OriginatingTo: "session:1", + AccountId: "primary", + MessageSid: "msg", + CommandBody: "secret hitl prompt", + RawBody: "secret hitl prompt", + BodyForAgent: "secret hitl prompt", + Body: "secret hitl prompt", + } as unknown as TemplateContext; + const resolvedQueue = { mode: "interrupt" } as unknown as QueueSettings; + const sessionEntry = { + sessionId: "session", + updatedAt: Date.now(), + traceLevel: "raw", + } as SessionEntry; + const followupRun = { + prompt: "secret hitl prompt", + summaryLine: "secret hitl prompt", + enqueuedAt: Date.now(), + run: { + agentId: "main", + sessionId: "session", + sessionKey: "main", + messageProvider: "webchat", + sessionFile: "/tmp/session.jsonl", + workspaceDir: "/tmp", + config: createCliBackendTestConfig(), + skillsSnapshot: {}, + traceAuthorized: true, + provider: "claude-cli", + model: "opus-4.5", + thinkLevel: "low", + verboseLevel: "off", + elevatedLevel: "off", + bashElevated: { + enabled: false, + allowed: false, + defaultLevel: "off", + }, + timeoutMs: 1_000, + blockReplyBreak: "message_end", + }, + } as unknown as FollowupRun; + + const result = await runReplyAgent({ + commandBody: "secret hitl prompt", + followupRun, + queueKey: "main", + resolvedQueue, + shouldSteer: false, + shouldFollowup: false, + isActive: false, + isStreaming: false, + typing, + sessionCtx, + sessionEntry, + sessionStore: { main: sessionEntry }, + defaultModel: "claude-cli/opus-4.5", + resolvedVerboseLevel: "off", + isNewSession: false, + blockStreamingEnabled: false, + resolvedBlockStreamingBreak: "message_end", + shouldInjectGroupIntro: false, + typingMode: "instant", + }); + + const texts = Array.isArray(result) + ? result.map((payload) => payload.text ?? "").join("\n") + : (result?.text ?? ""); + expect(texts).toContain("The agent cannot read this message."); + expect(texts).not.toContain("secret hitl prompt"); + }); + it("uses the selected CLI runtime for canonical Anthropic models", async () => { runCliAgentMock.mockResolvedValueOnce({ payloads: [{ text: "ok" }], diff --git a/src/auto-reply/reply/agent-runner.ts b/src/auto-reply/reply/agent-runner.ts index a176f7d6390..f2c70d2867d 100644 --- a/src/auto-reply/reply/agent-runner.ts +++ b/src/auto-reply/reply/agent-runner.ts @@ -1708,14 +1708,17 @@ export async function runReplyAgent(params: { } } const prefixPayloads = [...verboseNotices]; - const rawUserText = - runResult.meta?.finalPromptText ?? - sessionCtx.CommandBody ?? - sessionCtx.RawBody ?? - sessionCtx.BodyForAgent ?? - sessionCtx.Body; - const rawAssistantText = - runResult.meta?.finalAssistantRawText ?? runResult.meta?.finalAssistantVisibleText; + const isHookBlockedRun = runResult.meta?.error?.kind === "hook_block"; + const rawUserText = isHookBlockedRun + ? runResult.meta?.finalPromptText + : (runResult.meta?.finalPromptText ?? + sessionCtx.CommandBody ?? + sessionCtx.RawBody ?? + sessionCtx.BodyForAgent ?? + sessionCtx.Body); + const rawAssistantText = isHookBlockedRun + ? undefined + : (runResult.meta?.finalAssistantRawText ?? runResult.meta?.finalAssistantVisibleText); const traceAuthorized = followupRun.run.traceAuthorized === true; const executionTrace = mergeExecutionTrace({ fallbackAttempts, @@ -1847,7 +1850,7 @@ export async function runReplyAgent(params: { if (responseUsageLine) { finalPayloads = appendUsageLine(finalPayloads, responseUsageLine); } - if (runResult.meta?.error?.kind === "hook_block") { + if (isHookBlockedRun) { finalPayloads = markBeforeAgentRunBlockedPayloads(finalPayloads); }