Compare commits

..
Author SHA1 Message Date
Ashwin Bhat ece24efbb4 debug: add logging to credential clearing 2026-02-15 15:00:51 -08:00
Ashwin Bhat fea6d7077e fix: clear actions/checkout includeIf credential configs
actions/checkout now stores git credentials in external config files
via includeIf instead of directly in .git/config. Our configureGitAuth
was only unsetting extraheader from local config, missing the included
files. This caused git push to use the stale github-actions[bot] token
instead of the Claude app token.

Now we enumerate all config origins and clear extraheader entries from
any included credential files.
2026-02-15 14:54:33 -08:00
35 changed files with 198 additions and 886 deletions
+14 -15
View File
@@ -1,5 +1,5 @@
---
allowed-tools: Bash(./scripts/gh.sh:*),Bash(./scripts/edit-issue-labels.sh:*)
allowed-tools: Bash(gh label list:*),Bash(gh issue view:*),Bash(gh issue edit:*),Bash(gh search:*)
description: Apply labels to GitHub issues
---
@@ -14,18 +14,17 @@ Issue Information:
TASK OVERVIEW:
1. First, fetch the list of labels available in this repository by running: `./scripts/gh.sh label list`. Run exactly this command with nothing else.
1. First, fetch the list of labels available in this repository by running: `gh label list`. Run exactly this command with nothing else.
2. Next, use gh wrapper commands to get context about the issue:
2. Next, use gh commands to get context about the issue:
- Use `./scripts/gh.sh issue view ${{ github.event.issue.number }}` to retrieve the current issue's details
- Use `./scripts/gh.sh search issues` to find similar issues that might provide context for proper categorization
- `./scripts/gh.sh` is a wrapper for `gh` CLI. Example commands:
- `./scripts/gh.sh label list` — fetch all available labels
- `./scripts/gh.sh issue view 123` view issue details
- `./scripts/gh.sh issue view 123 --comments` — view with comments
- `./scripts/gh.sh search issues "query" --limit 10` search for issues
- `./scripts/edit-issue-labels.sh` — apply labels to the issue
- Use `gh issue view ${{ github.event.issue.number }}` to retrieve the current issue's details
- Use `gh search issues` to find similar issues that might provide context for proper categorization
- You have access to these Bash commands:
- Bash(gh label list:\*) - to get available labels
- Bash(gh issue view:\*) - to view issue details
- Bash(gh issue edit:\*) - to apply labels to the issue
- Bash(gh search:\*) - to search for similar issues
3. Analyze the issue content, considering:
@@ -40,12 +39,12 @@ TASK OVERVIEW:
- Choose labels that accurately reflect the issue's nature
- Be specific but comprehensive
- IMPORTANT: Add a priority label (P1, P2, or P3) based on the label descriptions from ./scripts/gh.sh label list
- IMPORTANT: Add a priority label (P1, P2, or P3) based on the label descriptions from gh label list
- Consider platform labels (android, ios) if applicable
- If you find similar issues using ./scripts/gh.sh search, consider using a "duplicate" label if appropriate. Only do so if the issue is a duplicate of another OPEN issue.
- If you find similar issues using gh search, consider using a "duplicate" label if appropriate. Only do so if the issue is a duplicate of another OPEN issue.
5. Apply the selected labels:
- Use `./scripts/edit-issue-labels.sh --issue NUMBER --add-label LABEL1 --add-label LABEL2` to apply your selected labels
- Use `gh issue edit` to apply your selected labels
- DO NOT post any comments explaining your decision
- DO NOT communicate directly with users
- If no labels are clearly applicable, do not apply any labels
@@ -55,7 +54,7 @@ IMPORTANT GUIDELINES:
- Be thorough in your analysis
- Only select labels from the provided list above
- DO NOT post any comments to the issue
- Your ONLY action should be to apply labels using ./scripts/edit-issue-labels.sh
- Your ONLY action should be to apply labels using gh issue edit
- It's okay to not add any labels if none are clearly applicable
---
+4 -4
View File
@@ -19,9 +19,9 @@ jobs:
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
issues: write
contents: read
pull-requests: read
issues: read
id-token: write
steps:
- name: Checkout repository
@@ -31,7 +31,7 @@ jobs:
- name: Run Claude Code
id: claude
uses: anthropics/claude-code-action@main
uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
claude_args: |
+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
- name: Run Claude Code for Issue Triage
uses: anthropics/claude-code-action@main
with:
prompt: "/label-issue REPO: ${{ github.repository }} ISSUE_NUMBER: ${{ github.event.issue.number }}"
prompt: "/label-issue REPO: ${{ github.repository }} ISSUE_NUMBER${{ github.event.issue.number }}"
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
allowed_non_write_users: "*" # Required for issue triage workflow, if users without repo write access create issues
github_token: ${{ secrets.GITHUB_TOKEN }}
@@ -1,47 +0,0 @@
name: Non-write Users Check
on:
pull_request:
paths:
- ".github/**"
permissions:
contents: read
pull-requests: write
jobs:
allowed-non-write-check:
runs-on: ubuntu-latest
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- run: |
DIFF=$(gh pr diff "$PR_NUMBER" -R "$REPO" || true)
if ! echo "$DIFF" | grep -qE '^diff --git a/\.github/.*\.ya?ml'; then
exit 0
fi
MATCHES=$(echo "$DIFF" | grep "^+.*allowed_non_write_users" || true)
if [ -z "$MATCHES" ]; then
exit 0
fi
EXISTING=$(gh pr view "$PR_NUMBER" -R "$REPO" --json comments --jq '.comments[].body' \
| grep -c "<!-- non-write-users-check -->" || true)
if [ "$EXISTING" -gt 0 ]; then
exit 0
fi
gh pr comment "$PR_NUMBER" -R "$REPO" --body '<!-- non-write-users-check -->
**`allowed_non_write_users` detected**
This PR adds or modifies `allowed_non_write_users`, which allows users without write access to trigger Claude Code Action workflows. This can introduce security risks.
If this is a new flow, please make sure you actually need `allowed_non_write_users`. If you are editing an existing workflow, double check that you are not adding new Claude permissions which might lead to a vulnerability.
See existing workflows in this repo for safe usage examples, or contact the AppSec team.'
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
REPO: ${{ github.repository }}
+2 -34
View File
@@ -28,20 +28,11 @@ inputs:
required: false
default: ""
allowed_bots:
description: "Comma-separated list of allowed bot usernames, or '*' to allow all bots. Empty string (default) allows no bots. WARNING: On public repos with '*', external Apps may be able to invoke this action with prompts they control. See docs/security.md."
description: "Comma-separated list of allowed bot usernames, or '*' to allow all bots. Empty string (default) allows no bots."
required: false
default: ""
allowed_non_write_users:
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).
SECURITY: Processing untrusted content exposes the workflow to prompt injection. When this
input is set, Claude does a best-effort scrub of Anthropic, cloud, and GitHub Actions secrets
from subprocess environments. This reduces but does not eliminate prompt injection risk -
only use for workflows with very limited permissions and validate all outputs.
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: ""
include_comments_by_actor:
@@ -98,10 +89,6 @@ inputs:
description: "Use just one comment to deliver issue/PR comments"
required: false
default: "false"
classify_inline_comments:
description: "Buffer inline comments without confirmed=true and classify them (real review vs test/probe) before posting after the session ends. Set to 'false' to post all inline comments immediately (pre-buffering behavior)."
required: false
default: "true"
use_commit_signing:
description: "Enable commit signing using GitHub's commit signature verification. When false, Claude uses standard git commands"
required: false
@@ -134,10 +121,6 @@ inputs:
description: "Optional path to a custom Bun executable. If provided, skips automatic Bun installation and uses this executable instead. WARNING: Using an incompatible version may cause problems if the action requires specific Bun features. This input is typically not needed unless you're debugging something specific or have unique needs in your environment."
required: false
default: ""
display_report:
description: "Whether to display the Claude Code Report in GitHub Step Summary. Set to 'false' to disable when using custom formatting solutions. WARNING: This outputs Claude-authored content in the GitHub Step Summary. This should only be used in cases where the action is used solely with trusted input."
required: false
default: "false"
show_full_output:
description: "Show full JSON output from Claude Code. WARNING: This outputs ALL Claude messages including tool execution results which may contain secrets, API keys, or other sensitive information. These logs are publicly visible in GitHub Actions. Only enable for debugging in non-sensitive environments."
required: false
@@ -213,12 +196,10 @@ runs:
OVERRIDE_GITHUB_TOKEN: ${{ inputs.github_token }}
ALLOWED_BOTS: ${{ inputs.allowed_bots }}
ALLOWED_NON_WRITE_USERS: ${{ inputs.allowed_non_write_users }}
CLAUDE_CODE_SUBPROCESS_ENV_SCRUB: ${{ env.CLAUDE_CODE_SUBPROCESS_ENV_SCRUB || (inputs.allowed_non_write_users != '' && '1') || '' }}
INCLUDE_COMMENTS_BY_ACTOR: ${{ inputs.include_comments_by_actor }}
EXCLUDE_COMMENTS_BY_ACTOR: ${{ inputs.exclude_comments_by_actor }}
GITHUB_RUN_ID: ${{ github.run_id }}
USE_STICKY_COMMENT: ${{ inputs.use_sticky_comment }}
CLASSIFY_INLINE_COMMENTS: ${{ inputs.classify_inline_comments }}
DEFAULT_WORKFLOW_TOKEN: ${{ github.token }}
USE_COMMIT_SIGNING: ${{ inputs.use_commit_signing }}
SSH_SIGNING_KEY: ${{ inputs.ssh_signing_key }}
@@ -237,7 +218,6 @@ runs:
INPUT_PATH_TO_CLAUDE_CODE_EXECUTABLE: ${{ inputs.path_to_claude_code_executable }}
INPUT_PATH_TO_BUN_EXECUTABLE: ${{ inputs.path_to_bun_executable }}
INPUT_SHOW_FULL_OUTPUT: ${{ inputs.show_full_output }}
DISPLAY_REPORT: ${{ inputs.display_report }}
INPUT_PLUGINS: ${{ inputs.plugins }}
INPUT_PLUGIN_MARKETPLACES: ${{ inputs.plugin_marketplaces }}
PATH_TO_CLAUDE_CODE_EXECUTABLE: ${{ inputs.path_to_claude_code_executable }}
@@ -297,18 +277,6 @@ runs:
run: |
bun run ${GITHUB_ACTION_PATH}/src/entrypoints/cleanup-ssh-signing.ts
- name: Post buffered inline comments
if: always() && inputs.classify_inline_comments != 'false'
shell: bash
env:
GITHUB_TOKEN: ${{ steps.run.outputs.github_token || inputs.github_token || github.token }}
REPO_OWNER: ${{ github.event.repository.owner.login }}
REPO_NAME: ${{ github.event.repository.name }}
PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }}
ANTHROPIC_API_KEY: ${{ inputs.anthropic_api_key }}
run: |
bun run ${GITHUB_ACTION_PATH}/src/entrypoints/post-buffered-inline-comments.ts
- name: Revoke app token
if: always() && inputs.github_token == '' && steps.run.outputs.skipped_due_to_workflow_validation_mismatch != 'true'
shell: bash
+1 -1
View File
@@ -124,7 +124,7 @@ runs:
PATH_TO_CLAUDE_CODE_EXECUTABLE: ${{ inputs.path_to_claude_code_executable }}
run: |
if [ -z "$PATH_TO_CLAUDE_CODE_EXECUTABLE" ]; then
CLAUDE_CODE_VERSION="2.1.83"
CLAUDE_CODE_VERSION="2.1.42"
echo "Installing Claude Code v${CLAUDE_CODE_VERSION}..."
for attempt in 1 2 3; do
echo "Installation attempt $attempt..."
+17 -19
View File
@@ -6,7 +6,7 @@
"name": "@anthropic-ai/claude-code-base-action",
"dependencies": {
"@actions/core": "^1.10.1",
"@anthropic-ai/claude-agent-sdk": "^0.2.83",
"@anthropic-ai/claude-agent-sdk": "^0.2.42",
"shell-quote": "^1.8.3",
},
"devDependencies": {
@@ -27,41 +27,39 @@
"@actions/io": ["@actions/io@1.1.3", "", {}, "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="],
"@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.2.83", "", { "optionalDependencies": { "@img/sharp-darwin-arm64": "^0.34.2", "@img/sharp-darwin-x64": "^0.34.2", "@img/sharp-linux-arm": "^0.34.2", "@img/sharp-linux-arm64": "^0.34.2", "@img/sharp-linux-x64": "^0.34.2", "@img/sharp-linuxmusl-arm64": "^0.34.2", "@img/sharp-linuxmusl-x64": "^0.34.2", "@img/sharp-win32-arm64": "^0.34.2", "@img/sharp-win32-x64": "^0.34.2" }, "peerDependencies": { "zod": "^4.0.0" } }, "sha512-O8g56htGMxrwbjCbqUqRBMNC0O98B7SkPnfQC7vmo3w2DVnUrBj3qat/IBLB8SI4sjVSZHeJrcK7+ozsCzStSw=="],
"@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.2.42", "", { "optionalDependencies": { "@img/sharp-darwin-arm64": "^0.33.5", "@img/sharp-darwin-x64": "^0.33.5", "@img/sharp-linux-arm": "^0.33.5", "@img/sharp-linux-arm64": "^0.33.5", "@img/sharp-linux-x64": "^0.33.5", "@img/sharp-linuxmusl-arm64": "^0.33.5", "@img/sharp-linuxmusl-x64": "^0.33.5", "@img/sharp-win32-x64": "^0.33.5" }, "peerDependencies": { "zod": "^4.0.0" } }, "sha512-/CugP7AjP57Dqtl2sbsDtxdbpQoPKIhjyF5WrTViGu4NHQdM+UikrRs4MhZ2jeotiC5R7iK9ZUN9SiBgcZ8oLw=="],
"@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="],
"@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="],
"@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.0.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ=="],
"@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.4" }, "os": "darwin", "cpu": "x64" }, "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw=="],
"@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.0.4" }, "os": "darwin", "cpu": "x64" }, "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q=="],
"@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g=="],
"@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg=="],
"@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg=="],
"@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.0.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ=="],
"@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A=="],
"@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.0.5", "", { "os": "linux", "cpu": "arm" }, "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g=="],
"@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw=="],
"@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA=="],
"@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw=="],
"@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw=="],
"@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw=="],
"@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA=="],
"@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg=="],
"@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw=="],
"@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.4" }, "os": "linux", "cpu": "arm" }, "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw=="],
"@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.0.5" }, "os": "linux", "cpu": "arm" }, "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ=="],
"@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg=="],
"@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.0.4" }, "os": "linux", "cpu": "arm64" }, "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA=="],
"@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ=="],
"@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.0.4" }, "os": "linux", "cpu": "x64" }, "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA=="],
"@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg=="],
"@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" }, "os": "linux", "cpu": "arm64" }, "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g=="],
"@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q=="],
"@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.0.4" }, "os": "linux", "cpu": "x64" }, "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw=="],
"@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g=="],
"@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="],
"@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.33.5", "", { "os": "win32", "cpu": "x64" }, "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg=="],
"@types/bun": ["@types/bun@1.2.19", "", { "dependencies": { "bun-types": "1.2.19" } }, "sha512-d9ZCmrH3CJ2uYKXQIUuZ/pUnTqIvLDS0SK7pFmbx8ma+ziH/FRMoAq5bYpRG7y+w1gl+HgyNZbtqgMq4W4e2Lg=="],
+1 -1
View File
@@ -11,7 +11,7 @@
},
"dependencies": {
"@actions/core": "^1.10.1",
"@anthropic-ai/claude-agent-sdk": "^0.2.83",
"@anthropic-ai/claude-agent-sdk": "^0.2.42",
"shell-quote": "^1.8.3"
},
"devDependencies": {
+1 -1
View File
@@ -119,7 +119,7 @@ function sanitizeSdkOutput(
duration_ms: resultMsg.duration_ms,
num_turns: resultMsg.num_turns,
total_cost_usd: resultMsg.total_cost_usd,
permission_denials_count: resultMsg.permission_denials?.length ?? 0,
permission_denials: resultMsg.permission_denials,
},
null,
2,
+17 -19
View File
@@ -7,7 +7,7 @@
"dependencies": {
"@actions/core": "^1.10.1",
"@actions/github": "^6.0.1",
"@anthropic-ai/claude-agent-sdk": "^0.2.83",
"@anthropic-ai/claude-agent-sdk": "^0.2.42",
"@modelcontextprotocol/sdk": "^1.11.0",
"@octokit/graphql": "^8.2.2",
"@octokit/rest": "^21.1.1",
@@ -37,41 +37,39 @@
"@actions/io": ["@actions/io@1.1.3", "", {}, "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="],
"@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.2.83", "", { "optionalDependencies": { "@img/sharp-darwin-arm64": "^0.34.2", "@img/sharp-darwin-x64": "^0.34.2", "@img/sharp-linux-arm": "^0.34.2", "@img/sharp-linux-arm64": "^0.34.2", "@img/sharp-linux-x64": "^0.34.2", "@img/sharp-linuxmusl-arm64": "^0.34.2", "@img/sharp-linuxmusl-x64": "^0.34.2", "@img/sharp-win32-arm64": "^0.34.2", "@img/sharp-win32-x64": "^0.34.2" }, "peerDependencies": { "zod": "^4.0.0" } }, "sha512-O8g56htGMxrwbjCbqUqRBMNC0O98B7SkPnfQC7vmo3w2DVnUrBj3qat/IBLB8SI4sjVSZHeJrcK7+ozsCzStSw=="],
"@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.2.42", "", { "optionalDependencies": { "@img/sharp-darwin-arm64": "^0.33.5", "@img/sharp-darwin-x64": "^0.33.5", "@img/sharp-linux-arm": "^0.33.5", "@img/sharp-linux-arm64": "^0.33.5", "@img/sharp-linux-x64": "^0.33.5", "@img/sharp-linuxmusl-arm64": "^0.33.5", "@img/sharp-linuxmusl-x64": "^0.33.5", "@img/sharp-win32-x64": "^0.33.5" }, "peerDependencies": { "zod": "^4.0.0" } }, "sha512-/CugP7AjP57Dqtl2sbsDtxdbpQoPKIhjyF5WrTViGu4NHQdM+UikrRs4MhZ2jeotiC5R7iK9ZUN9SiBgcZ8oLw=="],
"@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="],
"@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="],
"@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.0.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ=="],
"@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.4" }, "os": "darwin", "cpu": "x64" }, "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw=="],
"@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.0.4" }, "os": "darwin", "cpu": "x64" }, "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q=="],
"@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g=="],
"@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg=="],
"@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg=="],
"@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.0.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ=="],
"@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A=="],
"@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.0.5", "", { "os": "linux", "cpu": "arm" }, "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g=="],
"@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw=="],
"@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA=="],
"@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw=="],
"@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw=="],
"@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw=="],
"@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA=="],
"@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg=="],
"@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw=="],
"@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.4" }, "os": "linux", "cpu": "arm" }, "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw=="],
"@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.0.5" }, "os": "linux", "cpu": "arm" }, "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ=="],
"@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg=="],
"@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.0.4" }, "os": "linux", "cpu": "arm64" }, "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA=="],
"@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ=="],
"@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.0.4" }, "os": "linux", "cpu": "x64" }, "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA=="],
"@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg=="],
"@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" }, "os": "linux", "cpu": "arm64" }, "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g=="],
"@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q=="],
"@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.0.4" }, "os": "linux", "cpu": "x64" }, "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw=="],
"@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g=="],
"@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="],
"@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.33.5", "", { "os": "win32", "cpu": "x64" }, "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg=="],
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.16.0", "", { "dependencies": { "ajv": "^6.12.6", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-8ofX7gkZcLj9H9rSd50mCgm3SSF8C7XoclxJuLoV0Cz3rEQ1tv9MZRYYvJtm9n1BiEQQMzSmE/w2AEkNacLYfg=="],
-5
View File
@@ -4,16 +4,11 @@
- **Repository Access**: The action can only be triggered by users with write access to the repository
- **Bot User Control**: By default, GitHub Apps and bots cannot trigger this action for security reasons. Use the `allowed_bots` parameter to enable specific bots or all bots
- **⚠️ Allowed bots are not checked for repository permissions.** A bot that matches an entry does **not** need to be installed on your repository or have write access. On a **public repository**, external parties — including GitHub Apps created by anyone — may be able to trigger workflow events such as opening issues, commenting, or reviewing pull requests. If your workflow listens on those events and `allowed_bots` is set to `'*'`, any such App can invoke this action with a prompt it controls.
- Prefer an explicit list over `'*'`
- Only list App names you trust
- If you need `'*'`, scope workflow `permissions:` to the minimum required
- **⚠️ Non-Write User Access (RISKY)**: The `allowed_non_write_users` parameter allows bypassing the write permission requirement. **This is a significant security risk and should only be used for workflows with extremely limited permissions** (e.g., issue labeling workflows that only have `issues: write` permission). This feature:
- Only works when `github_token` is provided as input (not with GitHub App authentication)
- Accepts either a comma-separated list of specific usernames or `*` to allow all users
- **Should be used with extreme caution** as it bypasses the primary security mechanism of this action
- Is designed for automation workflows where user permissions are already restricted by the workflow's permission scope
- When set, Claude does a best-effort scrub of Anthropic, cloud, and GitHub Actions secrets from subprocess environments. This reduces but does not eliminate prompt injection risk — keep workflow permissions minimal and validate all outputs. Set `CLAUDE_CODE_SUBPROCESS_ENV_SCRUB: 0` in your workflow or job `env:` block to opt out.
- **Token Permissions**: The GitHub app receives only a short-lived token scoped specifically to the repository it's operating in
- **No Cross-Repository Access**: Each action invocation is limited to the repository where it was triggered
- **Limited Scope**: The token cannot access other repositories or perform actions beyond the configured permissions
+4 -11
View File
@@ -55,7 +55,7 @@ jobs:
Note: The PR branch is already checked out in the current working directory.
Use `gh pr comment` for top-level feedback.
Use `mcp__github_inline_comment__create_inline_comment` (with `confirmed: true`) to highlight specific code issues.
Use `mcp__github_inline_comment__create_inline_comment` to highlight specific code issues.
Only post GitHub comments - don't submit review text as messages.
claude_args: |
@@ -398,7 +398,6 @@ jobs:
issues: write
id-token: write
steps:
- uses: actions/checkout@v4
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
@@ -415,18 +414,13 @@ jobs:
3. Suggest appropriate labels
4. Check if it duplicates existing issues
Use ./scripts/gh.sh to interact with GitHub:
- `./scripts/gh.sh issue view [number]` to view the issue
- `./scripts/gh.sh search issues "query"` to find similar issues
- `./scripts/gh.sh label list` to see available labels
Based on your analysis, add the appropriate labels using:
`./scripts/edit-issue-labels.sh --issue [number] --add-label "label1" --add-label "label2"`
`gh issue edit [number] --add-label "label1,label2"`
If it appears to be a duplicate, post a comment mentioning the original issue.
claude_args: |
--allowedTools "Bash(./scripts/gh.sh:*),Bash(./scripts/edit-issue-labels.sh:*)"
--allowedTools "Bash(gh issue:*),Bash(gh search:*)"
```
**Key Configuration:**
@@ -434,7 +428,6 @@ jobs:
- Triggered on new issues
- Issue context in prompt
- Label management capabilities
- Requires `scripts/gh.sh` and `scripts/edit-issue-labels.sh` in your repo (see this repo's `scripts/` directory for examples)
**Expected Output:** Automatically labeled and categorized issues.
@@ -585,7 +578,7 @@ prompt: |
### Common Tool Permissions
- **PR Comments**: `Bash(gh pr comment:*)`
- **Inline Comments**: `mcp__github_inline_comment__create_inline_comment` — pass `confirmed: true` to post immediately. When omitted, the comment is buffered and classified after the session ends (real review comments post, test/probe comments are filtered). This prevents subagent test comments from reaching PRs. To disable classification entirely, set `classify_inline_comments: 'false'` on the action.
- **Inline Comments**: `mcp__github_inline_comment__create_inline_comment`
- **File Operations**: `Read,Write,Edit`
- **Git Operations**: `Bash(git:*)`
+29 -30
View File
@@ -52,36 +52,35 @@ jobs:
## Inputs
| Input | Description | Required | Default |
| -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------- |
| `anthropic_api_key` | Anthropic API key (required for direct API, not needed for Bedrock/Vertex) | No\* | - |
| `claude_code_oauth_token` | Claude Code OAuth token (alternative to anthropic_api_key) | No\* | - |
| `prompt` | Instructions for Claude. Can be a direct prompt or custom template for automation workflows | No | - |
| `track_progress` | Force tag mode with tracking comments. Only works with specific PR/issue events. Preserves GitHub context | No | `false` |
| `include_fix_links` | Include 'Fix this' links in PR code review feedback that open Claude Code with context to fix the identified issue | No | `true` |
| `claude_args` | Additional [arguments to pass directly to Claude CLI](https://docs.claude.com/en/docs/claude-code/cli-reference#cli-flags) (e.g., `--max-turns 10 --model claude-4-0-sonnet-20250805`) | No | "" |
| `base_branch` | The base branch to use for creating new branches (e.g., 'main', 'develop') | No | - |
| `use_sticky_comment` | Use just one comment to deliver PR comments (only applies for pull_request event workflows) | No | `false` |
| `classify_inline_comments` | Buffer inline comments without `confirmed: true` and classify them (real review vs test/probe) via Haiku before posting after the session ends. Prevents subagent test comments. Set `'false'` to post all inline comments immediately | No | `true` |
| `github_token` | GitHub token for Claude to operate with. **Only include this if you're connecting a custom GitHub app of your own!** | No | - |
| `use_bedrock` | Use Amazon Bedrock with OIDC authentication instead of direct Anthropic API | No | `false` |
| `use_vertex` | Use Google Vertex AI with OIDC authentication instead of direct Anthropic API | No | `false` |
| `assignee_trigger` | The assignee username that triggers the action (e.g. @claude). Only used for issue assignment | No | - |
| `label_trigger` | The label name that triggers the action when applied to an issue (e.g. "claude") | No | - |
| `trigger_phrase` | The trigger phrase to look for in comments, issue/PR bodies, and issue titles | No | `@claude` |
| `branch_prefix` | The prefix to use for Claude branches (defaults to 'claude/', use 'claude-' for dash format) | No | `claude/` |
| `settings` | Claude Code settings as JSON string or path to settings JSON file | No | "" |
| `additional_permissions` | Additional permissions to enable. Currently supports 'actions: read' for viewing workflow results | No | "" |
| `use_commit_signing` | Enable commit signing using GitHub's API. Simple but cannot perform complex git operations like rebasing. See [Security](./security.md#commit-signing) | No | `false` |
| `ssh_signing_key` | SSH private key for signing commits. Enables signed commits with full git CLI support (rebasing, etc.). See [Security](./security.md#commit-signing) | No | "" |
| `bot_id` | GitHub user ID to use for git operations (defaults to Claude's bot ID). Required with `ssh_signing_key` for verified commits | No | `41898282` |
| `bot_name` | GitHub username to use for git operations (defaults to Claude's bot name). Required with `ssh_signing_key` for verified commits | No | `claude[bot]` |
| `allowed_bots` | Comma-separated list of allowed bot usernames, or '\*' to allow all bots. Empty string (default) allows no bots. **⚠️ On public repos with `'*'`, external Apps may be able to invoke this action.** See [Security](./security.md) | 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 | "" |
| `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 | "" |
| `plugins` | Newline-separated list of Claude Code plugin names to install (e.g., see example in workflow above). Plugins are installed before Claude Code execution | No | "" |
| Input | Description | Required | Default |
| -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------- |
| `anthropic_api_key` | Anthropic API key (required for direct API, not needed for Bedrock/Vertex) | No\* | - |
| `claude_code_oauth_token` | Claude Code OAuth token (alternative to anthropic_api_key) | No\* | - |
| `prompt` | Instructions for Claude. Can be a direct prompt or custom template for automation workflows | No | - |
| `track_progress` | Force tag mode with tracking comments. Only works with specific PR/issue events. Preserves GitHub context | No | `false` |
| `include_fix_links` | Include 'Fix this' links in PR code review feedback that open Claude Code with context to fix the identified issue | No | `true` |
| `claude_args` | Additional [arguments to pass directly to Claude CLI](https://docs.claude.com/en/docs/claude-code/cli-reference#cli-flags) (e.g., `--max-turns 10 --model claude-4-0-sonnet-20250805`) | No | "" |
| `base_branch` | The base branch to use for creating new branches (e.g., 'main', 'develop') | No | - |
| `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 | - |
| `use_bedrock` | Use Amazon Bedrock with OIDC authentication instead of direct Anthropic API | No | `false` |
| `use_vertex` | Use Google Vertex AI with OIDC authentication instead of direct Anthropic API | No | `false` |
| `assignee_trigger` | The assignee username that triggers the action (e.g. @claude). Only used for issue assignment | No | - |
| `label_trigger` | The label name that triggers the action when applied to an issue (e.g. "claude") | No | - |
| `trigger_phrase` | The trigger phrase to look for in comments, issue/PR bodies, and issue titles | No | `@claude` |
| `branch_prefix` | The prefix to use for Claude branches (defaults to 'claude/', use 'claude-' for dash format) | No | `claude/` |
| `settings` | Claude Code settings as JSON string or path to settings JSON file | No | "" |
| `additional_permissions` | Additional permissions to enable. Currently supports 'actions: read' for viewing workflow results | No | "" |
| `use_commit_signing` | Enable commit signing using GitHub's API. Simple but cannot perform complex git operations like rebasing. See [Security](./security.md#commit-signing) | No | `false` |
| `ssh_signing_key` | SSH private key for signing commits. Enables signed commits with full git CLI support (rebasing, etc.). See [Security](./security.md#commit-signing) | No | "" |
| `bot_id` | GitHub user ID to use for git operations (defaults to Claude's bot ID). Required with `ssh_signing_key` for verified commits | No | `41898282` |
| `bot_name` | GitHub username to use for git operations (defaults to Claude's bot name). Required with `ssh_signing_key` for verified commits | 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 | "" |
| `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 | "" |
| `plugins` | Newline-separated list of Claude Code plugin names to install (e.g., see example in workflow above). Plugins are installed before Claude Code execution | No | "" |
### Deprecated Inputs
+2 -2
View File
@@ -21,8 +21,8 @@ jobs:
- name: Run Claude Code for Issue Triage
uses: anthropics/claude-code-action@v1
with:
# NOTE: /label-issue requires .claude/commands/label-issue.md and scripts/edit-issue-labels.sh in your repo (see this repo for examples)
prompt: "/label-issue REPO: ${{ github.repository }} ISSUE_NUMBER: ${{ github.event.issue.number }}"
# NOTE: /label-issue here requires a .claude/commands/label-issue.md file in your repo (see this repo's .claude directory for an example)
prompt: "/label-issue REPO: ${{ github.repository }} ISSUE_NUMBER${{ github.event.issue.number }}"
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
allowed_non_write_users: "*" # Required for issue triage workflow, if users without repo write access create issues
+1 -1
View File
@@ -12,7 +12,7 @@
"dependencies": {
"@actions/core": "^1.10.1",
"@actions/github": "^6.0.1",
"@anthropic-ai/claude-agent-sdk": "^0.2.83",
"@anthropic-ai/claude-agent-sdk": "^0.2.42",
"@modelcontextprotocol/sdk": "^1.11.0",
"@octokit/graphql": "^8.2.2",
"@octokit/rest": "^21.1.1",
-87
View File
@@ -1,87 +0,0 @@
#!/usr/bin/env bash
#
# Edits labels on a GitHub issue.
# Usage: ./scripts/edit-issue-labels.sh --issue 123 --add-label bug --add-label needs-triage --remove-label untriaged
#
set -euo pipefail
ISSUE=""
ADD_LABELS=()
REMOVE_LABELS=()
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--issue)
ISSUE="$2"
shift 2
;;
--add-label)
ADD_LABELS+=("$2")
shift 2
;;
--remove-label)
REMOVE_LABELS+=("$2")
shift 2
;;
*)
exit 1
;;
esac
done
# Validate issue number
if [[ -z "$ISSUE" ]]; then
exit 1
fi
if ! [[ "$ISSUE" =~ ^[0-9]+$ ]]; then
exit 1
fi
if [[ ${#ADD_LABELS[@]} -eq 0 && ${#REMOVE_LABELS[@]} -eq 0 ]]; then
exit 1
fi
# Fetch valid labels from the repo
VALID_LABELS=$(gh label list --limit 500 --json name --jq '.[].name')
# Filter to only labels that exist in the repo
FILTERED_ADD=()
for label in "${ADD_LABELS[@]}"; do
if echo "$VALID_LABELS" | grep -qxF "$label"; then
FILTERED_ADD+=("$label")
fi
done
FILTERED_REMOVE=()
for label in "${REMOVE_LABELS[@]}"; do
if echo "$VALID_LABELS" | grep -qxF "$label"; then
FILTERED_REMOVE+=("$label")
fi
done
if [[ ${#FILTERED_ADD[@]} -eq 0 && ${#FILTERED_REMOVE[@]} -eq 0 ]]; then
exit 0
fi
# Build gh command arguments
GH_ARGS=("issue" "edit" "$ISSUE")
for label in "${FILTERED_ADD[@]}"; do
GH_ARGS+=("--add-label" "$label")
done
for label in "${FILTERED_REMOVE[@]}"; do
GH_ARGS+=("--remove-label" "$label")
done
gh "${GH_ARGS[@]}"
if [[ ${#FILTERED_ADD[@]} -gt 0 ]]; then
echo "Added: ${FILTERED_ADD[*]}"
fi
if [[ ${#FILTERED_REMOVE[@]} -gt 0 ]]; then
echo "Removed: ${FILTERED_REMOVE[*]}"
fi
-96
View File
@@ -1,96 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# Wrapper around gh CLI that only allows specific subcommands and flags.
# All commands are scoped to the current repository via GH_REPO or GITHUB_REPOSITORY.
#
# Usage:
# ./scripts/gh.sh issue view 123
# ./scripts/gh.sh issue view 123 --comments
# ./scripts/gh.sh issue list --state open --limit 20
# ./scripts/gh.sh search issues "search query" --limit 10
# ./scripts/gh.sh label list --limit 100
export GH_HOST=github.com
REPO="${GH_REPO:-${GITHUB_REPOSITORY:-}}"
if [[ -z "$REPO" || "$REPO" == */*/* || "$REPO" != */* ]]; then
echo "Error: GH_REPO or GITHUB_REPOSITORY must be set to owner/repo format (e.g., GITHUB_REPOSITORY=anthropics/claude-code)" >&2
exit 1
fi
export GH_REPO="$REPO"
ALLOWED_FLAGS=(--comments --state --limit --label)
FLAGS_WITH_VALUES=(--state --limit --label)
SUB1="${1:-}"
SUB2="${2:-}"
CMD="$SUB1 $SUB2"
case "$CMD" in
"issue view"|"issue list"|"search issues"|"label list")
;;
*)
echo "Error: only 'issue view', 'issue list', 'search issues', 'label list' are allowed (e.g., ./scripts/gh.sh issue view 123)" >&2
exit 1
;;
esac
shift 2
# Separate flags from positional arguments
POSITIONAL=()
FLAGS=()
skip_next=false
for arg in "$@"; do
if [[ "$skip_next" == true ]]; then
FLAGS+=("$arg")
skip_next=false
elif [[ "$arg" == -* ]]; then
flag="${arg%%=*}"
matched=false
for allowed in "${ALLOWED_FLAGS[@]}"; do
if [[ "$flag" == "$allowed" ]]; then
matched=true
break
fi
done
if [[ "$matched" == false ]]; then
echo "Error: only --comments, --state, --limit, --label flags are allowed (e.g., ./scripts/gh.sh issue list --state open --limit 20)" >&2
exit 1
fi
FLAGS+=("$arg")
# If flag expects a value and isn't using = syntax, skip next arg
if [[ "$arg" != *=* ]]; then
for vflag in "${FLAGS_WITH_VALUES[@]}"; do
if [[ "$flag" == "$vflag" ]]; then
skip_next=true
break
fi
done
fi
else
POSITIONAL+=("$arg")
fi
done
if [[ "$CMD" == "search issues" ]]; then
QUERY="${POSITIONAL[0]:-}"
QUERY_LOWER=$(echo "$QUERY" | tr '[:upper:]' '[:lower:]')
if [[ "$QUERY_LOWER" == *"repo:"* || "$QUERY_LOWER" == *"org:"* || "$QUERY_LOWER" == *"user:"* ]]; then
echo "Error: search query must not contain repo:, org:, or user: qualifiers (e.g., ./scripts/gh.sh search issues \"bug report\" --limit 10)" >&2
exit 1
fi
gh "$SUB1" "$SUB2" "$QUERY" --repo "$REPO" "${FLAGS[@]}"
elif [[ "$CMD" == "issue view" ]]; then
if [[ ${#POSITIONAL[@]} -ne 1 ]] || ! [[ "${POSITIONAL[0]}" =~ ^[0-9]+$ ]]; then
echo "Error: issue view requires exactly one numeric issue number (e.g., ./scripts/gh.sh issue view 123)" >&2
exit 1
fi
gh "$SUB1" "$SUB2" "${POSITIONAL[0]}" "${FLAGS[@]}"
else
if [[ ${#POSITIONAL[@]} -ne 0 ]]; then
echo "Error: issue list and label list do not accept positional arguments (e.g., ./scripts/gh.sh issue list --state open, ./scripts/gh.sh label list --limit 100)" >&2
exit 1
fi
gh "$SUB1" "$SUB2" "${FLAGS[@]}"
fi
-36
View File
@@ -1,36 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# Wrapper around `git push` that only allows `origin <ref>` with no flags.
# Defends against --receive-pack / --exec RCE and arbitrary-remote exfiltration
# (H1 #3556799). `git push:*` in allowedTools permits `git push --receive-pack='sh -c ...' ext::sh`
# which runs arbitrary shell on the Actions runner. This wrapper closes that.
#
# Usage:
# git-push.sh origin HEAD
# git-push.sh origin claude/issue-123-20260304
if [[ $# -ne 2 ]]; then
echo "Error: exactly two arguments required: origin <ref>" >&2
exit 1
fi
for arg in "$@"; do
if [[ "$arg" == -* ]]; then
echo "Error: flags are not allowed (got: $arg)" >&2
exit 1
fi
done
if [[ "$1" != "origin" ]]; then
echo "Error: remote must be 'origin' (got: $1)" >&2
exit 1
fi
REF="$2"
if [[ "$REF" != "HEAD" ]] && ! git check-ref-format --branch "$REF" >/dev/null 2>&1; then
echo "Error: invalid ref: $REF" >&2
exit 1
fi
exec git push origin "$REF"
+18 -13
View File
@@ -23,15 +23,19 @@ import { GITHUB_SERVER_URL } from "../github/api/config";
import { extractUserRequest } from "../utils/extract-user-request";
export type { CommonFields, PreparedContext } from "./types";
const GIT_PUSH_WRAPPER = `${process.env.GITHUB_ACTION_PATH}/scripts/git-push.sh`;
/** Filename for the user request file, read by the SDK runner */
const USER_REQUEST_FILENAME = "claude-user-request.txt";
// Tag mode defaults - these tools are needed for tag mode to function.
// Edit/MultiEdit/Write are intentionally omitted: acceptEdits permission mode
// auto-allows file edits inside $GITHUB_WORKSPACE and denies writes outside it.
const BASE_ALLOWED_TOOLS = ["Glob", "Grep", "LS", "Read"];
// Tag mode defaults - these tools are needed for tag mode to function
const BASE_ALLOWED_TOOLS = [
"Edit",
"MultiEdit",
"Glob",
"Grep",
"LS",
"Read",
"Write",
];
export function buildAllowedToolsString(
customAllowedTools?: string[],
@@ -55,7 +59,10 @@ export function buildAllowedToolsString(
baseTools.push(
"Bash(git add:*)",
"Bash(git commit:*)",
`Bash(${GIT_PUSH_WRAPPER}:*)`,
"Bash(git push:*)",
"Bash(git status:*)",
"Bash(git diff:*)",
"Bash(git log:*)",
"Bash(git rm:*)",
);
}
@@ -427,7 +434,7 @@ function getCommitInstructions(
Bash(git commit -m "<message>\\n\\n${coAuthorLine}")`
: ""
}
- Push to the remote: Bash(${GIT_PUSH_WRAPPER} origin HEAD)`;
- Push to the remote: Bash(git push origin HEAD)`;
} else {
const branchName = eventData.claudeBranch || eventData.baseBranch;
return `
@@ -441,7 +448,7 @@ function getCommitInstructions(
Bash(git commit -m "<message>\\n\\n${coAuthorLine}")`
: ""
}
- Push to the remote: Bash(${GIT_PUSH_WRAPPER} origin ${branchName})`;
- Push to the remote: Bash(git push origin ${branchName})`;
}
}
}
@@ -816,7 +823,7 @@ ${
: `- Use git commands via the Bash tool for version control (remember that you have access to these git commands):
- Stage files: Bash(git add <files>)
- Commit changes: Bash(git commit -m "<message>")
- Push to remote: Bash(${GIT_PUSH_WRAPPER} origin <branch>)
- Push to remote: Bash(git push origin <branch>) (NEVER force push)
- Delete files: Bash(git rm <files>) followed by commit and push
- Check status: Bash(git status)
- View diff: Bash(git diff)${eventData.isPR && eventData.baseBranch ? `\n - IMPORTANT: For PR diffs, use: Bash(git diff origin/${eventData.baseBranch}...HEAD)` : ""}`
@@ -970,9 +977,7 @@ export async function createPrompt(
console.log("========================");
}
// NOTE: these env var exports are dead — nothing reads ALLOWED_TOOLS / DISALLOWED_TOOLS.
// The live path is modes/tag/index.ts which builds --allowedTools into claudeArgs directly.
// Kept only so the H1 report's pointed-to file stays in sync with the live fix.
// Set allowed tools
const hasActionsReadPermission = false;
const allAllowedTools = buildAllowedToolsString(
-1
View File
@@ -23,7 +23,6 @@ export function collectActionInputsPresence(): string {
github_token: "",
max_turns: "",
use_sticky_comment: "false",
classify_inline_comments: "true",
use_commit_signing: "false",
ssh_signing_key: "",
};
@@ -1,233 +0,0 @@
#!/usr/bin/env bun
/**
* Reads buffered inline-comment calls from /tmp/inline-comments-buffer.jsonl,
* classifies each as "real review" vs "test/probe" using Haiku, and posts
* only the real ones. Calls with confirmed=false are never posted.
*
* If the Anthropic API is unavailable (Bedrock/Vertex users without a direct
* key), falls back to posting everything with confirmed !== false. This
* preserves backward compatibility before this change, all unconfirmed
* calls posted immediately.
*/
import { readFileSync } from "fs";
import { createOctokit } from "../github/api/client";
const BUFFER_PATH = "/tmp/inline-comments-buffer.jsonl";
type BufferedComment = {
ts: string;
path: string;
line?: number;
startLine?: number;
side?: "LEFT" | "RIGHT";
commit_id?: string;
body: string;
confirmed?: boolean;
};
const CLASSIFICATION_PROMPT = `You are classifying PR inline comments as either REAL code review feedback or TEST/PROBE calls.
A TEST/PROBE call is when an automated agent is checking whether a commenting tool works. These typically:
- Start with phrases like "Test comment", "Testing if", "Can I", "Does this work", "Checking if"
- Have generic/placeholder content not specific to any code
- Exist to verify tool functionality, not to provide review feedback
A REAL review comment:
- Discusses specific code, logic, bugs, or style
- Provides actionable feedback for the PR author
- References concrete aspects of the change
For each numbered comment body below, respond with ONLY a JSON array of booleans where true = REAL review comment, false = test/probe. No other text.
Comments:
`;
async function classifyComments(bodies: string[]): Promise<boolean[] | null> {
const apiKey = process.env.ANTHROPIC_API_KEY;
if (!apiKey) {
console.log(
"ANTHROPIC_API_KEY not set — skipping classification, posting all unconfirmed comments",
);
return null;
}
const prompt =
CLASSIFICATION_PROMPT +
bodies.map((b, i) => `${i + 1}. ${JSON.stringify(b)}`).join("\n");
try {
const res = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: {
"content-type": "application/json",
"x-api-key": apiKey,
"anthropic-version": "2023-06-01",
},
body: JSON.stringify({
model: "claude-haiku-4-5",
max_tokens: 1024,
messages: [{ role: "user", content: prompt }],
}),
});
if (!res.ok) {
console.log(
`Classification API returned ${res.status} — posting all unconfirmed comments`,
);
return null;
}
const data = (await res.json()) as {
content: { type: string; text: string }[];
};
const text = data.content.find((c) => c.type === "text")?.text ?? "";
const match = text.match(/\[[\s\S]*\]/);
if (!match) {
console.log(
"Could not parse classification response — posting all unconfirmed comments",
);
return null;
}
const parsed = JSON.parse(match[0]);
if (
!Array.isArray(parsed) ||
parsed.length !== bodies.length ||
!parsed.every((v) => typeof v === "boolean")
) {
console.log(
"Classification response shape mismatch — posting all unconfirmed comments",
);
return null;
}
return parsed;
} catch (e) {
console.log(
`Classification failed (${e instanceof Error ? e.message : String(e)}) — posting all unconfirmed comments`,
);
return null;
}
}
async function postComment(
octokit: ReturnType<typeof createOctokit>["rest"],
owner: string,
repo: string,
pull_number: number,
headSha: string,
c: BufferedComment,
): Promise<boolean> {
const params: Parameters<typeof octokit.rest.pulls.createReviewComment>[0] = {
owner,
repo,
pull_number,
body: c.body,
path: c.path,
side: c.side || "RIGHT",
commit_id: c.commit_id || headSha,
};
if (c.startLine) {
params.start_line = c.startLine;
params.start_side = c.side || "RIGHT";
params.line = c.line;
} else {
params.line = c.line;
}
try {
await octokit.rest.pulls.createReviewComment(params);
return true;
} catch (e) {
console.log(
` failed ${c.path}:${c.line}: ${e instanceof Error ? e.message : String(e)}`,
);
return false;
}
}
async function main() {
let raw: string;
try {
raw = readFileSync(BUFFER_PATH, "utf8");
} catch {
console.log("No buffered inline comments");
return;
}
const comments: BufferedComment[] = raw
.split("\n")
.filter(Boolean)
.map((line) => JSON.parse(line));
if (comments.length === 0) {
console.log("No buffered inline comments");
return;
}
console.log(`Found ${comments.length} buffered inline comment(s)`);
const githubToken = process.env.GITHUB_TOKEN;
const owner = process.env.REPO_OWNER;
const repo = process.env.REPO_NAME;
const prNumber = process.env.PR_NUMBER;
if (!githubToken || !owner || !repo || !prNumber) {
console.log(
"::warning::Missing GITHUB_TOKEN/REPO_OWNER/REPO_NAME/PR_NUMBER — cannot post buffered comments",
);
return;
}
// Partition: confirmed=false are never posted; the rest are candidates
const neverPost = comments.filter((c) => c.confirmed === false);
const candidates = comments.filter((c) => c.confirmed !== false);
if (neverPost.length > 0) {
console.log(` ${neverPost.length} with confirmed=false — not posting`);
}
if (candidates.length === 0) {
return;
}
// Classify candidates
const verdicts = await classifyComments(candidates.map((c) => c.body));
const toPost =
verdicts === null
? candidates
: candidates.filter((_, i) => verdicts[i] === true);
const filtered =
verdicts === null ? [] : candidates.filter((_, i) => verdicts[i] === false);
if (filtered.length > 0) {
console.log(
`::warning::${filtered.length} buffered comment(s) classified as test/probe — NOT posted:`,
);
for (const c of filtered) {
console.log(` [${c.path}:${c.line}] ${c.body.slice(0, 120)}`);
}
}
if (toPost.length === 0) {
console.log("No real comments to post");
return;
}
const octokit = createOctokit(githubToken).rest;
const pull_number = parseInt(prNumber, 10);
const pr = await octokit.pulls.get({ owner, repo, pull_number });
const headSha = pr.data.head.sha;
console.log(`Posting ${toPost.length} classified-as-real comment(s)`);
let posted = 0;
for (const c of toPost) {
if (await postComment(octokit, owner, repo, pull_number, headSha, c)) {
console.log(` posted ${c.path}:${c.line}`);
posted++;
}
}
console.log(`Posted ${posted}/${toPost.length}`);
}
main().catch((e) => {
console.error("post-buffered-inline-comments failed:", e);
process.exit(1);
});
+4 -40
View File
@@ -15,20 +15,12 @@ import { setupGitHubToken, WorkflowValidationSkipError } from "../github/token";
import { checkWritePermissions } from "../github/validation/permissions";
import { createOctokit } from "../github/api/client";
import type { Octokits } from "../github/api/client";
import {
parseGitHubContext,
isEntityContext,
isPullRequestEvent,
isPullRequestReviewEvent,
isPullRequestReviewCommentEvent,
} from "../github/context";
import { parseGitHubContext, isEntityContext } from "../github/context";
import type { GitHubContext } from "../github/context";
import { detectMode } from "../modes/detector";
import { prepareTagMode } from "../modes/tag";
import { prepareAgentMode } from "../modes/agent";
import { checkContainsTrigger } from "../github/validation/trigger";
import { restoreConfigFromBase } from "../github/operations/restore-config";
import { validateBranchName } from "../github/operations/branch";
import { collectActionInputsPresence } from "./collect-inputs";
import { updateCommentLink } from "./update-comment-link";
import { formatTurnsFromData } from "./format-turns";
@@ -59,7 +51,7 @@ async function installClaudeCode(): Promise<void> {
return;
}
const claudeCodeVersion = "2.1.83";
const claudeCodeVersion = "2.1.42";
console.log(`Installing Claude Code v${claudeCodeVersion}...`);
for (let attempt = 1; attempt <= 3; attempt++) {
@@ -225,30 +217,6 @@ async function run() {
validateEnvironmentVariables();
// On PRs, .claude/ and .mcp.json in the checkout are attacker-controlled.
// Restore them from the base branch before the CLI reads them.
//
// We read pull_request.base.ref from the payload directly because agent
// mode's branchInfo.baseBranch defaults to "main" rather than the PR's
// actual target (agent/index.ts). For issue_comment on a PR the payload
// lacks base.ref, so we fall back to the mode-provided value — tag mode
// fetches it from GraphQL; agent mode on issue_comment is an edge case
// that at worst restores from the wrong trusted branch (still secure).
if (isEntityContext(context) && context.isPR) {
let restoreBase = baseBranch;
if (
isPullRequestEvent(context) ||
isPullRequestReviewEvent(context) ||
isPullRequestReviewCommentEvent(context)
) {
restoreBase = context.payload.pull_request.base.ref;
validateBranchName(restoreBase);
}
if (restoreBase) {
restoreConfigFromBase(restoreBase);
}
}
await setupClaudeCodeSettings(process.env.INPUT_SETTINGS);
await installPlugins(
@@ -327,12 +295,8 @@ async function run() {
}
}
// Write step summary (unless display_report is set to false)
if (
executionFile &&
existsSync(executionFile) &&
process.env.DISPLAY_REPORT !== "false"
) {
// Write step summary
if (executionFile && existsSync(executionFile)) {
await writeStepSummary(executionFile);
}
-2
View File
@@ -90,7 +90,6 @@ type BaseContext = {
branchPrefix: string;
branchNameTemplate?: string;
useStickyComment: boolean;
classifyInlineComments: boolean;
useCommitSigning: boolean;
sshSigningKey: string;
botId: string;
@@ -151,7 +150,6 @@ export function parseGitHubContext(): GitHubContext {
branchPrefix: process.env.BRANCH_PREFIX ?? "claude/",
branchNameTemplate: process.env.BRANCH_NAME_TEMPLATE,
useStickyComment: process.env.USE_STICKY_COMMENT === "true",
classifyInlineComments: process.env.CLASSIFY_INLINE_COMMENTS !== "false",
useCommitSigning: process.env.USE_COMMIT_SIGNING === "true",
sshSigningKey: process.env.SSH_SIGNING_KEY || "",
botId: process.env.BOT_ID ?? String(CLAUDE_APP_BOT_ID),
+46 -4
View File
@@ -42,13 +42,55 @@ export async function configureGitAuth(
await $`git config user.email "${botId}+${botName}@${noreplyDomain}"`;
console.log(`✓ Set git user as ${botName}`);
// Remove the authorization header that actions/checkout sets
// Remove the authorization header that actions/checkout sets.
// Newer versions of actions/checkout store credentials in external config files
// referenced via includeIf, so --unset-all on local config may not find them.
// We clear both: local config entries and any includeIf credential files.
console.log("Removing existing git authentication headers...");
try {
await $`git config --unset-all http.${GITHUB_SERVER_URL}/.extraheader`;
console.log("✓ Removed existing authentication headers");
} catch (e) {
console.log("No existing authentication headers to remove");
console.log("✓ Removed existing authentication headers from local config");
} catch {
console.log("No existing authentication headers in local config to remove");
}
// Clear extraheader from includeIf credential config files set by actions/checkout
try {
const configOutput = await $`git config --list --show-origin`.text();
const extraheaderPattern = `http.${GITHUB_SERVER_URL}/.extraheader=`;
console.log(
`Scanning git config origins for extraheader pattern: ${extraheaderPattern}`,
);
let foundExternalEntries = false;
for (const line of configOutput.split("\n")) {
if (!line.includes(extraheaderPattern)) continue;
// Format: "file:<path>\t<key>=<value>"
const match = line.match(/^file:(.+?)\t/);
if (!match) {
console.log(
`Found extraheader but could not parse origin: ${line.substring(0, 80)}`,
);
continue;
}
const configFile = match[1]!;
// Skip the local .git/config — we already handled that above
if (configFile.endsWith("/.git/config")) {
console.log("Skipping .git/config (already handled)");
continue;
}
foundExternalEntries = true;
try {
await $`git config --file ${configFile} --unset-all http.${GITHUB_SERVER_URL}/.extraheader`;
console.log(`✓ Removed authentication headers from ${configFile}`);
} catch {
console.log(`Could not unset extraheader from ${configFile}`);
}
}
if (!foundExternalEntries) {
console.log("No external credential config files found");
}
} catch (error) {
console.log(`Could not enumerate git config origins: ${error}`);
}
// Update the remote URL to include the token for authentication
-79
View File
@@ -1,79 +0,0 @@
import { execFileSync } from "child_process";
import { rmSync } from "fs";
// Paths that are both PR-controllable and read from cwd at CLI startup.
//
// Deliberately excluded from the CLI's broader auto-edit blocklist:
// .git/ — not tracked by git; PR commits cannot place files there.
// Restoring it would also undo the PR checkout entirely.
// .gitconfig — git reads ~/.gitconfig and .git/config, never cwd/.gitconfig.
// .bashrc etc. — shells source these from $HOME; checkout cannot reach $HOME.
// .vscode/.idea— IDE config; nothing in the CLI's startup path reads them.
const SENSITIVE_PATHS = [
".claude",
".mcp.json",
".claude.json",
".gitmodules",
".ripgreprc",
];
/**
* Restores security-sensitive config paths from the PR base branch.
*
* The CLI's non-interactive mode trusts cwd: it reads `.mcp.json`,
* `.claude/settings.json`, and `.claude/settings.local.json` from the working
* directory and acts on them before any tool-permission gating executing
* hooks (including SessionStart), setting env vars (NODE_OPTIONS, LD_PRELOAD,
* PATH), running apiKeyHelper/awsAuthRefresh shell commands, and auto-approving
* MCP servers. When this action checks out a PR head, all of these are
* attacker-controlled.
*
* Rather than enumerate every dangerous key, this replaces the entire `.claude/`
* tree and `.mcp.json` with the versions from the PR base branch, which a
* maintainer has reviewed and merged. Paths absent on base are deleted.
*
* Known limitation: if a PR legitimately modifies `.claude/` and the CLI later
* commits with `git add -A`, the revert will be included in that commit. This
* is a narrow UX tradeoff for closing the RCE surface.
*
* @param baseBranch - PR base branch name. Must be pre-validated (branch.ts
* calls validateBranchName on it before returning).
*/
export function restoreConfigFromBase(baseBranch: string): void {
console.log(
`Restoring ${SENSITIVE_PATHS.join(", ")} from origin/${baseBranch} (PR head is untrusted)`,
);
// Fetch base first — if this fails we haven't touched the workspace and the
// caller sees a clean error.
execFileSync("git", ["fetch", "origin", baseBranch, "--depth=1"], {
stdio: "inherit",
});
// Delete PR-controlled versions. If the restore below fails for a given path,
// that path stays deleted — the safe fallback (no attacker-controlled config).
// A bare `git checkout` alone wouldn't remove files the PR added, so nuke first.
for (const p of SENSITIVE_PATHS) {
rmSync(p, { recursive: true, force: true });
}
for (const p of SENSITIVE_PATHS) {
try {
execFileSync("git", ["checkout", `origin/${baseBranch}`, "--", p], {
stdio: "pipe",
});
} catch {
// Path doesn't exist on base — it stays deleted.
}
}
// `git checkout <ref> -- <path>` stages the restored files. Unstage so the
// revert doesn't silently leak into commits the CLI makes later.
try {
execFileSync("git", ["reset", "--", ...SENSITIVE_PATHS], {
stdio: "pipe",
});
} catch {
// Nothing was staged, or paths don't exist on HEAD — either is fine.
}
}
+3 -57
View File
@@ -1,7 +1,6 @@
#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { appendFileSync } from "fs";
import { z } from "zod";
import { createOctokit } from "../github/api/client";
import { sanitizeContent } from "../github/utils/sanitizer";
@@ -11,13 +10,6 @@ const REPO_OWNER = process.env.REPO_OWNER;
const REPO_NAME = process.env.REPO_NAME;
const PR_NUMBER = process.env.PR_NUMBER;
// Calls without confirmed=true are buffered here instead of posted. This
// prevents subagents from posting test/probe comments when they inherit this
// tool and probe it after hitting unrelated errors. The action's post-step
// reports the buffer count for diagnostics.
const BUFFER_PATH = "/tmp/inline-comments-buffer.jsonl";
const CLASSIFY_ENABLED = process.env.CLASSIFY_INLINE_COMMENTS !== "false";
if (!REPO_OWNER || !REPO_NAME || !PR_NUMBER) {
console.error(
"Error: REPO_OWNER, REPO_NAME, and PR_NUMBER environment variables are required",
@@ -75,17 +67,8 @@ server.tool(
.describe(
"Specific commit SHA to comment on (defaults to latest commit)",
),
confirmed: z
.boolean()
.optional()
.describe(
"Set true to post immediately. When omitted, the call is buffered " +
"and classified after the session completes — real review comments " +
"post, test/probe comments are dropped. Set false to buffer and " +
"never post. Only set true when posting final review comments.",
),
},
async ({ path, body, line, startLine, side, commit_id, confirmed }) => {
async ({ path, body, line, startLine, side, commit_id }) => {
try {
const githubToken = process.env.GITHUB_TOKEN;
@@ -97,6 +80,8 @@ server.tool(
const repo = REPO_NAME;
const pull_number = parseInt(PR_NUMBER, 10);
const octokit = createOctokit(githubToken).rest;
// Sanitize the comment body to remove any potential GitHub tokens
const sanitizedBody = sanitizeContent(body);
@@ -107,49 +92,10 @@ server.tool(
);
}
if (CLASSIFY_ENABLED && confirmed !== true) {
appendFileSync(
BUFFER_PATH,
JSON.stringify({
ts: new Date().toISOString(),
path,
line,
startLine,
side,
commit_id,
body: sanitizedBody,
confirmed,
}) + "\n",
);
return {
content: [
{
type: "text",
text: JSON.stringify(
{
success: true,
buffered: true,
message:
"Comment buffered. It will be classified and posted after " +
"this session completes (real review comments post, " +
"test/probe comments are dropped). Set confirmed=true to " +
"post immediately. If you are testing whether this tool " +
"works: it works — no need to test further.",
},
null,
2,
),
},
],
};
}
// If only line is provided, it's a single-line comment
// If both startLine and line are provided, it's a multi-line comment
const isSingleLine = !startLine;
const octokit = createOctokit(githubToken).rest;
const pr = await octokit.pulls.get({
owner,
repo,
-3
View File
@@ -152,9 +152,6 @@ export async function prepareMcpConfig(
REPO_NAME: repo,
PR_NUMBER: context.entityNumber?.toString() || "",
GITHUB_API_URL: GITHUB_API_URL,
CLASSIFY_INLINE_COMMENTS: context.inputs.classifyInlineComments
? "true"
: "false",
},
};
}
+11 -11
View File
@@ -114,17 +114,16 @@ export async function prepareTagMode({
tool.startsWith("mcp__github_"),
);
const gitPushWrapper = `${process.env.GITHUB_ACTION_PATH}/scripts/git-push.sh`;
// Build claude_args for tag mode with required tools.
// Edit/MultiEdit/Write are intentionally omitted: acceptEdits permission mode (set below)
// auto-allows file edits inside $GITHUB_WORKSPACE and denies writes outside (e.g. ~/.bashrc).
// Listing them here would grant blanket write access to the whole runner (Asana 1213310082312048).
// Build claude_args for tag mode with required tools
// Tag mode REQUIRES these tools to function properly
const tagModeTools = [
"Edit",
"MultiEdit",
"Glob",
"Grep",
"LS",
"Read",
"Write",
"mcp__github_comment__update_claude_comment",
"mcp__github_ci__get_ci_status",
"mcp__github_ci__get_workflow_run_details",
@@ -138,7 +137,10 @@ export async function prepareTagMode({
tagModeTools.push(
"Bash(git add:*)",
"Bash(git commit:*)",
`Bash(${gitPushWrapper}:*)`,
"Bash(git push:*)",
"Bash(git status:*)",
"Bash(git diff:*)",
"Bash(git log:*)",
"Bash(git rm:*)",
);
} else {
@@ -169,10 +171,8 @@ export async function prepareTagMode({
const escapedOurConfig = ourMcpConfig.replace(/'/g, "'\\''");
claudeArgs = `--mcp-config '${escapedOurConfig}'`;
// Add required tools for tag mode.
// acceptEdits: file edits auto-allowed inside cwd ($GITHUB_WORKSPACE), denied outside.
// Headless SDK has no prompt handler, so anything that falls through to "ask" is denied.
claudeArgs += ` --permission-mode acceptEdits --allowedTools "${tagModeTools.join(",")}"`;
// Add required tools for tag mode
claudeArgs += ` --allowedTools "${tagModeTools.join(",")}"`;
// Append user's claude_args (which may have more --mcp-config flags)
if (userClaudeArgs) {
+21 -23
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env bun
import { describe, test, expect, beforeAll } from "bun:test";
import { describe, test, expect } from "bun:test";
import {
generatePrompt,
getEventTypeAndContext,
@@ -9,10 +9,6 @@ import {
} from "../src/create-prompt";
import type { PreparedContext } from "../src/create-prompt";
beforeAll(() => {
process.env.GITHUB_ACTION_PATH = "/test/action/path";
});
describe("generatePrompt", () => {
const mockGitHubData = {
contextData: {
@@ -509,7 +505,7 @@ describe("generatePrompt", () => {
const prompt = await generatePrompt(envVars, mockGitHubData, false, "tag");
// Should contain PR-specific instructions (git commands when not using signing)
expect(prompt).toContain("scripts/git-push.sh origin");
expect(prompt).toContain("git push");
expect(prompt).toContain(
"Always push to the existing branch when triggered on a PR",
);
@@ -647,7 +643,7 @@ describe("generatePrompt", () => {
const prompt = await generatePrompt(envVars, mockGitHubData, false, "tag");
// Should contain open PR instructions (git commands when not using signing)
expect(prompt).toContain("scripts/git-push.sh origin");
expect(prompt).toContain("git push");
expect(prompt).toContain(
"Always push to the existing branch when triggered on a PR",
);
@@ -761,7 +757,7 @@ describe("generatePrompt", () => {
expect(prompt).toContain("Use git commands via the Bash tool");
expect(prompt).toContain("git add");
expect(prompt).toContain("git commit");
expect(prompt).toContain("scripts/git-push.sh origin");
expect(prompt).toContain("git push");
// Should use the minimal comment tool
expect(prompt).toContain("mcp__github_comment__update_claude_comment");
@@ -890,18 +886,17 @@ describe("buildAllowedToolsString", () => {
const result = buildAllowedToolsString();
// The base tools should be in the result
// Edit/MultiEdit/Write are NOT in allowedTools — acceptEdits permission mode handles them
expect(result).not.toContain("Edit");
expect(result).not.toContain("Write");
expect(result).toContain("Edit");
expect(result).toContain("Glob");
expect(result).toContain("Grep");
expect(result).toContain("LS");
expect(result).toContain("Read");
expect(result).toContain("Write");
// Default is no commit signing, so should have specific Bash git commands
expect(result).toContain("Bash(git add:*)");
expect(result).toContain("Bash(git commit:*)");
expect(result).toContain("scripts/git-push.sh:*)");
expect(result).toContain("Bash(git push:*)");
expect(result).toContain("mcp__github_comment__update_claude_comment");
// Should not have commit signing tools
@@ -913,12 +908,12 @@ describe("buildAllowedToolsString", () => {
const result = buildAllowedToolsString([], false, false);
// The base tools should be in the result
expect(result).not.toContain("Edit");
expect(result).toContain("Edit");
expect(result).toContain("Glob");
expect(result).toContain("Grep");
expect(result).toContain("LS");
expect(result).toContain("Read");
expect(result).not.toContain("Write");
expect(result).toContain("Write");
// Should have specific Bash git commands for non-signing mode
expect(result).toContain("Bash(git add:*)");
@@ -935,7 +930,7 @@ describe("buildAllowedToolsString", () => {
const result = buildAllowedToolsString(customTools);
// Base tools should be present
expect(result).toContain("Read");
expect(result).toContain("Edit");
expect(result).toContain("Glob");
// Custom tools should be appended
@@ -955,7 +950,7 @@ describe("buildAllowedToolsString", () => {
const result = buildAllowedToolsString([], true);
// Base tools should be present
expect(result).toContain("Read");
expect(result).toContain("Edit");
expect(result).toContain("Glob");
// GitHub Actions tools should be included
@@ -969,7 +964,7 @@ describe("buildAllowedToolsString", () => {
const result = buildAllowedToolsString(customTools, true);
// Base tools should be present
expect(result).toContain("Read");
expect(result).toContain("Edit");
// Custom tools should be included
expect(result).toContain("Tool1");
@@ -985,12 +980,12 @@ describe("buildAllowedToolsString", () => {
const result = buildAllowedToolsString([], false, true);
// Base tools should be present
expect(result).not.toContain("Edit");
expect(result).toContain("Edit");
expect(result).toContain("Glob");
expect(result).toContain("Grep");
expect(result).toContain("LS");
expect(result).toContain("Read");
expect(result).not.toContain("Write");
expect(result).toContain("Write");
// Commit signing tools should be included
expect(result).toContain("mcp__github_file_ops__commit_files");
@@ -1006,17 +1001,20 @@ describe("buildAllowedToolsString", () => {
const result = buildAllowedToolsString([], false, false);
// Base tools should be present
expect(result).not.toContain("Edit");
expect(result).toContain("Edit");
expect(result).toContain("Glob");
expect(result).toContain("Grep");
expect(result).toContain("LS");
expect(result).toContain("Read");
expect(result).not.toContain("Write");
expect(result).toContain("Write");
// Specific Bash git commands should be included
expect(result).toContain("Bash(git add:*)");
expect(result).toContain("Bash(git commit:*)");
expect(result).toContain("scripts/git-push.sh:*)");
expect(result).toContain("Bash(git push:*)");
expect(result).toContain("Bash(git status:*)");
expect(result).toContain("Bash(git diff:*)");
expect(result).toContain("Bash(git log:*)");
expect(result).toContain("Bash(git rm:*)");
// Comment tool from minimal server should be included
@@ -1032,7 +1030,7 @@ describe("buildAllowedToolsString", () => {
const result = buildAllowedToolsString(customTools, true, false);
// Base tools should be present
expect(result).toContain("Read");
expect(result).toContain("Edit");
expect(result).toContain("Bash(git add:*)");
// Custom tools should be included
-1
View File
@@ -32,7 +32,6 @@ describe("prepareMcpConfig", () => {
labelTrigger: "",
branchPrefix: "",
useStickyComment: false,
classifyInlineComments: true,
useCommitSigning: false,
sshSigningKey: "",
botId: String(CLAUDE_APP_BOT_ID),
-1
View File
@@ -19,7 +19,6 @@ const defaultInputs = {
labelTrigger: "",
branchPrefix: "claude/",
useStickyComment: false,
classifyInlineComments: true,
useCommitSigning: false,
sshSigningKey: "",
botId: String(CLAUDE_APP_BOT_ID),
-1
View File
@@ -19,7 +19,6 @@ describe("detectMode with enhanced routing", () => {
labelTrigger: "",
branchPrefix: "claude/",
useStickyComment: false,
classifyInlineComments: true,
useCommitSigning: false,
sshSigningKey: "",
botId: "123456",
-1
View File
@@ -67,7 +67,6 @@ describe("checkWritePermissions", () => {
labelTrigger: "",
branchPrefix: "claude/",
useStickyComment: false,
classifyInlineComments: true,
useCommitSigning: false,
sshSigningKey: "",
botId: String(CLAUDE_APP_BOT_ID),
+1 -1
View File
@@ -135,7 +135,7 @@ describe("pull_request_target event support", () => {
const prompt = generatePrompt(envVars, mockGitHubData, false, "tag");
// Should include git commands for non-commit-signing mode
expect(prompt).toContain("scripts/git-push.sh origin");
expect(prompt).toContain("git push");
expect(prompt).toContain(
"Always push to the existing branch when triggered on a PR",
);
-5
View File
@@ -34,7 +34,6 @@ describe("checkContainsTrigger", () => {
labelTrigger: "",
branchPrefix: "claude/",
useStickyComment: false,
classifyInlineComments: true,
useCommitSigning: false,
allowedBots: "",
},
@@ -63,7 +62,6 @@ describe("checkContainsTrigger", () => {
labelTrigger: "",
branchPrefix: "claude/",
useStickyComment: false,
classifyInlineComments: true,
useCommitSigning: false,
allowedBots: "",
},
@@ -276,7 +274,6 @@ describe("checkContainsTrigger", () => {
labelTrigger: "",
branchPrefix: "claude/",
useStickyComment: false,
classifyInlineComments: true,
useCommitSigning: false,
allowedBots: "",
},
@@ -306,7 +303,6 @@ describe("checkContainsTrigger", () => {
labelTrigger: "",
branchPrefix: "claude/",
useStickyComment: false,
classifyInlineComments: true,
useCommitSigning: false,
allowedBots: "",
},
@@ -336,7 +332,6 @@ describe("checkContainsTrigger", () => {
labelTrigger: "",
branchPrefix: "claude/",
useStickyComment: false,
classifyInlineComments: true,
useCommitSigning: false,
allowedBots: "",
},