Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion base-action/src/run-claude-sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ async function createPromptConfig(
/**
* Sanitizes SDK output to match CLI sanitization behavior
*/
function sanitizeSdkOutput(
export function sanitizeSdkOutput(
message: SDKMessage,
showFullOutput: boolean,
): string | null {
Expand Down Expand Up @@ -120,6 +120,9 @@ function sanitizeSdkOutput(
num_turns: resultMsg.num_turns,
total_cost_usd: resultMsg.total_cost_usd,
permission_denials: resultMsg.permission_denials,
// Include result field when there's an error so users can see what went wrong
...(resultMsg.is_error &&
"result" in resultMsg && { result: resultMsg.result }),
},
null,
2,
Expand Down
83 changes: 83 additions & 0 deletions test/sanitize-sdk-output.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { describe, it, expect } from "bun:test";
import { sanitizeSdkOutput } from "../base-action/src/run-claude-sdk";
import type { SDKMessage } from "@anthropic-ai/claude-agent-sdk";

describe("sanitizeSdkOutput", () => {
it("should show full output when showFullOutput is true", () => {
const message = {
type: "result",
subtype: "success",
is_error: false,
result: "some sensitive result",
duration_ms: 100,
num_turns: 1,
total_cost_usd: 0.01,
permission_denials: [],
} as unknown as SDKMessage;

const output = sanitizeSdkOutput(message, true);
expect(output).toContain("some sensitive result");
});

it("should not include result field for successful results", () => {
const message = {
type: "result",
subtype: "success",
is_error: false,
result: "successful output that should be hidden",
duration_ms: 100,
num_turns: 1,
total_cost_usd: 0.01,
permission_denials: [],
} as unknown as SDKMessage;

const output = sanitizeSdkOutput(message, false);
const parsed = JSON.parse(output!);
expect(parsed.result).toBeUndefined();
expect(parsed.is_error).toBe(false);
});

it("should include result field when is_error is true", () => {
const message = {
type: "result",
subtype: "success",
is_error: true,
result:
'API Error: 404 {"type":"error","error":{"type":"not_found_error","message":"model: claude-sonnet-4-5-20250514"}}',
duration_ms: 230,
num_turns: 1,
total_cost_usd: 0,
permission_denials: [],
} as unknown as SDKMessage;

const output = sanitizeSdkOutput(message, false);
const parsed = JSON.parse(output!);
expect(parsed.is_error).toBe(true);
expect(parsed.result).toContain("API Error: 404");
expect(parsed.result).toContain("not_found_error");
});

it("should suppress non-result non-init messages", () => {
const message = {
type: "assistant",
content: "some assistant content",
} as unknown as SDKMessage;

const output = sanitizeSdkOutput(message, false);
expect(output).toBeNull();
});

it("should show sanitized init messages", () => {
const message = {
type: "system",
subtype: "init",
model: "claude-sonnet-4-5-20250929",
} as unknown as SDKMessage;

const output = sanitizeSdkOutput(message, false);
const parsed = JSON.parse(output!);
expect(parsed.type).toBe("system");
expect(parsed.subtype).toBe("init");
expect(parsed.model).toBe("claude-sonnet-4-5-20250929");
});
});