Merge pull request 'chore: merge 7 upstream commits from anthropics/claude-code-action' (#54) from chore/upstream-sync-20260316 into main

Reviewed-on: https://git.wxs.ro/actions/claude-code-action/pulls/54
This commit is contained in:
Vlad Mocanu committed 2026-03-16 08:33:00 +00:00
commit c8a67b6d5e
25 files changed
+428 -57

No files matched your search

+18 -1
View File
@@ -28,7 +28,7 @@ inputs:
required: false required: false
default: "" default: ""
allowed_bots: allowed_bots:
description: "Comma-separated list of allowed bot usernames, or '*' to allow all bots. Empty string (default) allows no 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."
required: false required: false
default: "" default: ""
allowed_non_write_users: allowed_non_write_users:
@@ -97,6 +97,10 @@ inputs:
description: "Use just one comment to deliver issue/PR comments" description: "Use just one comment to deliver issue/PR comments"
required: false required: false
default: "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: use_commit_signing:
description: "Enable commit signing using GitHub's commit signature verification. When false, Claude uses standard git commands" description: "Enable commit signing using GitHub's commit signature verification. When false, Claude uses standard git commands"
required: false required: false
@@ -252,6 +256,7 @@ runs:
EXCLUDE_COMMENTS_BY_ACTOR: ${{ inputs.exclude_comments_by_actor }} EXCLUDE_COMMENTS_BY_ACTOR: ${{ inputs.exclude_comments_by_actor }}
GITHUB_RUN_ID: ${{ github.run_id }} GITHUB_RUN_ID: ${{ github.run_id }}
USE_STICKY_COMMENT: ${{ inputs.use_sticky_comment }} USE_STICKY_COMMENT: ${{ inputs.use_sticky_comment }}
CLASSIFY_INLINE_COMMENTS: ${{ inputs.classify_inline_comments }}
DEFAULT_WORKFLOW_TOKEN: ${{ github.token }} DEFAULT_WORKFLOW_TOKEN: ${{ github.token }}
USE_COMMIT_SIGNING: ${{ inputs.use_commit_signing }} USE_COMMIT_SIGNING: ${{ inputs.use_commit_signing }}
SSH_SIGNING_KEY: ${{ inputs.ssh_signing_key }} SSH_SIGNING_KEY: ${{ inputs.ssh_signing_key }}
@@ -334,6 +339,18 @@ runs:
run: | run: |
bun run ${GITHUB_ACTION_PATH}/src/entrypoints/cleanup-ssh-signing.ts 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 - name: Revoke app token
if: always() && inputs.github_token == '' && steps.run.outputs.skipped_due_to_workflow_validation_mismatch != 'true' && steps.detect-platform.outputs.platform != 'forgejo' if: always() && inputs.github_token == '' && steps.run.outputs.skipped_due_to_workflow_validation_mismatch != 'true' && steps.detect-platform.outputs.platform != 'forgejo'
shell: bash shell: bash
+1 -1
View File
@@ -124,7 +124,7 @@ runs:
PATH_TO_CLAUDE_CODE_EXECUTABLE: ${{ inputs.path_to_claude_code_executable }} PATH_TO_CLAUDE_CODE_EXECUTABLE: ${{ inputs.path_to_claude_code_executable }}
run: | run: |
if [ -z "$PATH_TO_CLAUDE_CODE_EXECUTABLE" ]; then if [ -z "$PATH_TO_CLAUDE_CODE_EXECUTABLE" ]; then
CLAUDE_CODE_VERSION="2.1.71" CLAUDE_CODE_VERSION="2.1.76"
echo "Installing Claude Code v${CLAUDE_CODE_VERSION}..." echo "Installing Claude Code v${CLAUDE_CODE_VERSION}..."
for attempt in 1 2 3; do for attempt in 1 2 3; do
echo "Installation attempt $attempt..." echo "Installation attempt $attempt..."
+2 -2
View File
@@ -6,7 +6,7 @@
"name": "@anthropic-ai/claude-code-base-action", "name": "@anthropic-ai/claude-code-base-action",
"dependencies": { "dependencies": {
"@actions/core": "^1.10.1", "@actions/core": "^1.10.1",
"@anthropic-ai/claude-agent-sdk": "^0.2.71", "@anthropic-ai/claude-agent-sdk": "^0.2.76",
"shell-quote": "^1.8.3", "shell-quote": "^1.8.3",
}, },
"devDependencies": { "devDependencies": {
@@ -27,7 +27,7 @@
"@actions/io": ["@actions/io@1.1.3", "", {}, "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="], "@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.71", "", { "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-pIsQJnM7Y+cJHL7aFY6SCCW3FIni218gVEpPqG8XGowfYxboFNBbNssWiUNRwthT8bp9jypcX7q5kx0Xsw14xg=="], "@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.2.76", "", { "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-HZxvnT8ZWkzCnQygaYCA0dl8RSUzuVbxE1YG4ecy6vh4nQbTT36CxUxBy+QVdR12pPQluncC0mCOLhI2918Eaw=="],
"@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="], "@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="],
+1 -1
View File
@@ -11,7 +11,7 @@
}, },
"dependencies": { "dependencies": {
"@actions/core": "^1.10.1", "@actions/core": "^1.10.1",
"@anthropic-ai/claude-agent-sdk": "^0.2.71", "@anthropic-ai/claude-agent-sdk": "^0.2.76",
"shell-quote": "^1.8.3" "shell-quote": "^1.8.3"
}, },
"devDependencies": { "devDependencies": {
+2 -2
View File
@@ -7,7 +7,7 @@
"dependencies": { "dependencies": {
"@actions/core": "^1.10.1", "@actions/core": "^1.10.1",
"@actions/github": "^6.0.1", "@actions/github": "^6.0.1",
"@anthropic-ai/claude-agent-sdk": "^0.2.71", "@anthropic-ai/claude-agent-sdk": "^0.2.76",
"@modelcontextprotocol/sdk": "^1.11.0", "@modelcontextprotocol/sdk": "^1.11.0",
"@octokit/graphql": "^8.2.2", "@octokit/graphql": "^8.2.2",
"@octokit/rest": "^21.1.1", "@octokit/rest": "^21.1.1",
@@ -37,7 +37,7 @@
"@actions/io": ["@actions/io@1.1.3", "", {}, "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="], "@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.71", "", { "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-pIsQJnM7Y+cJHL7aFY6SCCW3FIni218gVEpPqG8XGowfYxboFNBbNssWiUNRwthT8bp9jypcX7q5kx0Xsw14xg=="], "@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.2.76", "", { "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-HZxvnT8ZWkzCnQygaYCA0dl8RSUzuVbxE1YG4ecy6vh4nQbTT36CxUxBy+QVdR12pPQluncC0mCOLhI2918Eaw=="],
"@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="], "@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="],
+4
View File
@@ -4,6 +4,10 @@
- **Repository Access**: The action can only be triggered by users with write access to the repository - **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 - **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: - **⚠️ 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) - 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 - Accepts either a comma-separated list of specific usernames or `*` to allow all users
+2 -2
View File
@@ -55,7 +55,7 @@ jobs:
Note: The PR branch is already checked out in the current working directory. Note: The PR branch is already checked out in the current working directory.
Use `gh pr comment` for top-level feedback. Use `gh pr comment` for top-level feedback.
Use `mcp__github_inline_comment__create_inline_comment` to highlight specific code issues. Use `mcp__github_inline_comment__create_inline_comment` (with `confirmed: true`) to highlight specific code issues.
Only post GitHub comments - don't submit review text as messages. Only post GitHub comments - don't submit review text as messages.
claude_args: | claude_args: |
@@ -585,7 +585,7 @@ prompt: |
### Common Tool Permissions ### Common Tool Permissions
- **PR Comments**: `Bash(gh pr comment:*)` - **PR Comments**: `Bash(gh pr comment:*)`
- **Inline Comments**: `mcp__github_inline_comment__create_inline_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.
- **File Operations**: `Read,Write,Edit` - **File Operations**: `Read,Write,Edit`
- **Git Operations**: `Bash(git:*)` - **Git Operations**: `Bash(git:*)`
+3 -2
View File
@@ -53,7 +53,7 @@ jobs:
## Inputs ## Inputs
| Input | Description | Required | Default | | Input | Description | Required | Default |
| -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------- | | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------- |
| `anthropic_api_key` | Anthropic API key (required for direct API, not needed for Bedrock/Vertex) | No\* | - | | `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\* | - | | `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 | - | | `prompt` | Instructions for Claude. Can be a direct prompt or custom template for automation workflows | No | - |
@@ -62,6 +62,7 @@ jobs:
| `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 | "" | | `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 | - | | `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` | | `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 | - | | `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_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` | | `use_vertex` | Use Google Vertex AI with OIDC authentication instead of direct Anthropic API | No | `false` |
@@ -75,7 +76,7 @@ jobs:
| `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 | "" | | `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_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]` | | `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_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 | "" | | `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_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 | "" | | `path_to_bun_executable` | Optional path to a custom Bun executable. Skips automatic Bun installation. Useful for Nix, custom containers, or specialized environments | No | "" |
+1 -1
View File
@@ -12,7 +12,7 @@
"dependencies": { "dependencies": {
"@actions/core": "^1.10.1", "@actions/core": "^1.10.1",
"@actions/github": "^6.0.1", "@actions/github": "^6.0.1",
"@anthropic-ai/claude-agent-sdk": "^0.2.71", "@anthropic-ai/claude-agent-sdk": "^0.2.76",
"@modelcontextprotocol/sdk": "^1.11.0", "@modelcontextprotocol/sdk": "^1.11.0",
"@octokit/graphql": "^8.2.2", "@octokit/graphql": "^8.2.2",
"@octokit/rest": "^21.1.1", "@octokit/rest": "^21.1.1",
+36
View File
@@ -0,0 +1,36 @@
#!/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"
+10 -14
View File
@@ -26,19 +26,15 @@ import { extractUserRequest } from "../utils/extract-user-request";
import { parseToolsInput } from "../utils/parse-tools-input"; import { parseToolsInput } from "../utils/parse-tools-input";
export type { CommonFields, PreparedContext } from "./types"; 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 */ /** Filename for the user request file, read by the SDK runner */
const USER_REQUEST_FILENAME = "claude-user-request.txt"; const USER_REQUEST_FILENAME = "claude-user-request.txt";
// Tag mode defaults - these tools are needed for tag mode to function // Tag mode defaults - these tools are needed for tag mode to function.
const BASE_ALLOWED_TOOLS = [ // Edit/MultiEdit/Write are intentionally omitted: acceptEdits permission mode
"Edit", // auto-allows file edits inside $GITHUB_WORKSPACE and denies writes outside it.
"MultiEdit", const BASE_ALLOWED_TOOLS = ["Glob", "Grep", "LS", "Read"];
"Glob",
"Grep",
"LS",
"Read",
"Write",
];
export function buildAllowedToolsString( export function buildAllowedToolsString(
customAllowedTools?: string[], customAllowedTools?: string[],
@@ -62,7 +58,7 @@ export function buildAllowedToolsString(
baseTools.push( baseTools.push(
"Bash(git add:*)", "Bash(git add:*)",
"Bash(git commit:*)", "Bash(git commit:*)",
"Bash(git push:*)", `Bash(${GIT_PUSH_WRAPPER}:*)`,
"Bash(git status:*)", "Bash(git status:*)",
"Bash(git diff:*)", "Bash(git diff:*)",
"Bash(git log:*)", "Bash(git log:*)",
@@ -443,7 +439,7 @@ function getCommitInstructions(
Bash(git commit -m "<message>\\n\\n${coAuthorLine}")` Bash(git commit -m "<message>\\n\\n${coAuthorLine}")`
: "" : ""
} }
- Push to the remote: Bash(git push origin HEAD)`; - Push to the remote: Bash(${GIT_PUSH_WRAPPER} origin HEAD)`;
} else { } else {
const branchName = eventData.claudeBranch || eventData.baseBranch; const branchName = eventData.claudeBranch || eventData.baseBranch;
return ` return `
@@ -457,7 +453,7 @@ function getCommitInstructions(
Bash(git commit -m "<message>\\n\\n${coAuthorLine}")` Bash(git commit -m "<message>\\n\\n${coAuthorLine}")`
: "" : ""
} }
- Push to the remote: Bash(git push origin ${branchName})`; - Push to the remote: Bash(${GIT_PUSH_WRAPPER} origin ${branchName})`;
} }
} }
} }
@@ -840,7 +836,7 @@ ${
: `- Use git commands via the Bash tool for version control (remember that you have access to these git commands): : `- 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>) - Stage files: Bash(git add <files>)
- Commit changes: Bash(git commit -m "<message>") - Commit changes: Bash(git commit -m "<message>")
- Push to remote: Bash(git push origin <branch>) (NEVER force push) - Push to remote: Bash(${GIT_PUSH_WRAPPER} origin <branch>)
- Delete files: Bash(git rm <files>) followed by commit and push - Delete files: Bash(git rm <files>) followed by commit and push
- Check status: Bash(git status) - 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)` : ""}` - View diff: Bash(git diff)${eventData.isPR && eventData.baseBranch ? `\n - IMPORTANT: For PR diffs, use: Bash(git diff origin/${eventData.baseBranch}...HEAD)` : ""}`
+1
View File
@@ -23,6 +23,7 @@ export function collectActionInputsPresence(): string {
github_token: "", github_token: "",
max_turns: "", max_turns: "",
use_sticky_comment: "false", use_sticky_comment: "false",
classify_inline_comments: "true",
use_commit_signing: "false", use_commit_signing: "false",
ssh_signing_key: "", ssh_signing_key: "",
}; };
@@ -0,0 +1,240 @@
#!/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)
.flatMap((line) => {
try {
return [JSON.parse(line) as BufferedComment];
} catch {
console.log(`::warning::Skipping malformed JSONL line: ${line.slice(0, 120)}`);
return [];
}
});
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);
});
+1 -1
View File
@@ -54,7 +54,7 @@ async function installClaudeCode(): Promise<void> {
return; return;
} }
const claudeCodeVersion = "2.1.71"; const claudeCodeVersion = "2.1.76";
console.log(`Installing Claude Code v${claudeCodeVersion}...`); console.log(`Installing Claude Code v${claudeCodeVersion}...`);
// On musl/Alpine systems, the native binary may require a newer musl than // On musl/Alpine systems, the native binary may require a newer musl than
+2
View File
@@ -115,6 +115,7 @@ type BaseContext = {
branchPrefix: string; branchPrefix: string;
branchNameTemplate?: string; branchNameTemplate?: string;
useStickyComment: boolean; useStickyComment: boolean;
classifyInlineComments: boolean;
useCommitSigning: boolean; useCommitSigning: boolean;
sshSigningKey: string; sshSigningKey: string;
botId: string; botId: string;
@@ -178,6 +179,7 @@ export function parseGitHubContext(): GitHubContext {
branchPrefix: process.env.BRANCH_PREFIX ?? "claude/", branchPrefix: process.env.BRANCH_PREFIX ?? "claude/",
branchNameTemplate: process.env.BRANCH_NAME_TEMPLATE, branchNameTemplate: process.env.BRANCH_NAME_TEMPLATE,
useStickyComment: process.env.USE_STICKY_COMMENT === "true", useStickyComment: process.env.USE_STICKY_COMMENT === "true",
classifyInlineComments: process.env.CLASSIFY_INLINE_COMMENTS !== "false",
useCommitSigning: process.env.USE_COMMIT_SIGNING === "true", useCommitSigning: process.env.USE_COMMIT_SIGNING === "true",
sshSigningKey: process.env.SSH_SIGNING_KEY || "", sshSigningKey: process.env.SSH_SIGNING_KEY || "",
botId: process.env.BOT_ID ?? String(CLAUDE_APP_BOT_ID), botId: process.env.BOT_ID ?? String(CLAUDE_APP_BOT_ID),
+57 -3
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env node #!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { appendFileSync } from "fs";
import { z } from "zod"; import { z } from "zod";
import { createOctokit } from "../github/api/client"; import { createOctokit } from "../github/api/client";
import { sanitizeContent } from "../github/utils/sanitizer"; import { sanitizeContent } from "../github/utils/sanitizer";
@@ -10,6 +11,13 @@ const REPO_OWNER = process.env.REPO_OWNER;
const REPO_NAME = process.env.REPO_NAME; const REPO_NAME = process.env.REPO_NAME;
const PR_NUMBER = process.env.PR_NUMBER; 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) { if (!REPO_OWNER || !REPO_NAME || !PR_NUMBER) {
console.error( console.error(
"Error: REPO_OWNER, REPO_NAME, and PR_NUMBER environment variables are required", "Error: REPO_OWNER, REPO_NAME, and PR_NUMBER environment variables are required",
@@ -67,8 +75,17 @@ server.tool(
.describe( .describe(
"Specific commit SHA to comment on (defaults to latest commit)", "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 }) => { async ({ path, body, line, startLine, side, commit_id, confirmed }) => {
try { try {
const githubToken = process.env.GITHUB_TOKEN; const githubToken = process.env.GITHUB_TOKEN;
@@ -80,8 +97,6 @@ server.tool(
const repo = REPO_NAME; const repo = REPO_NAME;
const pull_number = parseInt(PR_NUMBER, 10); const pull_number = parseInt(PR_NUMBER, 10);
const octokit = createOctokit(githubToken).rest;
// Sanitize the comment body to remove any potential GitHub tokens // Sanitize the comment body to remove any potential GitHub tokens
const sanitizedBody = sanitizeContent(body); const sanitizedBody = sanitizeContent(body);
@@ -92,10 +107,49 @@ 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 only line is provided, it's a single-line comment
// If both startLine and line are provided, it's a multi-line comment // If both startLine and line are provided, it's a multi-line comment
const isSingleLine = !startLine; const isSingleLine = !startLine;
const octokit = createOctokit(githubToken).rest;
const pr = await octokit.pulls.get({ const pr = await octokit.pulls.get({
owner, owner,
repo, repo,
+3
View File
@@ -157,6 +157,9 @@ export async function prepareMcpConfig(
REPO_NAME: repo, REPO_NAME: repo,
PR_NUMBER: context.entityNumber?.toString() || "", PR_NUMBER: context.entityNumber?.toString() || "",
GITHUB_API_URL: GITHUB_API_URL, GITHUB_API_URL: GITHUB_API_URL,
CLASSIFY_INLINE_COMMENTS: context.inputs.classifyInlineComments
? "true"
: "false",
}, },
}; };
} }
+11 -8
View File
@@ -128,16 +128,17 @@ export async function prepareTagMode({
// Merge user-provided allowed_tools input (fixes upstream #181, #287, #724) // Merge user-provided allowed_tools input (fixes upstream #181, #287, #724)
const userAllowedTools = parseToolsInput(process.env.ALLOWED_TOOLS_INPUT); const userAllowedTools = parseToolsInput(process.env.ALLOWED_TOOLS_INPUT);
// Build claude_args for tag mode with required tools const gitPushWrapper = `${process.env.GITHUB_ACTION_PATH}/scripts/git-push.sh`;
// Tag mode REQUIRES these tools to function properly
// 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).
const tagModeTools = [ const tagModeTools = [
"Edit",
"MultiEdit",
"Glob", "Glob",
"Grep", "Grep",
"LS", "LS",
"Read", "Read",
"Write",
"mcp__github_comment__update_claude_comment", "mcp__github_comment__update_claude_comment",
"mcp__github_ci__get_ci_status", "mcp__github_ci__get_ci_status",
"mcp__github_ci__get_workflow_run_details", "mcp__github_ci__get_workflow_run_details",
@@ -152,7 +153,7 @@ export async function prepareTagMode({
tagModeTools.push( tagModeTools.push(
"Bash(git add:*)", "Bash(git add:*)",
"Bash(git commit:*)", "Bash(git commit:*)",
"Bash(git push:*)", `Bash(${gitPushWrapper}:*)`,
"Bash(git status:*)", "Bash(git status:*)",
"Bash(git diff:*)", "Bash(git diff:*)",
"Bash(git log:*)", "Bash(git log:*)",
@@ -186,8 +187,10 @@ export async function prepareTagMode({
const escapedOurConfig = ourMcpConfig.replace(/'/g, "'\\''"); const escapedOurConfig = ourMcpConfig.replace(/'/g, "'\\''");
claudeArgs = `--mcp-config '${escapedOurConfig}'`; claudeArgs = `--mcp-config '${escapedOurConfig}'`;
// Add required tools for tag mode // Add required tools for tag mode.
claudeArgs += ` --allowedTools "${tagModeTools.join(",")}"`; // 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(",")}"`;
// Append user's claude_args (which may have more --mcp-config flags) // Append user's claude_args (which may have more --mcp-config flags)
if (userClaudeArgs) { if (userClaudeArgs) {
+23 -18
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env bun #!/usr/bin/env bun
import { describe, test, expect } from "bun:test"; import { describe, test, expect, beforeAll } from "bun:test";
import { import {
generatePrompt, generatePrompt,
getEventTypeAndContext, getEventTypeAndContext,
@@ -9,6 +9,10 @@ import {
} from "../src/create-prompt"; } from "../src/create-prompt";
import type { PreparedContext } from "../src/create-prompt"; import type { PreparedContext } from "../src/create-prompt";
beforeAll(() => {
process.env.GITHUB_ACTION_PATH = "/test/action/path";
});
describe("generatePrompt", () => { describe("generatePrompt", () => {
const mockGitHubData = { const mockGitHubData = {
contextData: { contextData: {
@@ -505,7 +509,7 @@ describe("generatePrompt", () => {
const prompt = await generatePrompt(envVars, mockGitHubData, false, "tag"); const prompt = await generatePrompt(envVars, mockGitHubData, false, "tag");
// Should contain PR-specific instructions (git commands when not using signing) // Should contain PR-specific instructions (git commands when not using signing)
expect(prompt).toContain("git push"); expect(prompt).toContain("scripts/git-push.sh origin");
expect(prompt).toContain( expect(prompt).toContain(
"Always push to the existing branch when triggered on a PR", "Always push to the existing branch when triggered on a PR",
); );
@@ -643,7 +647,7 @@ describe("generatePrompt", () => {
const prompt = await generatePrompt(envVars, mockGitHubData, false, "tag"); const prompt = await generatePrompt(envVars, mockGitHubData, false, "tag");
// Should contain open PR instructions (git commands when not using signing) // Should contain open PR instructions (git commands when not using signing)
expect(prompt).toContain("git push"); expect(prompt).toContain("scripts/git-push.sh origin");
expect(prompt).toContain( expect(prompt).toContain(
"Always push to the existing branch when triggered on a PR", "Always push to the existing branch when triggered on a PR",
); );
@@ -757,7 +761,7 @@ describe("generatePrompt", () => {
expect(prompt).toContain("Use git commands via the Bash tool"); expect(prompt).toContain("Use git commands via the Bash tool");
expect(prompt).toContain("git add"); expect(prompt).toContain("git add");
expect(prompt).toContain("git commit"); expect(prompt).toContain("git commit");
expect(prompt).toContain("git push"); expect(prompt).toContain("scripts/git-push.sh origin");
// Should use the minimal comment tool // Should use the minimal comment tool
expect(prompt).toContain("mcp__github_comment__update_claude_comment"); expect(prompt).toContain("mcp__github_comment__update_claude_comment");
@@ -886,17 +890,18 @@ describe("buildAllowedToolsString", () => {
const result = buildAllowedToolsString(); const result = buildAllowedToolsString();
// The base tools should be in the result // The base tools should be in the result
expect(result).toContain("Edit"); // 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("Glob"); expect(result).toContain("Glob");
expect(result).toContain("Grep"); expect(result).toContain("Grep");
expect(result).toContain("LS"); expect(result).toContain("LS");
expect(result).toContain("Read"); expect(result).toContain("Read");
expect(result).toContain("Write");
// Default is no commit signing, so should have specific Bash git commands // Default is no commit signing, so should have specific Bash git commands
expect(result).toContain("Bash(git add:*)"); expect(result).toContain("Bash(git add:*)");
expect(result).toContain("Bash(git commit:*)"); expect(result).toContain("Bash(git commit:*)");
expect(result).toContain("Bash(git push:*)"); expect(result).toContain("scripts/git-push.sh:*)");
expect(result).toContain("mcp__github_comment__update_claude_comment"); expect(result).toContain("mcp__github_comment__update_claude_comment");
// Should not have commit signing tools // Should not have commit signing tools
@@ -908,12 +913,12 @@ describe("buildAllowedToolsString", () => {
const result = buildAllowedToolsString([], false, false); const result = buildAllowedToolsString([], false, false);
// The base tools should be in the result // The base tools should be in the result
expect(result).toContain("Edit"); expect(result).not.toContain("Edit");
expect(result).toContain("Glob"); expect(result).toContain("Glob");
expect(result).toContain("Grep"); expect(result).toContain("Grep");
expect(result).toContain("LS"); expect(result).toContain("LS");
expect(result).toContain("Read"); expect(result).toContain("Read");
expect(result).toContain("Write"); expect(result).not.toContain("Write");
// Should have specific Bash git commands for non-signing mode // Should have specific Bash git commands for non-signing mode
expect(result).toContain("Bash(git add:*)"); expect(result).toContain("Bash(git add:*)");
@@ -930,7 +935,7 @@ describe("buildAllowedToolsString", () => {
const result = buildAllowedToolsString(customTools); const result = buildAllowedToolsString(customTools);
// Base tools should be present // Base tools should be present
expect(result).toContain("Edit"); expect(result).toContain("Read");
expect(result).toContain("Glob"); expect(result).toContain("Glob");
// Custom tools should be appended // Custom tools should be appended
@@ -950,7 +955,7 @@ describe("buildAllowedToolsString", () => {
const result = buildAllowedToolsString([], true); const result = buildAllowedToolsString([], true);
// Base tools should be present // Base tools should be present
expect(result).toContain("Edit"); expect(result).toContain("Read");
expect(result).toContain("Glob"); expect(result).toContain("Glob");
// GitHub Actions tools should be included // GitHub Actions tools should be included
@@ -964,7 +969,7 @@ describe("buildAllowedToolsString", () => {
const result = buildAllowedToolsString(customTools, true); const result = buildAllowedToolsString(customTools, true);
// Base tools should be present // Base tools should be present
expect(result).toContain("Edit"); expect(result).toContain("Read");
// Custom tools should be included // Custom tools should be included
expect(result).toContain("Tool1"); expect(result).toContain("Tool1");
@@ -980,12 +985,12 @@ describe("buildAllowedToolsString", () => {
const result = buildAllowedToolsString([], false, true); const result = buildAllowedToolsString([], false, true);
// Base tools should be present // Base tools should be present
expect(result).toContain("Edit"); expect(result).not.toContain("Edit");
expect(result).toContain("Glob"); expect(result).toContain("Glob");
expect(result).toContain("Grep"); expect(result).toContain("Grep");
expect(result).toContain("LS"); expect(result).toContain("LS");
expect(result).toContain("Read"); expect(result).toContain("Read");
expect(result).toContain("Write"); expect(result).not.toContain("Write");
// Commit signing tools should be included // Commit signing tools should be included
expect(result).toContain("mcp__github_file_ops__commit_files"); expect(result).toContain("mcp__github_file_ops__commit_files");
@@ -1001,17 +1006,17 @@ describe("buildAllowedToolsString", () => {
const result = buildAllowedToolsString([], false, false); const result = buildAllowedToolsString([], false, false);
// Base tools should be present // Base tools should be present
expect(result).toContain("Edit"); expect(result).not.toContain("Edit");
expect(result).toContain("Glob"); expect(result).toContain("Glob");
expect(result).toContain("Grep"); expect(result).toContain("Grep");
expect(result).toContain("LS"); expect(result).toContain("LS");
expect(result).toContain("Read"); expect(result).toContain("Read");
expect(result).toContain("Write"); expect(result).not.toContain("Write");
// Specific Bash git commands should be included // Specific Bash git commands should be included
expect(result).toContain("Bash(git add:*)"); expect(result).toContain("Bash(git add:*)");
expect(result).toContain("Bash(git commit:*)"); expect(result).toContain("Bash(git commit:*)");
expect(result).toContain("Bash(git push:*)"); expect(result).toContain("scripts/git-push.sh:*)");
expect(result).toContain("Bash(git status:*)"); expect(result).toContain("Bash(git status:*)");
expect(result).toContain("Bash(git diff:*)"); expect(result).toContain("Bash(git diff:*)");
expect(result).toContain("Bash(git log:*)"); expect(result).toContain("Bash(git log:*)");
@@ -1030,7 +1035,7 @@ describe("buildAllowedToolsString", () => {
const result = buildAllowedToolsString(customTools, true, false); const result = buildAllowedToolsString(customTools, true, false);
// Base tools should be present // Base tools should be present
expect(result).toContain("Edit"); expect(result).toContain("Read");
expect(result).toContain("Bash(git add:*)"); expect(result).toContain("Bash(git add:*)");
// Custom tools should be included // Custom tools should be included
+1
View File
@@ -33,6 +33,7 @@ describe("prepareMcpConfig", () => {
labelTrigger: "", labelTrigger: "",
branchPrefix: "", branchPrefix: "",
useStickyComment: false, useStickyComment: false,
classifyInlineComments: true,
useCommitSigning: false, useCommitSigning: false,
sshSigningKey: "", sshSigningKey: "",
botId: String(CLAUDE_APP_BOT_ID), botId: String(CLAUDE_APP_BOT_ID),
+1
View File
@@ -19,6 +19,7 @@ const defaultInputs = {
labelTrigger: "", labelTrigger: "",
branchPrefix: "claude/", branchPrefix: "claude/",
useStickyComment: false, useStickyComment: false,
classifyInlineComments: true,
useCommitSigning: false, useCommitSigning: false,
sshSigningKey: "", sshSigningKey: "",
botId: String(CLAUDE_APP_BOT_ID), botId: String(CLAUDE_APP_BOT_ID),
+1
View File
@@ -19,6 +19,7 @@ describe("detectMode with enhanced routing", () => {
labelTrigger: "", labelTrigger: "",
branchPrefix: "claude/", branchPrefix: "claude/",
useStickyComment: false, useStickyComment: false,
classifyInlineComments: true,
useCommitSigning: false, useCommitSigning: false,
sshSigningKey: "", sshSigningKey: "",
botId: "123456", botId: "123456",
+1
View File
@@ -68,6 +68,7 @@ describe("checkWritePermissions", () => {
labelTrigger: "", labelTrigger: "",
branchPrefix: "claude/", branchPrefix: "claude/",
useStickyComment: false, useStickyComment: false,
classifyInlineComments: true,
useCommitSigning: false, useCommitSigning: false,
sshSigningKey: "", sshSigningKey: "",
botId: String(CLAUDE_APP_BOT_ID), 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"); const prompt = generatePrompt(envVars, mockGitHubData, false, "tag");
// Should include git commands for non-commit-signing mode // Should include git commands for non-commit-signing mode
expect(prompt).toContain("git push"); expect(prompt).toContain("scripts/git-push.sh origin");
expect(prompt).toContain( expect(prompt).toContain(
"Always push to the existing branch when triggered on a PR", "Always push to the existing branch when triggered on a PR",
); );
+5
View File
@@ -35,6 +35,7 @@ describe("checkContainsTrigger", () => {
labelTrigger: "", labelTrigger: "",
branchPrefix: "claude/", branchPrefix: "claude/",
useStickyComment: false, useStickyComment: false,
classifyInlineComments: true,
useCommitSigning: false, useCommitSigning: false,
allowedBots: "", allowedBots: "",
}, },
@@ -63,6 +64,7 @@ describe("checkContainsTrigger", () => {
labelTrigger: "", labelTrigger: "",
branchPrefix: "claude/", branchPrefix: "claude/",
useStickyComment: false, useStickyComment: false,
classifyInlineComments: true,
useCommitSigning: false, useCommitSigning: false,
allowedBots: "", allowedBots: "",
}, },
@@ -275,6 +277,7 @@ describe("checkContainsTrigger", () => {
labelTrigger: "", labelTrigger: "",
branchPrefix: "claude/", branchPrefix: "claude/",
useStickyComment: false, useStickyComment: false,
classifyInlineComments: true,
useCommitSigning: false, useCommitSigning: false,
allowedBots: "", allowedBots: "",
}, },
@@ -304,6 +307,7 @@ describe("checkContainsTrigger", () => {
labelTrigger: "", labelTrigger: "",
branchPrefix: "claude/", branchPrefix: "claude/",
useStickyComment: false, useStickyComment: false,
classifyInlineComments: true,
useCommitSigning: false, useCommitSigning: false,
allowedBots: "", allowedBots: "",
}, },
@@ -333,6 +337,7 @@ describe("checkContainsTrigger", () => {
labelTrigger: "", labelTrigger: "",
branchPrefix: "claude/", branchPrefix: "claude/",
useStickyComment: false, useStickyComment: false,
classifyInlineComments: true,
useCommitSigning: false, useCommitSigning: false,
allowedBots: "", allowedBots: "",
}, },