mirror of
https://github.com/anthropics/claude-code-action.git
synced 2026-01-22 22:44:13 +08:00
feat: add sticky_comment input to reduce GitHub comment spam (#211)
* feat: no claude spam * feat: add silent property * feat: add silent property * feat: add silent property * chore: call me a sticky comment * chore: applying review comments * chore: apply review comments * format * reword --------- Co-authored-by: Ashwin Bhat <ashwin@anthropic.com>
This commit is contained in:
committed by
GitHub
parent
bcb072b63f
commit
79f2086fce
@@ -85,6 +85,7 @@ jobs:
|
|||||||
| `base_branch` | The base branch to use for creating new branches (e.g., 'main', 'develop') | No | - |
|
| `base_branch` | The base branch to use for creating new branches (e.g., 'main', 'develop') | No | - |
|
||||||
| `max_turns` | Maximum number of conversation turns Claude can take (limits back-and-forth exchanges) | No | - |
|
| `max_turns` | Maximum number of conversation turns Claude can take (limits back-and-forth exchanges) | No | - |
|
||||||
| `timeout_minutes` | Timeout in minutes for execution | No | `30` |
|
| `timeout_minutes` | Timeout in minutes for execution | No | `30` |
|
||||||
|
| `use_sticky_comment` | Use just one comment to deliver PR comments (only applies for pull_request event workflows) | No | `false` |
|
||||||
| `github_token` | GitHub token for Claude to operate with. **Only include this if you're connecting a custom GitHub app of your own!** | No | - |
|
| `github_token` | GitHub token for Claude to operate with. **Only include this if you're connecting a custom GitHub app of your own!** | No | - |
|
||||||
| `model` | Model to use (provider-specific format required for Bedrock/Vertex) | No | - |
|
| `model` | Model to use (provider-specific format required for Bedrock/Vertex) | No | - |
|
||||||
| `anthropic_model` | **DEPRECATED**: Use `model` instead. Kept for backward compatibility. | No | - |
|
| `anthropic_model` | **DEPRECATED**: Use `model` instead. Kept for backward compatibility. | No | - |
|
||||||
|
|||||||
@@ -78,6 +78,10 @@ inputs:
|
|||||||
description: "Timeout in minutes for execution"
|
description: "Timeout in minutes for execution"
|
||||||
required: false
|
required: false
|
||||||
default: "30"
|
default: "30"
|
||||||
|
use_sticky_comment:
|
||||||
|
description: "Use just one comment to deliver issue/PR comments"
|
||||||
|
required: false
|
||||||
|
default: "false"
|
||||||
|
|
||||||
outputs:
|
outputs:
|
||||||
execution_file:
|
execution_file:
|
||||||
@@ -116,6 +120,7 @@ runs:
|
|||||||
MCP_CONFIG: ${{ inputs.mcp_config }}
|
MCP_CONFIG: ${{ inputs.mcp_config }}
|
||||||
OVERRIDE_GITHUB_TOKEN: ${{ inputs.github_token }}
|
OVERRIDE_GITHUB_TOKEN: ${{ inputs.github_token }}
|
||||||
GITHUB_RUN_ID: ${{ github.run_id }}
|
GITHUB_RUN_ID: ${{ github.run_id }}
|
||||||
|
USE_STICKY_COMMENT: ${{ inputs.use_sticky_comment }}
|
||||||
|
|
||||||
- name: Run Claude Code
|
- name: Run Claude Code
|
||||||
id: claude-code
|
id: claude-code
|
||||||
@@ -180,6 +185,7 @@ runs:
|
|||||||
TRIGGER_USERNAME: ${{ github.event.comment.user.login || github.event.issue.user.login || github.event.pull_request.user.login || github.event.sender.login || github.triggering_actor || github.actor || '' }}
|
TRIGGER_USERNAME: ${{ github.event.comment.user.login || github.event.issue.user.login || github.event.pull_request.user.login || github.event.sender.login || github.triggering_actor || github.actor || '' }}
|
||||||
PREPARE_SUCCESS: ${{ steps.prepare.outcome == 'success' }}
|
PREPARE_SUCCESS: ${{ steps.prepare.outcome == 'success' }}
|
||||||
PREPARE_ERROR: ${{ steps.prepare.outputs.prepare_error || '' }}
|
PREPARE_ERROR: ${{ steps.prepare.outputs.prepare_error || '' }}
|
||||||
|
USE_STICKY_COMMENT: ${{ inputs.use_sticky_comment }}
|
||||||
|
|
||||||
- name: Display Claude Code Report
|
- name: Display Claude Code Report
|
||||||
if: steps.prepare.outputs.contains_trigger == 'true' && steps.claude-code.outputs.execution_file != ''
|
if: steps.prepare.outputs.contains_trigger == 'true' && steps.claude-code.outputs.execution_file != ''
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ export type ParsedGitHubContext = {
|
|||||||
directPrompt: string;
|
directPrompt: string;
|
||||||
baseBranch?: string;
|
baseBranch?: string;
|
||||||
branchPrefix: string;
|
branchPrefix: string;
|
||||||
|
useStickyComment: boolean;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -62,6 +63,7 @@ export function parseGitHubContext(): ParsedGitHubContext {
|
|||||||
directPrompt: process.env.DIRECT_PROMPT ?? "",
|
directPrompt: process.env.DIRECT_PROMPT ?? "",
|
||||||
baseBranch: process.env.BASE_BRANCH,
|
baseBranch: process.env.BASE_BRANCH,
|
||||||
branchPrefix: process.env.BRANCH_PREFIX ?? "claude/",
|
branchPrefix: process.env.BRANCH_PREFIX ?? "claude/",
|
||||||
|
useStickyComment: process.env.STICKY_COMMENT === "true",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { appendFileSync } from "fs";
|
|||||||
import { createJobRunLink, createCommentBody } from "./common";
|
import { createJobRunLink, createCommentBody } from "./common";
|
||||||
import {
|
import {
|
||||||
isPullRequestReviewCommentEvent,
|
isPullRequestReviewCommentEvent,
|
||||||
|
isPullRequestEvent,
|
||||||
type ParsedGitHubContext,
|
type ParsedGitHubContext,
|
||||||
} from "../../context";
|
} from "../../context";
|
||||||
import type { Octokit } from "@octokit/rest";
|
import type { Octokit } from "@octokit/rest";
|
||||||
@@ -25,8 +26,39 @@ export async function createInitialComment(
|
|||||||
try {
|
try {
|
||||||
let response;
|
let response;
|
||||||
|
|
||||||
|
if (
|
||||||
|
context.inputs.useStickyComment &&
|
||||||
|
context.isPR &&
|
||||||
|
!isPullRequestEvent(context)
|
||||||
|
) {
|
||||||
|
const comments = await octokit.rest.issues.listComments({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: context.entityNumber,
|
||||||
|
});
|
||||||
|
const existingComment = comments.data.find(
|
||||||
|
(comment) =>
|
||||||
|
comment.user?.login.indexOf("claude[bot]") !== -1 ||
|
||||||
|
comment.body === initialBody,
|
||||||
|
);
|
||||||
|
if (existingComment) {
|
||||||
|
response = await octokit.rest.issues.updateComment({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
comment_id: existingComment.id,
|
||||||
|
body: initialBody,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Create new comment if no existing one found
|
||||||
|
response = await octokit.rest.issues.createComment({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: context.entityNumber,
|
||||||
|
body: initialBody,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (isPullRequestReviewCommentEvent(context)) {
|
||||||
// Only use createReplyForReviewComment if it's a PR review comment AND we have a comment_id
|
// Only use createReplyForReviewComment if it's a PR review comment AND we have a comment_id
|
||||||
if (isPullRequestReviewCommentEvent(context)) {
|
|
||||||
response = await octokit.rest.pulls.createReplyForReviewComment({
|
response = await octokit.rest.pulls.createReplyForReviewComment({
|
||||||
owner,
|
owner,
|
||||||
repo,
|
repo,
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ const defaultInputs = {
|
|||||||
useVertex: false,
|
useVertex: false,
|
||||||
timeoutMinutes: 30,
|
timeoutMinutes: 30,
|
||||||
branchPrefix: "claude/",
|
branchPrefix: "claude/",
|
||||||
|
useStickyComment: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultRepository = {
|
const defaultRepository = {
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ describe("checkWritePermissions", () => {
|
|||||||
customInstructions: "",
|
customInstructions: "",
|
||||||
directPrompt: "",
|
directPrompt: "",
|
||||||
branchPrefix: "claude/",
|
branchPrefix: "claude/",
|
||||||
|
useStickyComment: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ describe("checkContainsTrigger", () => {
|
|||||||
disallowedTools: [],
|
disallowedTools: [],
|
||||||
customInstructions: "",
|
customInstructions: "",
|
||||||
branchPrefix: "claude/",
|
branchPrefix: "claude/",
|
||||||
|
useStickyComment: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(checkContainsTrigger(context)).toBe(true);
|
expect(checkContainsTrigger(context)).toBe(true);
|
||||||
@@ -64,6 +65,7 @@ describe("checkContainsTrigger", () => {
|
|||||||
disallowedTools: [],
|
disallowedTools: [],
|
||||||
customInstructions: "",
|
customInstructions: "",
|
||||||
branchPrefix: "claude/",
|
branchPrefix: "claude/",
|
||||||
|
useStickyComment: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(checkContainsTrigger(context)).toBe(false);
|
expect(checkContainsTrigger(context)).toBe(false);
|
||||||
@@ -276,6 +278,7 @@ describe("checkContainsTrigger", () => {
|
|||||||
disallowedTools: [],
|
disallowedTools: [],
|
||||||
customInstructions: "",
|
customInstructions: "",
|
||||||
branchPrefix: "claude/",
|
branchPrefix: "claude/",
|
||||||
|
useStickyComment: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(checkContainsTrigger(context)).toBe(true);
|
expect(checkContainsTrigger(context)).toBe(true);
|
||||||
@@ -305,6 +308,7 @@ describe("checkContainsTrigger", () => {
|
|||||||
disallowedTools: [],
|
disallowedTools: [],
|
||||||
customInstructions: "",
|
customInstructions: "",
|
||||||
branchPrefix: "claude/",
|
branchPrefix: "claude/",
|
||||||
|
useStickyComment: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(checkContainsTrigger(context)).toBe(true);
|
expect(checkContainsTrigger(context)).toBe(true);
|
||||||
@@ -334,6 +338,7 @@ describe("checkContainsTrigger", () => {
|
|||||||
disallowedTools: [],
|
disallowedTools: [],
|
||||||
customInstructions: "",
|
customInstructions: "",
|
||||||
branchPrefix: "claude/",
|
branchPrefix: "claude/",
|
||||||
|
useStickyComment: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(checkContainsTrigger(context)).toBe(false);
|
expect(checkContainsTrigger(context)).toBe(false);
|
||||||
|
|||||||
Reference in New Issue
Block a user