Compare commits

..

8 Commits

Author SHA1 Message Date
Ashwin Bhat
142a77fa55 chore: bump Bun to 2.1.1 and setup-bun action to v2.1.2
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Generated-By: Claude Code (cli/claude=100%)
Claude-Steers: 6
Claude-Permission-Prompts: 2
Claude-Escapes: 1
2026-01-20 13:03:43 -08:00
Ashwin Bhat
ba60ef7ba2 Consolidate CI workflows into a single entry point (#836)
* refactor: consolidate CI workflows with ci-all.yml orchestrator

- Add ci-all.yml to orchestrate all CI workflows on push to main
- Update individual workflows to use workflow_call for reusability
- Remove redundant push triggers from individual test workflows
- Update release.yml to trigger on CI All workflow completion
- Auto-release on version bump commits after CI passes

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Generated-By: Claude Code (cli/claude-opus-4-5=100%)
Claude-Steers: 8
Claude-Permission-Prompts: 1
Claude-Escapes: 0

* address security review comments

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-20 11:58:13 -08:00
GitHub Actions
f3c892ca8d chore: bump Claude Code to 2.1.11 and Agent SDK to 0.2.11 2026-01-17 01:44:05 +00:00
Ashwin Bhat
6e896a06bb fix: ensure SSH signing key has trailing newline (#834)
ssh-keygen requires a trailing newline to parse private keys correctly.
Without it, git signing fails with the confusing error:
'Couldn't load public key: No such file or directory?'

This normalizes the key to always end with a newline before writing.
2026-01-16 14:44:22 -08:00
Ashwin Bhat
a017b830c0 chore: comment out release-base-action job in release workflow (#833)
Temporarily disable the release-base-action job that syncs releases
to the claude-code-base-action repository. The job checkout step
and subsequent tag/release creation steps are now commented out.


Claude-Generated-By: Claude Code (cli/claude-opus-4-5=100%)
Claude-Steers: 2
Claude-Permission-Prompts: 2
Claude-Escapes: 0

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-16 14:16:27 -08:00
GitHub Actions
75f52e56b2 chore: bump Claude Code to 2.1.9 and Agent SDK to 0.2.9 2026-01-16 02:18:38 +00:00
Ashwin Bhat
1bbc9e7ff7 fix: add checkHumanActor to agent mode (#826)
Fixes issue #641 where users were getting banned due to rapid successive
Claude runs triggered by the synchronize event.

Changes:
- Add checkHumanActor call to agent mode's prepare() method to reject
  bot-triggered workflows unless explicitly allowed via allowed_bots
- Update checkHumanActor to accept GitHubContext (union type) instead
  of just ParsedGitHubContext
- Add tests for bot rejection/allowance in agent mode

Claude-Generated-By: Claude Code (cli/claude-opus-4-5=100%)
Claude-Steers: 1
Claude-Permission-Prompts: 3
Claude-Escapes: 0
2026-01-15 10:28:46 -08:00
Ashwin Bhat
625ea1519c docs: clarify that Claude does not auto-create PRs by default (#824)
Add a new section to security.md explaining that in the default
configuration, Claude commits to a branch and provides a link for
the user to create the PR themselves, ensuring human oversight.


Claude-Generated-By: Claude Code (cli/claude-opus-4-5=100%)
Claude-Steers: 2
Claude-Permission-Prompts: 2
Claude-Escapes: 0

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-14 15:22:40 -08:00
21 changed files with 267 additions and 105 deletions

37
.github/workflows/ci-all.yml vendored Normal file
View File

@@ -0,0 +1,37 @@
# Orchestrates all CI workflows - runs on PRs, pushes to main, and manual dispatch
# Individual test workflows are called as reusable workflows
name: CI All
on:
push:
branches:
- main
pull_request:
workflow_dispatch:
permissions:
contents: read
jobs:
ci:
uses: ./.github/workflows/ci.yml
test-base-action:
uses: ./.github/workflows/test-base-action.yml
secrets: inherit # Required for ANTHROPIC_API_KEY
test-custom-executables:
uses: ./.github/workflows/test-custom-executables.yml
secrets: inherit
test-mcp-servers:
uses: ./.github/workflows/test-mcp-servers.yml
secrets: inherit
test-settings:
uses: ./.github/workflows/test-settings.yml
secrets: inherit
test-structured-output:
uses: ./.github/workflows/test-structured-output.yml
secrets: inherit

View File

@@ -1,9 +1,8 @@
name: CI
on:
push:
branches: [main]
pull_request:
workflow_call:
jobs:
test:

View File

@@ -8,10 +8,23 @@ on:
required: false
type: boolean
default: false
workflow_run:
workflows: ["CI All"]
types:
- completed
branches:
- main
jobs:
create-release:
runs-on: ubuntu-latest
# Run if: manual dispatch OR (CI All succeeded AND commit is a version bump)
if: |
github.event_name == 'workflow_dispatch' ||
(github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.head_branch == 'main' &&
github.event.workflow_run.event == 'push' &&
startsWith(github.event.workflow_run.head_commit.message, 'chore: bump Claude Code to'))
environment: production
permissions:
contents: write
@@ -84,7 +97,8 @@ jobs:
update-major-tag:
needs: create-release
if: ${{ !inputs.dry_run }}
# Skip for dry runs (workflow_run events are never dry runs)
if: github.event_name == 'workflow_run' || !inputs.dry_run
runs-on: ubuntu-latest
environment: production
permissions:
@@ -109,49 +123,48 @@ jobs:
echo "Updated $major_version tag to point to $next_version"
release-base-action:
needs: create-release
if: ${{ !inputs.dry_run }}
runs-on: ubuntu-latest
environment: production
steps:
- name: Checkout base-action repo
uses: actions/checkout@v5
with:
repository: anthropics/claude-code-base-action
token: ${{ secrets.CLAUDE_CODE_BASE_ACTION_PAT }}
fetch-depth: 0
- name: Create and push tag
run: |
set -e # Exit on any error
next_version="${{ needs.create-release.outputs.next_version }}"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
# Create the version tag
git tag -a "$next_version" -m "Release $next_version - synced from claude-code-action"
git push origin "$next_version"
# Update the v1 tag
git tag -fa v1 -m "Update v1 tag to ${next_version}"
git push origin v1 --force
# - name: Create GitHub release
# env:
# GH_TOKEN: ${{ secrets.CLAUDE_CODE_BASE_ACTION_PAT }}
# run: |
# next_version="${{ needs.create-release.outputs.next_version }}"
# # Create the release
# gh release create "$next_version" \
# --repo anthropics/claude-code-base-action \
# --title "$next_version" \
# --notes "Release $next_version - synced from anthropics/claude-code-action" \
# --latest=false
# # Update beta release to be latest
# gh release edit beta \
# --repo anthropics/claude-code-base-action \
# --latest
# release-base-action:
# needs: create-release
# if: ${{ !inputs.dry_run }}
# runs-on: ubuntu-latest
# environment: production
# steps:
# - name: Checkout base-action repo
# uses: actions/checkout@v5
# with:
# repository: anthropics/claude-code-base-action
# token: ${{ secrets.CLAUDE_CODE_BASE_ACTION_PAT }}
# fetch-depth: 0
#
# - name: Create and push tag
# run: |
# next_version="${{ needs.create-release.outputs.next_version }}"
#
# git config user.name "github-actions[bot]"
# git config user.email "github-actions[bot]@users.noreply.github.com"
#
# # Create the version tag
# git tag -a "$next_version" -m "Release $next_version - synced from claude-code-action"
# git push origin "$next_version"
#
# # Update the beta tag
# git tag -fa beta -m "Update beta tag to ${next_version}"
# git push origin beta --force
#
# - name: Create GitHub release
# env:
# GH_TOKEN: ${{ secrets.CLAUDE_CODE_BASE_ACTION_PAT }}
# run: |
# next_version="${{ needs.create-release.outputs.next_version }}"
#
# # Create the release
# gh release create "$next_version" \
# --repo anthropics/claude-code-base-action \
# --title "$next_version" \
# --notes "Release $next_version - synced from anthropics/claude-code-action" \
# --latest=false
#
# # Update beta release to be latest
# gh release edit beta \
# --repo anthropics/claude-code-base-action \
# --latest

View File

@@ -1,9 +1,6 @@
name: Test Claude Code Action
on:
push:
branches:
- main
pull_request:
workflow_dispatch:
inputs:
@@ -11,6 +8,7 @@ on:
description: "Test prompt for Claude"
required: false
default: "List the files in the current directory starting with 'package'"
workflow_call:
jobs:
test-inline-prompt:

View File

@@ -1,11 +1,9 @@
name: Test Custom Executables
on:
push:
branches:
- main
pull_request:
workflow_dispatch:
workflow_call:
jobs:
test-custom-executables:

View File

@@ -1,11 +1,9 @@
name: Test MCP Servers
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
workflow_call:
jobs:
test-mcp-integration:

View File

@@ -1,11 +1,9 @@
name: Test Settings Feature
on:
push:
branches:
- main
pull_request:
workflow_dispatch:
workflow_call:
jobs:
test-settings-inline-allow:

View File

@@ -1,11 +1,9 @@
name: Test Structured Outputs
on:
push:
branches:
- main
pull_request:
workflow_dispatch:
workflow_call:
permissions:
contents: read

View File

@@ -148,9 +148,9 @@ runs:
steps:
- name: Install Bun
if: inputs.path_to_bun_executable == ''
uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # https://github.com/oven-sh/setup-bun/releases/tag/v2.0.2
uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # https://github.com/oven-sh/setup-bun/releases/tag/v2.1.2
with:
bun-version: 1.2.11
bun-version: 2.1.1
- name: Setup Custom Bun Path
if: inputs.path_to_bun_executable != ''
@@ -213,7 +213,7 @@ runs:
# Install Claude Code if no custom executable is provided
if [ -z "$PATH_TO_CLAUDE_CODE_EXECUTABLE" ]; then
CLAUDE_CODE_VERSION="2.1.7"
CLAUDE_CODE_VERSION="2.1.11"
echo "Installing Claude Code v${CLAUDE_CODE_VERSION}..."
for attempt in 1 2 3; do
echo "Installation attempt $attempt..."

View File

@@ -11,7 +11,7 @@ Add the following to your workflow file:
```yaml
# Using a direct prompt
- name: Run Claude Code with direct prompt
uses: anthropics/claude-code-base-action@v1
uses: anthropics/claude-code-base-action@beta
with:
prompt: "Your prompt here"
allowed_tools: "Bash(git:*),View,GlobTool,GrepTool,BatchTool"
@@ -19,7 +19,7 @@ Add the following to your workflow file:
# Or using a prompt from a file
- name: Run Claude Code with prompt file
uses: anthropics/claude-code-base-action@v1
uses: anthropics/claude-code-base-action@beta
with:
prompt_file: "/path/to/prompt.txt"
allowed_tools: "Bash(git:*),View,GlobTool,GrepTool,BatchTool"
@@ -27,7 +27,7 @@ Add the following to your workflow file:
# Or limiting the conversation turns
- name: Run Claude Code with limited turns
uses: anthropics/claude-code-base-action@v1
uses: anthropics/claude-code-base-action@beta
with:
prompt: "Your prompt here"
allowed_tools: "Bash(git:*),View,GlobTool,GrepTool,BatchTool"
@@ -36,7 +36,7 @@ Add the following to your workflow file:
# Using custom system prompts
- name: Run Claude Code with custom system prompt
uses: anthropics/claude-code-base-action@v1
uses: anthropics/claude-code-base-action@beta
with:
prompt: "Build a REST API"
system_prompt: "You are a senior backend engineer. Focus on security, performance, and maintainability."
@@ -45,7 +45,7 @@ Add the following to your workflow file:
# Or appending to the default system prompt
- name: Run Claude Code with appended system prompt
uses: anthropics/claude-code-base-action@v1
uses: anthropics/claude-code-base-action@beta
with:
prompt: "Create a database schema"
append_system_prompt: "After writing code, be sure to code review yourself."
@@ -54,7 +54,7 @@ Add the following to your workflow file:
# Using custom environment variables
- name: Run Claude Code with custom environment variables
uses: anthropics/claude-code-base-action@v1
uses: anthropics/claude-code-base-action@beta
with:
prompt: "Deploy to staging environment"
claude_env: |
@@ -66,7 +66,7 @@ Add the following to your workflow file:
# Using fallback model for handling API errors
- name: Run Claude Code with fallback model
uses: anthropics/claude-code-base-action@v1
uses: anthropics/claude-code-base-action@beta
with:
prompt: "Review and fix TypeScript errors"
model: "claude-opus-4-1-20250805"
@@ -76,7 +76,7 @@ Add the following to your workflow file:
# Using OAuth token instead of API key
- name: Run Claude Code with OAuth token
uses: anthropics/claude-code-base-action@v1
uses: anthropics/claude-code-base-action@beta
with:
prompt: "Update dependencies"
allowed_tools: "Bash(git:*),View,GlobTool,GrepTool,BatchTool"
@@ -130,7 +130,7 @@ Example usage:
```yaml
- name: Run Claude Code with Node.js 20
uses: anthropics/claude-code-base-action@v1
uses: anthropics/claude-code-base-action@beta
env:
NODE_VERSION: "20.x"
with:
@@ -146,7 +146,7 @@ The `claude_env` input accepts YAML multiline format with key-value pairs:
```yaml
- name: Deploy with custom environment
uses: anthropics/claude-code-base-action@v1
uses: anthropics/claude-code-base-action@beta
with:
prompt: "Deploy the application to the staging environment"
claude_env: |
@@ -200,7 +200,7 @@ Provide a path to a JSON file containing Claude Code settings:
```yaml
- name: Run Claude Code with settings file
uses: anthropics/claude-code-base-action@v1
uses: anthropics/claude-code-base-action@beta
with:
prompt: "Your prompt here"
settings: "path/to/settings.json"
@@ -214,7 +214,7 @@ Provide the settings configuration directly as a JSON string:
```yaml
- name: Run Claude Code with inline settings
uses: anthropics/claude-code-base-action@v1
uses: anthropics/claude-code-base-action@beta
with:
prompt: "Your prompt here"
settings: |
@@ -263,7 +263,7 @@ Provide a path to a JSON file containing MCP configuration:
```yaml
- name: Run Claude Code with MCP config file
uses: anthropics/claude-code-base-action@v1
uses: anthropics/claude-code-base-action@beta
with:
prompt: "Your prompt here"
mcp_config: "path/to/mcp-config.json"
@@ -277,7 +277,7 @@ Provide the MCP configuration directly as a JSON string:
```yaml
- name: Run Claude Code with inline MCP config
uses: anthropics/claude-code-base-action@v1
uses: anthropics/claude-code-base-action@beta
with:
prompt: "Your prompt here"
mcp_config: |
@@ -317,7 +317,7 @@ You can combine MCP config with other inputs like allowed tools:
```yaml
# Using multiple inputs together
- name: Run Claude Code with MCP and custom tools
uses: anthropics/claude-code-base-action@v1
uses: anthropics/claude-code-base-action@beta
with:
prompt: "Access the custom MCP server and use its tools"
mcp_config: "mcp-config.json"
@@ -345,7 +345,7 @@ jobs:
- name: Run Code Review with Claude
id: code-review
uses: anthropics/claude-code-base-action@v1
uses: anthropics/claude-code-base-action@beta
with:
prompt: "Review the PR changes. Focus on code quality, potential bugs, and performance issues. Suggest improvements where appropriate. Write your review as markdown text."
allowed_tools: "Bash(git diff --name-only HEAD~1),Bash(git diff HEAD~1),View,GlobTool,GrepTool,Write"
@@ -408,7 +408,7 @@ Use provider-specific model names based on your chosen provider:
```yaml
# For direct Anthropic API (default)
- name: Run Claude Code with Anthropic API
uses: anthropics/claude-code-base-action@v1
uses: anthropics/claude-code-base-action@beta
with:
prompt: "Your prompt here"
model: "claude-3-7-sonnet-20250219"
@@ -422,7 +422,7 @@ Use provider-specific model names based on your chosen provider:
aws-region: us-west-2
- name: Run Claude Code with Bedrock
uses: anthropics/claude-code-base-action@v1
uses: anthropics/claude-code-base-action@beta
with:
prompt: "Your prompt here"
model: "anthropic.claude-3-7-sonnet-20250219-v1:0"
@@ -436,7 +436,7 @@ Use provider-specific model names based on your chosen provider:
service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }}
- name: Run Claude Code with Vertex AI
uses: anthropics/claude-code-base-action@v1
uses: anthropics/claude-code-base-action@beta
with:
prompt: "Your prompt here"
model: "claude-3-7-sonnet@20250219"
@@ -455,7 +455,7 @@ This example shows how to use OIDC authentication with AWS Bedrock:
aws-region: us-west-2
- name: Run Claude Code with AWS OIDC
uses: anthropics/claude-code-base-action@v1
uses: anthropics/claude-code-base-action@beta
with:
prompt: "Your prompt here"
use_bedrock: "true"
@@ -475,7 +475,7 @@ This example shows how to use OIDC authentication with GCP Vertex AI:
service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }}
- name: Run Claude Code with GCP OIDC
uses: anthropics/claude-code-base-action@v1
uses: anthropics/claude-code-base-action@beta
with:
prompt: "Your prompt here"
use_vertex: "true"

View File

@@ -97,9 +97,9 @@ runs:
- name: Install Bun
if: inputs.path_to_bun_executable == ''
uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # https://github.com/oven-sh/setup-bun/releases/tag/v2.0.2
uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # https://github.com/oven-sh/setup-bun/releases/tag/v2.1.2
with:
bun-version: 1.2.11
bun-version: 2.1.1
- name: Setup Custom Bun Path
if: inputs.path_to_bun_executable != ''
@@ -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.7"
CLAUDE_CODE_VERSION="2.1.11"
echo "Installing Claude Code v${CLAUDE_CODE_VERSION}..."
for attempt in 1 2 3; do
echo "Installation attempt $attempt..."

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.7",
"@anthropic-ai/claude-agent-sdk": "^0.2.11",
"shell-quote": "^1.8.3",
},
"devDependencies": {
@@ -27,7 +27,7 @@
"@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.7", "", { "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-I1/zcnLah74kZeRkj/1QnDaC6ItJ2m/Bftlm25uoaRkZx7i7SkcpqM9jGE/r2A8PMxnw5WpabP60Xgj99CrTuw=="],
"@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.2.11", "", { "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-50q4vfh57HYTUrwukULp3gvSaOZfRF5zukxhWvW6mmUHuD8+Xwhqn59sZhoDz9ZUw6Um+1lUCgWpJw5/TTMn5w=="],
"@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="],

View File

@@ -11,7 +11,7 @@
},
"dependencies": {
"@actions/core": "^1.10.1",
"@anthropic-ai/claude-agent-sdk": "^0.2.7",
"@anthropic-ai/claude-agent-sdk": "^0.2.11",
"shell-quote": "^1.8.3"
},
"devDependencies": {

View File

@@ -7,7 +7,7 @@
"dependencies": {
"@actions/core": "^1.10.1",
"@actions/github": "^6.0.1",
"@anthropic-ai/claude-agent-sdk": "^0.2.7",
"@anthropic-ai/claude-agent-sdk": "^0.2.11",
"@modelcontextprotocol/sdk": "^1.11.0",
"@octokit/graphql": "^8.2.2",
"@octokit/rest": "^21.1.1",
@@ -37,7 +37,7 @@
"@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.7", "", { "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-I1/zcnLah74kZeRkj/1QnDaC6ItJ2m/Bftlm25uoaRkZx7i7SkcpqM9jGE/r2A8PMxnw5WpabP60Xgj99CrTuw=="],
"@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.2.11", "", { "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-50q4vfh57HYTUrwukULp3gvSaOZfRF5zukxhWvW6mmUHuD8+Xwhqn59sZhoDz9ZUw6Um+1lUCgWpJw5/TTMn5w=="],
"@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="],

View File

@@ -13,6 +13,16 @@
- **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
## Pull Request Creation
In its default configuration, **Claude does not create pull requests automatically** when responding to `@claude` mentions. Instead:
- Claude commits code changes to a new branch
- Claude provides a **link to the GitHub PR creation page** in its response
- **The user must click the link and create the PR themselves**, ensuring human oversight before any code is proposed for merging
This design ensures that users retain full control over what pull requests are created and can review the changes before initiating the PR workflow.
## ⚠️ Prompt Injection Risks
**Beware of potential hidden markdown when tagging Claude on untrusted content.** External contributors may include hidden instructions through HTML comments, invisible characters, hidden attributes, or other techniques. The action sanitizes content by stripping HTML comments, invisible characters, markdown image alt text, hidden HTML attributes, and HTML entities, but new bypass techniques may emerge. We recommend reviewing the raw content of all input coming from external contributors before allowing Claude to process it.

View File

@@ -12,7 +12,7 @@
"dependencies": {
"@actions/core": "^1.10.1",
"@actions/github": "^6.0.1",
"@anthropic-ai/claude-agent-sdk": "^0.2.7",
"@anthropic-ai/claude-agent-sdk": "^0.2.11",
"@modelcontextprotocol/sdk": "^1.11.0",
"@octokit/graphql": "^8.2.2",
"@octokit/rest": "^21.1.1",

View File

@@ -82,8 +82,13 @@ export async function setupSshSigning(sshSigningKey: string): Promise<void> {
const sshDir = join(homedir(), ".ssh");
await mkdir(sshDir, { recursive: true, mode: 0o700 });
// Ensure key ends with newline (required for ssh-keygen to parse it)
const normalizedKey = sshSigningKey.endsWith("\n")
? sshSigningKey
: sshSigningKey + "\n";
// Write the signing key atomically with secure permissions (600)
await writeFile(SSH_SIGNING_KEY_PATH, sshSigningKey, { mode: 0o600 });
await writeFile(SSH_SIGNING_KEY_PATH, normalizedKey, { mode: 0o600 });
console.log(`✓ SSH signing key written to ${SSH_SIGNING_KEY_PATH}`);
// Configure git to use SSH signing

View File

@@ -6,11 +6,11 @@
*/
import type { Octokit } from "@octokit/rest";
import type { ParsedGitHubContext } from "../context";
import type { GitHubContext } from "../context";
export async function checkHumanActor(
octokit: Octokit,
githubContext: ParsedGitHubContext,
githubContext: GitHubContext,
) {
// Fetch user information from GitHub API
const { data: userData } = await octokit.users.getByUsername({

View File

@@ -8,6 +8,7 @@ import {
configureGitAuth,
setupSshSigning,
} from "../../github/operations/git-config";
import { checkHumanActor } from "../../github/validation/actor";
import type { GitHubContext } from "../../github/context";
import { isEntityContext } from "../../github/context";
@@ -80,7 +81,14 @@ export const agentMode: Mode = {
return false;
},
async prepare({ context, githubToken }: ModeOptions): Promise<ModeResult> {
async prepare({
context,
octokit,
githubToken,
}: ModeOptions): Promise<ModeResult> {
// Check if actor is human (prevents bot-triggered loops)
await checkHumanActor(octokit.rest, context);
// Configure git authentication for agent mode (same as tag mode)
// SSH signing takes precedence if provided
const useSshSigning = !!context.inputs.sshSigningKey;

View File

@@ -145,12 +145,12 @@ describe("Agent Mode", () => {
users: {
getAuthenticated: mock(() =>
Promise.resolve({
data: { login: "test-user", id: 12345 },
data: { login: "test-user", id: 12345, type: "User" },
}),
),
getByUsername: mock(() =>
Promise.resolve({
data: { login: "test-user", id: 12345 },
data: { login: "test-user", id: 12345, type: "User" },
}),
),
},
@@ -187,6 +187,65 @@ describe("Agent Mode", () => {
process.env.GITHUB_REF_NAME = originalRefName;
});
test("prepare method rejects bot actors without allowed_bots", async () => {
const contextWithPrompts = createMockAutomationContext({
eventName: "workflow_dispatch",
});
contextWithPrompts.actor = "claude[bot]";
contextWithPrompts.inputs.allowedBots = "";
const mockOctokit = {
rest: {
users: {
getByUsername: mock(() =>
Promise.resolve({
data: { login: "claude[bot]", id: 12345, type: "Bot" },
}),
),
},
},
} as any;
await expect(
agentMode.prepare({
context: contextWithPrompts,
octokit: mockOctokit,
githubToken: "test-token",
}),
).rejects.toThrow(
"Workflow initiated by non-human actor: claude (type: Bot)",
);
});
test("prepare method allows bot actors when in allowed_bots list", async () => {
const contextWithPrompts = createMockAutomationContext({
eventName: "workflow_dispatch",
});
contextWithPrompts.actor = "dependabot[bot]";
contextWithPrompts.inputs.allowedBots = "dependabot";
const mockOctokit = {
rest: {
users: {
getByUsername: mock(() =>
Promise.resolve({
data: { login: "dependabot[bot]", id: 12345, type: "Bot" },
}),
),
},
},
} as any;
// Should not throw - bot is in allowed list
await expect(
agentMode.prepare({
context: contextWithPrompts,
octokit: mockOctokit,
githubToken: "test-token",
}),
).resolves.toBeDefined();
});
test("prepare method creates prompt file with correct content", async () => {
const contextWithPrompts = createMockAutomationContext({
eventName: "workflow_dispatch",
@@ -199,12 +258,12 @@ describe("Agent Mode", () => {
users: {
getAuthenticated: mock(() =>
Promise.resolve({
data: { login: "test-user", id: 12345 },
data: { login: "test-user", id: 12345, type: "User" },
}),
),
getByUsername: mock(() =>
Promise.resolve({
data: { login: "test-user", id: 12345 },
data: { login: "test-user", id: 12345, type: "User" },
}),
),
},

View File

@@ -55,6 +55,47 @@ describe("SSH Signing", () => {
expect(permissions).toBe(0o600);
});
test("should normalize key to have trailing newline", async () => {
// ssh-keygen requires a trailing newline to parse the key
const keyWithoutNewline =
"-----BEGIN OPENSSH PRIVATE KEY-----\ntest-key-content\n-----END OPENSSH PRIVATE KEY-----";
const keyWithNewline = keyWithoutNewline + "\n";
// Create directory
await mkdir(testSshDir, { recursive: true, mode: 0o700 });
// Normalize the key (same logic as setupSshSigning)
const normalizedKey = keyWithoutNewline.endsWith("\n")
? keyWithoutNewline
: keyWithoutNewline + "\n";
await writeFile(testKeyPath, normalizedKey, { mode: 0o600 });
// Verify the written key ends with newline
const keyContent = await readFile(testKeyPath, "utf-8");
expect(keyContent).toBe(keyWithNewline);
expect(keyContent.endsWith("\n")).toBe(true);
});
test("should not add extra newline if key already has one", async () => {
const keyWithNewline =
"-----BEGIN OPENSSH PRIVATE KEY-----\ntest-key-content\n-----END OPENSSH PRIVATE KEY-----\n";
await mkdir(testSshDir, { recursive: true, mode: 0o700 });
// Normalize the key (same logic as setupSshSigning)
const normalizedKey = keyWithNewline.endsWith("\n")
? keyWithNewline
: keyWithNewline + "\n";
await writeFile(testKeyPath, normalizedKey, { mode: 0o600 });
// Verify no double newline
const keyContent = await readFile(testKeyPath, "utf-8");
expect(keyContent).toBe(keyWithNewline);
expect(keyContent.endsWith("\n\n")).toBe(false);
});
test("should create .ssh directory with secure permissions", async () => {
// Clean up first
await rm(testSshDir, { recursive: true, force: true });