diff --git a/action.yml b/action.yml index 63f37ae4f..622a26892 100644 --- a/action.yml +++ b/action.yml @@ -31,6 +31,10 @@ inputs: description: "Comma-separated list of usernames to allow without write permissions, or '*' to allow all users. Only works when github_token input is provided. WARNING: Use with extreme caution - this bypasses security checks and should only be used for workflows with very limited permissions (e.g., issue labeling)." required: false default: "" + excluded_comment_users: + description: "Comma-separated list of usernames whose comments should be excluded from the prompt context. Useful for filtering out bot comments or previous Claude responses." + required: false + default: "" # Claude Code configuration prompt: @@ -165,6 +169,7 @@ runs: OVERRIDE_GITHUB_TOKEN: ${{ inputs.github_token }} ALLOWED_BOTS: ${{ inputs.allowed_bots }} ALLOWED_NON_WRITE_USERS: ${{ inputs.allowed_non_write_users }} + EXCLUDED_COMMENT_USERS: ${{ inputs.excluded_comment_users }} GITHUB_RUN_ID: ${{ github.run_id }} USE_STICKY_COMMENT: ${{ inputs.use_sticky_comment }} DEFAULT_WORKFLOW_TOKEN: ${{ github.token }} diff --git a/docs/usage.md b/docs/usage.md index 818b0c8ab..4b3e13d77 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -48,6 +48,8 @@ jobs: # actions: read # Optional: allow bot users to trigger the action # allowed_bots: "dependabot[bot],renovate[bot]" + # Optional: exclude specific users' comments from context (e.g., bots) + # excluded_comment_users: "github-actions[bot],claude[bot]" ``` ## Inputs @@ -76,6 +78,7 @@ jobs: | `bot_name` | GitHub username to use for git operations (defaults to Claude's bot name) | No | `claude[bot]` | | `allowed_bots` | Comma-separated list of allowed bot usernames, or '\*' to allow all bots. Empty string (default) allows no bots | No | "" | | `allowed_non_write_users` | **⚠️ RISKY**: Comma-separated list of usernames to allow without write permissions, or '\*' for all users. Only works with `github_token` input. See [Security](./security.md) | No | "" | +| `excluded_comment_users` | Comma-separated list of usernames whose comments should be excluded from the prompt context. Useful for filtering out bot comments or previous Claude responses. Only applies in agent mode. | No | "" | | `path_to_claude_code_executable` | Optional path to a custom Claude Code executable. Skips automatic installation. Useful for Nix, custom containers, or specialized environments | No | "" | | `path_to_bun_executable` | Optional path to a custom Bun executable. Skips automatic Bun installation. Useful for Nix, custom containers, or specialized environments | No | "" | | `plugin_marketplaces` | Newline-separated list of Claude Code plugin marketplace Git URLs to install from (e.g., see example in workflow above). Marketplaces are added before plugin installation | No | "" | diff --git a/src/create-prompt/index.ts b/src/create-prompt/index.ts index f34deda79..9c66b5b42 100644 --- a/src/create-prompt/index.ts +++ b/src/create-prompt/index.ts @@ -484,8 +484,20 @@ export function generateDefaultPrompt( const { eventType, triggerContext } = getEventTypeAndContext(context); + // Parse excluded comment users from context + const excludedUsers = context.githubContext?.inputs?.excludedCommentUsers + ? context.githubContext.inputs.excludedCommentUsers + .split(",") + .map((u) => u.trim().toLowerCase()) + .filter((u) => u.length > 0) + : undefined; + const formattedContext = formatContext(contextData, eventData.isPR); - const formattedComments = formatComments(comments, imageUrlMap); + const formattedComments = formatComments( + comments, + imageUrlMap, + excludedUsers, + ); const formattedReviewComments = eventData.isPR ? formatReviewComments(reviewData, imageUrlMap) : ""; diff --git a/src/entrypoints/collect-inputs.ts b/src/entrypoints/collect-inputs.ts index bfb400808..98912fcc6 100644 --- a/src/entrypoints/collect-inputs.ts +++ b/src/entrypoints/collect-inputs.ts @@ -8,6 +8,7 @@ export function collectActionInputsPresence(): void { base_branch: "", branch_prefix: "claude/", allowed_bots: "", + excluded_comment_users: "", mode: "tag", model: "", anthropic_model: "", diff --git a/src/github/context.ts b/src/github/context.ts index 92f272cb1..98b72a96a 100644 --- a/src/github/context.ts +++ b/src/github/context.ts @@ -94,6 +94,7 @@ type BaseContext = { botName: string; allowedBots: string; allowedNonWriteUsers: string; + excludedCommentUsers: string; trackProgress: boolean; }; }; @@ -149,6 +150,7 @@ export function parseGitHubContext(): GitHubContext { botName: process.env.BOT_NAME ?? CLAUDE_BOT_LOGIN, allowedBots: process.env.ALLOWED_BOTS ?? "", allowedNonWriteUsers: process.env.ALLOWED_NON_WRITE_USERS ?? "", + excludedCommentUsers: process.env.EXCLUDED_COMMENT_USERS ?? "", trackProgress: process.env.TRACK_PROGRESS === "true", }, }; diff --git a/src/github/data/formatter.ts b/src/github/data/formatter.ts index 63c4883a7..a84d608da 100644 --- a/src/github/data/formatter.ts +++ b/src/github/data/formatter.ts @@ -48,9 +48,22 @@ export function formatBody( export function formatComments( comments: GitHubComment[], imageUrlMap?: Map, + excludedCommentUsers?: string[], ): string { return comments - .filter((comment) => !comment.isMinimized) + .filter((comment) => { + // Filter out minimized comments + if (comment.isMinimized) return false; + + // Filter out excluded users if specified + if (excludedCommentUsers && excludedCommentUsers.length > 0) { + return !excludedCommentUsers.includes( + comment.author.login.toLowerCase(), + ); + } + + return true; + }) .map((comment) => { let body = comment.body; diff --git a/test/data-formatter.test.ts b/test/data-formatter.test.ts index 7ac455c47..28285ffaa 100644 --- a/test/data-formatter.test.ts +++ b/test/data-formatter.test.ts @@ -309,6 +309,93 @@ describe("formatComments", () => { const result = formatComments(comments); expect(result).toBe(""); }); + + test("filters out comments from excluded users", () => { + const comments: GitHubComment[] = [ + { + id: "1", + databaseId: "100001", + body: "Comment from user1", + author: { login: "user1" }, + createdAt: "2023-01-01T00:00:00Z", + }, + { + id: "2", + databaseId: "100002", + body: "Comment from bot", + author: { login: "github-actions[bot]" }, + createdAt: "2023-01-02T00:00:00Z", + }, + { + id: "3", + databaseId: "100003", + body: "Comment from user2", + author: { login: "user2" }, + createdAt: "2023-01-03T00:00:00Z", + }, + { + id: "4", + databaseId: "100004", + body: "Comment from claude", + author: { login: "claude[bot]" }, + createdAt: "2023-01-04T00:00:00Z", + }, + ]; + + const excludedUsers = ["github-actions[bot]", "claude[bot]"]; + const result = formatComments(comments, undefined, excludedUsers); + expect(result).toBe( + `[user1 at 2023-01-01T00:00:00Z]: Comment from user1\n\n[user2 at 2023-01-03T00:00:00Z]: Comment from user2`, + ); + }); + + test("includes all comments when excluded users array is empty", () => { + const comments: GitHubComment[] = [ + { + id: "1", + databaseId: "100001", + body: "First comment", + author: { login: "user1" }, + createdAt: "2023-01-01T00:00:00Z", + }, + { + id: "2", + databaseId: "100002", + body: "Second comment", + author: { login: "user2" }, + createdAt: "2023-01-02T00:00:00Z", + }, + ]; + + const result = formatComments(comments, undefined, []); + expect(result).toBe( + `[user1 at 2023-01-01T00:00:00Z]: First comment\n\n[user2 at 2023-01-02T00:00:00Z]: Second comment`, + ); + }); + + test("includes all comments when excluded users is undefined", () => { + const comments: GitHubComment[] = [ + { + id: "1", + databaseId: "100001", + body: "First comment", + author: { login: "user1" }, + createdAt: "2023-01-01T00:00:00Z", + }, + { + id: "2", + databaseId: "100002", + body: "Second comment", + author: { login: "user2" }, + createdAt: "2023-01-02T00:00:00Z", + }, + ]; + + const result = formatComments(comments, undefined, []); + expect(result).toBe( + `[user1 at 2023-01-01T00:00:00Z]: First comment\n\n[user2 at 2023-01-02T00:00:00Z]: Second comment`, + ); + }); }); describe("formatReviewComments", () => { diff --git a/test/install-mcp-server.test.ts b/test/install-mcp-server.test.ts index 9d628504d..5198d2bea 100644 --- a/test/install-mcp-server.test.ts +++ b/test/install-mcp-server.test.ts @@ -36,6 +36,7 @@ describe("prepareMcpConfig", () => { botName: CLAUDE_BOT_LOGIN, allowedBots: "", allowedNonWriteUsers: "", + excludedCommentUsers: "", trackProgress: false, }, }; diff --git a/test/mockContext.ts b/test/mockContext.ts index 73255e63a..8cb2f9c72 100644 --- a/test/mockContext.ts +++ b/test/mockContext.ts @@ -24,6 +24,7 @@ const defaultInputs = { botName: CLAUDE_BOT_LOGIN, allowedBots: "", allowedNonWriteUsers: "", + excludedCommentUsers: "", trackProgress: false, }; diff --git a/test/modes/detector.test.ts b/test/modes/detector.test.ts index ed6a3a5da..aef7d85f0 100644 --- a/test/modes/detector.test.ts +++ b/test/modes/detector.test.ts @@ -24,6 +24,7 @@ describe("detectMode with enhanced routing", () => { botName: "claude-bot", allowedBots: "", allowedNonWriteUsers: "", + excludedCommentUsers: "", trackProgress: false, }, }; diff --git a/test/permissions.test.ts b/test/permissions.test.ts index 9aeb3011a..1c96bfe79 100644 --- a/test/permissions.test.ts +++ b/test/permissions.test.ts @@ -72,6 +72,7 @@ describe("checkWritePermissions", () => { botName: CLAUDE_BOT_LOGIN, allowedBots: "", allowedNonWriteUsers: "", + excludedCommentUsers: "", trackProgress: false, }, });