mirror of
https://github.com/anthropics/claude-code-action.git
synced 2026-01-24 15:34:13 +08:00
Compare commits
1 Commits
feature/me
...
claude/sla
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
68df0f8000 |
132
.github/workflows/bump-claude-code-version.yml
vendored
Normal file
132
.github/workflows/bump-claude-code-version.yml
vendored
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
name: Bump Claude Code Version
|
||||||
|
|
||||||
|
on:
|
||||||
|
repository_dispatch:
|
||||||
|
types: [bump_claude_code_version]
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: "Claude Code version to bump to"
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
bump-version:
|
||||||
|
name: Bump Claude Code Version
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment: release
|
||||||
|
timeout-minutes: 5
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.RELEASE_PAT }}
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Get version from event payload
|
||||||
|
id: get_version
|
||||||
|
run: |
|
||||||
|
# Get version from either repository_dispatch or workflow_dispatch
|
||||||
|
if [ "${{ github.event_name }}" = "repository_dispatch" ]; then
|
||||||
|
NEW_VERSION="${CLIENT_PAYLOAD_VERSION}"
|
||||||
|
else
|
||||||
|
NEW_VERSION="${INPUT_VERSION}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Sanitize the version to avoid issues enabled by problematic characters
|
||||||
|
NEW_VERSION=$(echo "$NEW_VERSION" | tr -d '`;$(){}[]|&<>' | tr -s ' ' '-')
|
||||||
|
|
||||||
|
if [ -z "$NEW_VERSION" ]; then
|
||||||
|
echo "Error: version not provided"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "NEW_VERSION=$NEW_VERSION" >> $GITHUB_ENV
|
||||||
|
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
|
||||||
|
env:
|
||||||
|
INPUT_VERSION: ${{ inputs.version }}
|
||||||
|
CLIENT_PAYLOAD_VERSION: ${{ github.event.client_payload.version }}
|
||||||
|
|
||||||
|
- name: Create branch and update base-action/action.yml
|
||||||
|
run: |
|
||||||
|
# Variables
|
||||||
|
TIMESTAMP=$(date +'%Y%m%d-%H%M%S')
|
||||||
|
BRANCH_NAME="bump-claude-code-${{ env.NEW_VERSION }}-$TIMESTAMP"
|
||||||
|
|
||||||
|
echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
# Get the default branch
|
||||||
|
DEFAULT_BRANCH=$(gh api repos/${GITHUB_REPOSITORY} --jq '.default_branch')
|
||||||
|
echo "DEFAULT_BRANCH=$DEFAULT_BRANCH" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
# Get the latest commit SHA from the default branch
|
||||||
|
BASE_SHA=$(gh api repos/${GITHUB_REPOSITORY}/git/refs/heads/$DEFAULT_BRANCH --jq '.object.sha')
|
||||||
|
|
||||||
|
# Create a new branch
|
||||||
|
gh api \
|
||||||
|
--method POST \
|
||||||
|
repos/${GITHUB_REPOSITORY}/git/refs \
|
||||||
|
-f ref="refs/heads/$BRANCH_NAME" \
|
||||||
|
-f sha="$BASE_SHA"
|
||||||
|
|
||||||
|
# Get the current base-action/action.yml content
|
||||||
|
ACTION_CONTENT=$(gh api repos/${GITHUB_REPOSITORY}/contents/base-action/action.yml?ref=$DEFAULT_BRANCH --jq '.content' | base64 -d)
|
||||||
|
|
||||||
|
# Update the Claude Code version in the npm install command
|
||||||
|
UPDATED_CONTENT=$(echo "$ACTION_CONTENT" | sed -E "s/(npm install -g @anthropic-ai\/claude-code@)[0-9]+\.[0-9]+\.[0-9]+/\1${{ env.NEW_VERSION }}/")
|
||||||
|
|
||||||
|
# Verify the change would be made
|
||||||
|
if ! echo "$UPDATED_CONTENT" | grep -q "@anthropic-ai/claude-code@${{ env.NEW_VERSION }}"; then
|
||||||
|
echo "Error: Failed to update Claude Code version in content"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get the current SHA of base-action/action.yml for the update API call
|
||||||
|
FILE_SHA=$(gh api repos/${GITHUB_REPOSITORY}/contents/base-action/action.yml?ref=$DEFAULT_BRANCH --jq '.sha')
|
||||||
|
|
||||||
|
# Create the updated base-action/action.yml content in base64
|
||||||
|
echo "$UPDATED_CONTENT" | base64 > action.yml.b64
|
||||||
|
|
||||||
|
# Commit the updated base-action/action.yml via GitHub API
|
||||||
|
gh api \
|
||||||
|
--method PUT \
|
||||||
|
repos/${GITHUB_REPOSITORY}/contents/base-action/action.yml \
|
||||||
|
-f message="chore: bump Claude Code version to ${{ env.NEW_VERSION }}" \
|
||||||
|
-F content=@action.yml.b64 \
|
||||||
|
-f sha="$FILE_SHA" \
|
||||||
|
-f branch="$BRANCH_NAME"
|
||||||
|
|
||||||
|
echo "Successfully created branch and updated Claude Code version to ${{ env.NEW_VERSION }}"
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.RELEASE_PAT }}
|
||||||
|
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||||
|
|
||||||
|
- name: Create Pull Request
|
||||||
|
run: |
|
||||||
|
# Determine trigger type for PR body
|
||||||
|
if [ "${{ github.event_name }}" = "repository_dispatch" ]; then
|
||||||
|
TRIGGER_INFO="repository dispatch event"
|
||||||
|
else
|
||||||
|
TRIGGER_INFO="manual workflow dispatch by @${GITHUB_ACTOR}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create PR body with proper YAML escape
|
||||||
|
printf -v PR_BODY "## Bump Claude Code to ${{ env.NEW_VERSION }}\n\nThis PR updates the Claude Code version in base-action/action.yml to ${{ env.NEW_VERSION }}.\n\n### Changes\n- Updated Claude Code version from current to \`${{ env.NEW_VERSION }}\`\n\n### Triggered by\n- $TRIGGER_INFO\n\n🤖 This PR was automatically created by the bump-claude-code-version workflow."
|
||||||
|
|
||||||
|
echo "Creating PR with gh pr create command"
|
||||||
|
PR_URL=$(gh pr create \
|
||||||
|
--repo "${GITHUB_REPOSITORY}" \
|
||||||
|
--title "chore: bump Claude Code version to ${{ env.NEW_VERSION }}" \
|
||||||
|
--body "$PR_BODY" \
|
||||||
|
--base "${DEFAULT_BRANCH}" \
|
||||||
|
--head "${BRANCH_NAME}")
|
||||||
|
|
||||||
|
echo "PR created successfully: $PR_URL"
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.RELEASE_PAT }}
|
||||||
|
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||||
|
GITHUB_ACTOR: ${{ github.actor }}
|
||||||
|
DEFAULT_BRANCH: ${{ env.DEFAULT_BRANCH }}
|
||||||
|
BRANCH_NAME: ${{ env.BRANCH_NAME }}
|
||||||
2
.github/workflows/claude.yml
vendored
2
.github/workflows/claude.yml
vendored
@@ -36,4 +36,4 @@ jobs:
|
|||||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||||
claude_args: |
|
claude_args: |
|
||||||
--allowedTools "Bash(bun install),Bash(bun test:*),Bash(bun run format),Bash(bun typecheck)"
|
--allowedTools "Bash(bun install),Bash(bun test:*),Bash(bun run format),Bash(bun typecheck)"
|
||||||
--model "claude-opus-4-5"
|
--model "claude-opus-4-1-20250805"
|
||||||
|
|||||||
100
action.yml
100
action.yml
@@ -120,10 +120,10 @@ outputs:
|
|||||||
value: ${{ steps.claude-code.outputs.execution_file }}
|
value: ${{ steps.claude-code.outputs.execution_file }}
|
||||||
branch_name:
|
branch_name:
|
||||||
description: "The branch created by Claude Code for this execution"
|
description: "The branch created by Claude Code for this execution"
|
||||||
value: ${{ steps.claude-code.outputs.CLAUDE_BRANCH }}
|
value: ${{ steps.prepare.outputs.CLAUDE_BRANCH }}
|
||||||
github_token:
|
github_token:
|
||||||
description: "The GitHub token used by the action (Claude App token if available)"
|
description: "The GitHub token used by the action (Claude App token if available)"
|
||||||
value: ${{ steps.claude-code.outputs.GITHUB_TOKEN }}
|
value: ${{ steps.prepare.outputs.github_token }}
|
||||||
structured_output:
|
structured_output:
|
||||||
description: "JSON string containing all structured output fields when --json-schema is provided in claude_args. Use fromJSON() to parse: fromJSON(steps.id.outputs.structured_output).field_name"
|
description: "JSON string containing all structured output fields when --json-schema is provided in claude_args. Use fromJSON() to parse: fromJSON(steps.id.outputs.structured_output).field_name"
|
||||||
value: ${{ steps.claude-code.outputs.structured_output }}
|
value: ${{ steps.claude-code.outputs.structured_output }}
|
||||||
@@ -153,14 +153,43 @@ runs:
|
|||||||
|
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
shell: bash
|
shell: bash
|
||||||
env:
|
|
||||||
PATH_TO_CLAUDE_CODE_EXECUTABLE: ${{ inputs.path_to_claude_code_executable }}
|
|
||||||
run: |
|
run: |
|
||||||
# Install main action dependencies
|
|
||||||
cd ${GITHUB_ACTION_PATH}
|
cd ${GITHUB_ACTION_PATH}
|
||||||
bun install
|
bun install
|
||||||
|
|
||||||
# Install base-action dependencies
|
- name: Prepare action
|
||||||
|
id: prepare
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
bun run ${GITHUB_ACTION_PATH}/src/entrypoints/prepare.ts
|
||||||
|
env:
|
||||||
|
MODE: ${{ inputs.mode }}
|
||||||
|
PROMPT: ${{ inputs.prompt }}
|
||||||
|
TRIGGER_PHRASE: ${{ inputs.trigger_phrase }}
|
||||||
|
ASSIGNEE_TRIGGER: ${{ inputs.assignee_trigger }}
|
||||||
|
LABEL_TRIGGER: ${{ inputs.label_trigger }}
|
||||||
|
BASE_BRANCH: ${{ inputs.base_branch }}
|
||||||
|
BRANCH_PREFIX: ${{ inputs.branch_prefix }}
|
||||||
|
OVERRIDE_GITHUB_TOKEN: ${{ inputs.github_token }}
|
||||||
|
ALLOWED_BOTS: ${{ inputs.allowed_bots }}
|
||||||
|
ALLOWED_NON_WRITE_USERS: ${{ inputs.allowed_non_write_users }}
|
||||||
|
GITHUB_RUN_ID: ${{ github.run_id }}
|
||||||
|
USE_STICKY_COMMENT: ${{ inputs.use_sticky_comment }}
|
||||||
|
DEFAULT_WORKFLOW_TOKEN: ${{ github.token }}
|
||||||
|
USE_COMMIT_SIGNING: ${{ inputs.use_commit_signing }}
|
||||||
|
BOT_ID: ${{ inputs.bot_id }}
|
||||||
|
BOT_NAME: ${{ inputs.bot_name }}
|
||||||
|
TRACK_PROGRESS: ${{ inputs.track_progress }}
|
||||||
|
ADDITIONAL_PERMISSIONS: ${{ inputs.additional_permissions }}
|
||||||
|
CLAUDE_ARGS: ${{ inputs.claude_args }}
|
||||||
|
ALL_INPUTS: ${{ toJson(inputs) }}
|
||||||
|
|
||||||
|
- name: Install Base Action Dependencies
|
||||||
|
if: steps.prepare.outputs.contains_trigger == 'true'
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
PATH_TO_CLAUDE_CODE_EXECUTABLE: ${{ inputs.path_to_claude_code_executable }}
|
||||||
|
run: |
|
||||||
echo "Installing base-action dependencies..."
|
echo "Installing base-action dependencies..."
|
||||||
cd ${GITHUB_ACTION_PATH}/base-action
|
cd ${GITHUB_ACTION_PATH}/base-action
|
||||||
bun install
|
bun install
|
||||||
@@ -169,7 +198,7 @@ runs:
|
|||||||
|
|
||||||
# Install Claude Code if no custom executable is provided
|
# Install Claude Code if no custom executable is provided
|
||||||
if [ -z "$PATH_TO_CLAUDE_CODE_EXECUTABLE" ]; then
|
if [ -z "$PATH_TO_CLAUDE_CODE_EXECUTABLE" ]; then
|
||||||
CLAUDE_CODE_VERSION="2.0.74"
|
CLAUDE_CODE_VERSION="2.0.69"
|
||||||
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..."
|
||||||
@@ -194,46 +223,30 @@ runs:
|
|||||||
echo "$CLAUDE_DIR" >> "$GITHUB_PATH"
|
echo "$CLAUDE_DIR" >> "$GITHUB_PATH"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Unified step: prepare + setup + run Claude
|
|
||||||
- name: Run Claude Code
|
- name: Run Claude Code
|
||||||
id: claude-code
|
id: claude-code
|
||||||
|
if: steps.prepare.outputs.contains_trigger == 'true'
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
bun run ${GITHUB_ACTION_PATH}/src/entrypoints/run.ts
|
|
||||||
env:
|
|
||||||
# Action configuration
|
|
||||||
CLAUDE_CODE_ACTION: "1"
|
|
||||||
MODE: ${{ inputs.mode }}
|
|
||||||
PROMPT: ${{ inputs.prompt }}
|
|
||||||
TRIGGER_PHRASE: ${{ inputs.trigger_phrase }}
|
|
||||||
ASSIGNEE_TRIGGER: ${{ inputs.assignee_trigger }}
|
|
||||||
LABEL_TRIGGER: ${{ inputs.label_trigger }}
|
|
||||||
BASE_BRANCH: ${{ inputs.base_branch }}
|
|
||||||
BRANCH_PREFIX: ${{ inputs.branch_prefix }}
|
|
||||||
OVERRIDE_GITHUB_TOKEN: ${{ inputs.github_token }}
|
|
||||||
ALLOWED_BOTS: ${{ inputs.allowed_bots }}
|
|
||||||
ALLOWED_NON_WRITE_USERS: ${{ inputs.allowed_non_write_users }}
|
|
||||||
GITHUB_RUN_ID: ${{ github.run_id }}
|
|
||||||
USE_STICKY_COMMENT: ${{ inputs.use_sticky_comment }}
|
|
||||||
DEFAULT_WORKFLOW_TOKEN: ${{ github.token }}
|
|
||||||
USE_COMMIT_SIGNING: ${{ inputs.use_commit_signing }}
|
|
||||||
BOT_ID: ${{ inputs.bot_id }}
|
|
||||||
BOT_NAME: ${{ inputs.bot_name }}
|
|
||||||
TRACK_PROGRESS: ${{ inputs.track_progress }}
|
|
||||||
ADDITIONAL_PERMISSIONS: ${{ inputs.additional_permissions }}
|
|
||||||
CLAUDE_ARGS: ${{ inputs.claude_args }}
|
|
||||||
ALL_INPUTS: ${{ toJson(inputs) }}
|
|
||||||
|
|
||||||
|
# Run the base-action
|
||||||
|
bun run ${GITHUB_ACTION_PATH}/base-action/src/index.ts
|
||||||
|
env:
|
||||||
# Base-action inputs
|
# Base-action inputs
|
||||||
|
CLAUDE_CODE_ACTION: "1"
|
||||||
|
INPUT_PROMPT_FILE: ${{ runner.temp }}/claude-prompts/claude-prompt.txt
|
||||||
INPUT_SETTINGS: ${{ inputs.settings }}
|
INPUT_SETTINGS: ${{ inputs.settings }}
|
||||||
INPUT_CLAUDE_ARGS: ${{ inputs.claude_args }}
|
INPUT_CLAUDE_ARGS: ${{ steps.prepare.outputs.claude_args }}
|
||||||
INPUT_EXPERIMENTAL_SLASH_COMMANDS_DIR: ${{ github.action_path }}/slash-commands
|
INPUT_EXPERIMENTAL_SLASH_COMMANDS_DIR: ${{ github.action_path }}/slash-commands
|
||||||
|
INPUT_ACTION_INPUTS_PRESENT: ${{ steps.prepare.outputs.action_inputs_present }}
|
||||||
INPUT_PATH_TO_CLAUDE_CODE_EXECUTABLE: ${{ inputs.path_to_claude_code_executable }}
|
INPUT_PATH_TO_CLAUDE_CODE_EXECUTABLE: ${{ inputs.path_to_claude_code_executable }}
|
||||||
INPUT_PATH_TO_BUN_EXECUTABLE: ${{ inputs.path_to_bun_executable }}
|
INPUT_PATH_TO_BUN_EXECUTABLE: ${{ inputs.path_to_bun_executable }}
|
||||||
INPUT_SHOW_FULL_OUTPUT: ${{ inputs.show_full_output }}
|
INPUT_SHOW_FULL_OUTPUT: ${{ inputs.show_full_output }}
|
||||||
INPUT_PLUGINS: ${{ inputs.plugins }}
|
INPUT_PLUGINS: ${{ inputs.plugins }}
|
||||||
INPUT_PLUGIN_MARKETPLACES: ${{ inputs.plugin_marketplaces }}
|
INPUT_PLUGIN_MARKETPLACES: ${{ inputs.plugin_marketplaces }}
|
||||||
|
|
||||||
|
# Model configuration
|
||||||
|
GITHUB_TOKEN: ${{ steps.prepare.outputs.GITHUB_TOKEN }}
|
||||||
NODE_VERSION: ${{ env.NODE_VERSION }}
|
NODE_VERSION: ${{ env.NODE_VERSION }}
|
||||||
DETAILED_PERMISSION_MESSAGES: "1"
|
DETAILED_PERMISSION_MESSAGES: "1"
|
||||||
|
|
||||||
@@ -273,33 +286,32 @@ runs:
|
|||||||
ANTHROPIC_DEFAULT_OPUS_MODEL: ${{ env.ANTHROPIC_DEFAULT_OPUS_MODEL }}
|
ANTHROPIC_DEFAULT_OPUS_MODEL: ${{ env.ANTHROPIC_DEFAULT_OPUS_MODEL }}
|
||||||
|
|
||||||
- name: Update comment with job link
|
- name: Update comment with job link
|
||||||
if: steps.claude-code.outputs.contains_trigger == 'true' && steps.claude-code.outputs.claude_comment_id && always()
|
if: steps.prepare.outputs.contains_trigger == 'true' && steps.prepare.outputs.claude_comment_id && always()
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
bun run ${GITHUB_ACTION_PATH}/src/entrypoints/update-comment-link.ts
|
bun run ${GITHUB_ACTION_PATH}/src/entrypoints/update-comment-link.ts
|
||||||
env:
|
env:
|
||||||
REPOSITORY: ${{ github.repository }}
|
REPOSITORY: ${{ github.repository }}
|
||||||
PR_NUMBER: ${{ github.event.issue.number || github.event.pull_request.number }}
|
PR_NUMBER: ${{ github.event.issue.number || github.event.pull_request.number }}
|
||||||
CLAUDE_COMMENT_ID: ${{ steps.claude-code.outputs.claude_comment_id }}
|
CLAUDE_COMMENT_ID: ${{ steps.prepare.outputs.claude_comment_id }}
|
||||||
GITHUB_RUN_ID: ${{ github.run_id }}
|
GITHUB_RUN_ID: ${{ github.run_id }}
|
||||||
GITHUB_TOKEN: ${{ steps.claude-code.outputs.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ steps.prepare.outputs.GITHUB_TOKEN }}
|
||||||
GH_TOKEN: ${{ steps.claude-code.outputs.GITHUB_TOKEN }}
|
|
||||||
GITHUB_EVENT_NAME: ${{ github.event_name }}
|
GITHUB_EVENT_NAME: ${{ github.event_name }}
|
||||||
TRIGGER_COMMENT_ID: ${{ github.event.comment.id }}
|
TRIGGER_COMMENT_ID: ${{ github.event.comment.id }}
|
||||||
CLAUDE_BRANCH: ${{ steps.claude-code.outputs.CLAUDE_BRANCH }}
|
CLAUDE_BRANCH: ${{ steps.prepare.outputs.CLAUDE_BRANCH }}
|
||||||
IS_PR: ${{ github.event.issue.pull_request != null || github.event_name == 'pull_request_target' || github.event_name == 'pull_request_review_comment' }}
|
IS_PR: ${{ github.event.issue.pull_request != null || github.event_name == 'pull_request_target' || github.event_name == 'pull_request_review_comment' }}
|
||||||
BASE_BRANCH: ${{ steps.claude-code.outputs.BASE_BRANCH }}
|
BASE_BRANCH: ${{ steps.prepare.outputs.BASE_BRANCH }}
|
||||||
CLAUDE_SUCCESS: ${{ steps.claude-code.outputs.conclusion == 'success' }}
|
CLAUDE_SUCCESS: ${{ steps.claude-code.outputs.conclusion == 'success' }}
|
||||||
OUTPUT_FILE: ${{ steps.claude-code.outputs.execution_file || '' }}
|
OUTPUT_FILE: ${{ steps.claude-code.outputs.execution_file || '' }}
|
||||||
TRIGGER_USERNAME: ${{ github.event.comment.user.login || github.event.issue.user.login || github.event.pull_request.user.login || github.event.sender.login || github.triggering_actor || github.actor || '' }}
|
TRIGGER_USERNAME: ${{ github.event.comment.user.login || github.event.issue.user.login || github.event.pull_request.user.login || github.event.sender.login || github.triggering_actor || github.actor || '' }}
|
||||||
PREPARE_SUCCESS: ${{ steps.claude-code.outcome == 'success' }}
|
PREPARE_SUCCESS: ${{ steps.prepare.outcome == 'success' }}
|
||||||
PREPARE_ERROR: ${{ steps.claude-code.outputs.prepare_error || '' }}
|
PREPARE_ERROR: ${{ steps.prepare.outputs.prepare_error || '' }}
|
||||||
USE_STICKY_COMMENT: ${{ inputs.use_sticky_comment }}
|
USE_STICKY_COMMENT: ${{ inputs.use_sticky_comment }}
|
||||||
USE_COMMIT_SIGNING: ${{ inputs.use_commit_signing }}
|
USE_COMMIT_SIGNING: ${{ inputs.use_commit_signing }}
|
||||||
TRACK_PROGRESS: ${{ inputs.track_progress }}
|
TRACK_PROGRESS: ${{ inputs.track_progress }}
|
||||||
|
|
||||||
- name: Display Claude Code Report
|
- name: Display Claude Code Report
|
||||||
if: steps.claude-code.outputs.contains_trigger == 'true' && steps.claude-code.outputs.execution_file != ''
|
if: steps.prepare.outputs.contains_trigger == 'true' && steps.claude-code.outputs.execution_file != ''
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
# Try to format the turns, but if it fails, dump the raw JSON
|
# Try to format the turns, but if it fails, dump the raw JSON
|
||||||
@@ -316,12 +328,12 @@ runs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Revoke app token
|
- name: Revoke app token
|
||||||
if: always() && inputs.github_token == '' && steps.claude-code.outputs.skipped_due_to_workflow_validation_mismatch != 'true'
|
if: always() && inputs.github_token == '' && steps.prepare.outputs.skipped_due_to_workflow_validation_mismatch != 'true'
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
curl -L \
|
curl -L \
|
||||||
-X DELETE \
|
-X DELETE \
|
||||||
-H "Accept: application/vnd.github+json" \
|
-H "Accept: application/vnd.github+json" \
|
||||||
-H "Authorization: Bearer ${{ steps.claude-code.outputs.GITHUB_TOKEN }}" \
|
-H "Authorization: Bearer ${{ steps.prepare.outputs.GITHUB_TOKEN }}" \
|
||||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||||
${GITHUB_API_URL:-https://api.github.com}/installation/token
|
${GITHUB_API_URL:-https://api.github.com}/installation/token
|
||||||
|
|||||||
@@ -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.0.74"
|
CLAUDE_CODE_VERSION="2.0.69"
|
||||||
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..."
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
{
|
{
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"configVersion": 0,
|
|
||||||
"workspaces": {
|
"workspaces": {
|
||||||
"": {
|
"": {
|
||||||
"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.1.74",
|
"@anthropic-ai/claude-agent-sdk": "^0.1.52",
|
||||||
"shell-quote": "^1.8.3",
|
"shell-quote": "^1.8.3",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -27,7 +26,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.1.74", "", { "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": "^3.24.1 || ^4.0.0" } }, "sha512-d6H3Oo625WAG3BrBFKJsuSshi4f0amc0kTJTm83LRPPFxn9kfq58FX4Oxxt+RUD9N3QumW9sQSEDnri20/F4qQ=="],
|
"@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.1.52", "", { "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": "^3.24.1" } }, "sha512-yF8N05+9NRbqYA/h39jQ726HTQFrdXXp7pEfDNKIJ2c4FdWvEjxBA/8ciZIebN6/PyvGDcbEp3yq2Co4rNpg6A=="],
|
||||||
|
|
||||||
"@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="],
|
"@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="],
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.10.1",
|
"@actions/core": "^1.10.1",
|
||||||
"@anthropic-ai/claude-agent-sdk": "^0.1.74",
|
"@anthropic-ai/claude-agent-sdk": "^0.1.52",
|
||||||
"shell-quote": "^1.8.3"
|
"shell-quote": "^1.8.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
/**
|
|
||||||
* Library exports for the base-action.
|
|
||||||
* These functions can be imported directly by the main action
|
|
||||||
* to avoid file/output-based communication between steps.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export { runClaudeWithSdk, runClaudeWithSdkFromFile } from "./run-claude-sdk";
|
|
||||||
export type { RunClaudeResult, PromptInput } from "./run-claude-sdk";
|
|
||||||
export { setupClaudeCodeSettings } from "./setup-claude-code-settings";
|
|
||||||
export { installPlugins } from "./install-plugins";
|
|
||||||
export { parseSdkOptions } from "./parse-sdk-options";
|
|
||||||
export type { ClaudeOptions } from "./run-claude";
|
|
||||||
export type { ParsedSdkOptions } from "./parse-sdk-options";
|
|
||||||
@@ -12,79 +12,12 @@ export type ParsedSdkOptions = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Flags that should accumulate multiple values instead of overwriting
|
// Flags that should accumulate multiple values instead of overwriting
|
||||||
// Include both camelCase and hyphenated variants for CLI compatibility
|
const ACCUMULATING_FLAGS = new Set(["allowedTools", "disallowedTools"]);
|
||||||
const ACCUMULATING_FLAGS = new Set([
|
|
||||||
"allowedTools",
|
|
||||||
"allowed-tools",
|
|
||||||
"disallowedTools",
|
|
||||||
"disallowed-tools",
|
|
||||||
"mcp-config",
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Delimiter used to join accumulated flag values
|
|
||||||
const ACCUMULATE_DELIMITER = "\x00";
|
|
||||||
|
|
||||||
type McpConfig = {
|
|
||||||
mcpServers?: Record<string, unknown>;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Merge multiple MCP config values into a single config.
|
|
||||||
* Each config can be a JSON string or a file path.
|
|
||||||
* For JSON strings, mcpServers objects are merged.
|
|
||||||
* For file paths, they are kept as-is (user's file takes precedence and is used last).
|
|
||||||
*/
|
|
||||||
function mergeMcpConfigs(configValues: string[]): string {
|
|
||||||
const merged: McpConfig = { mcpServers: {} };
|
|
||||||
let lastFilePath: string | null = null;
|
|
||||||
|
|
||||||
for (const config of configValues) {
|
|
||||||
const trimmed = config.trim();
|
|
||||||
if (!trimmed) continue;
|
|
||||||
|
|
||||||
// Check if it's a JSON string (starts with {) or a file path
|
|
||||||
if (trimmed.startsWith("{")) {
|
|
||||||
try {
|
|
||||||
const parsed = JSON.parse(trimmed) as McpConfig;
|
|
||||||
if (parsed.mcpServers) {
|
|
||||||
Object.assign(merged.mcpServers!, parsed.mcpServers);
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// If JSON parsing fails, treat as file path
|
|
||||||
lastFilePath = trimmed;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// It's a file path - store it to handle separately
|
|
||||||
lastFilePath = trimmed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have file paths, we need to keep the merged JSON and let the file
|
|
||||||
// be handled separately. Since we can only return one value, merge what we can.
|
|
||||||
// If there's a file path, we need a different approach - read the file at runtime.
|
|
||||||
// For now, if there's a file path, we'll stringify the merged config.
|
|
||||||
// The action prepends its config as JSON, so we can safely merge inline JSON configs.
|
|
||||||
|
|
||||||
// If no inline configs were found (all file paths), return the last file path
|
|
||||||
if (Object.keys(merged.mcpServers!).length === 0 && lastFilePath) {
|
|
||||||
return lastFilePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: If user passes a file path, we cannot merge it at parse time since
|
|
||||||
// we don't have access to the file system here. The action's built-in MCP
|
|
||||||
// servers are always passed as inline JSON, so they will be merged.
|
|
||||||
// If user also passes inline JSON, it will be merged.
|
|
||||||
// If user passes a file path, they should ensure it includes all needed servers.
|
|
||||||
|
|
||||||
return JSON.stringify(merged);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse claudeArgs string into extraArgs record for SDK pass-through
|
* Parse claudeArgs string into extraArgs record for SDK pass-through
|
||||||
* The SDK/CLI will handle --mcp-config, --json-schema, etc.
|
* The SDK/CLI will handle --mcp-config, --json-schema, etc.
|
||||||
* For allowedTools and disallowedTools, multiple occurrences are accumulated (null-char joined).
|
* For allowedTools and disallowedTools, multiple occurrences are accumulated (comma-joined).
|
||||||
* Accumulating flags also consume all consecutive non-flag values
|
|
||||||
* (e.g., --allowed-tools "Tool1" "Tool2" "Tool3" captures all three).
|
|
||||||
*/
|
*/
|
||||||
function parseClaudeArgsToExtraArgs(
|
function parseClaudeArgsToExtraArgs(
|
||||||
claudeArgs?: string,
|
claudeArgs?: string,
|
||||||
@@ -104,25 +37,13 @@ function parseClaudeArgsToExtraArgs(
|
|||||||
|
|
||||||
// Check if next arg is a value (not another flag)
|
// Check if next arg is a value (not another flag)
|
||||||
if (nextArg && !nextArg.startsWith("--")) {
|
if (nextArg && !nextArg.startsWith("--")) {
|
||||||
// For accumulating flags, consume all consecutive non-flag values
|
// For accumulating flags, join multiple values with commas
|
||||||
// This handles: --allowed-tools "Tool1" "Tool2" "Tool3"
|
if (ACCUMULATING_FLAGS.has(flag) && result[flag]) {
|
||||||
if (ACCUMULATING_FLAGS.has(flag)) {
|
result[flag] = `${result[flag]},${nextArg}`;
|
||||||
const values: string[] = [];
|
|
||||||
while (i + 1 < args.length && !args[i + 1]?.startsWith("--")) {
|
|
||||||
i++;
|
|
||||||
values.push(args[i]!);
|
|
||||||
}
|
|
||||||
const joinedValues = values.join(ACCUMULATE_DELIMITER);
|
|
||||||
if (result[flag]) {
|
|
||||||
result[flag] =
|
|
||||||
`${result[flag]}${ACCUMULATE_DELIMITER}${joinedValues}`;
|
|
||||||
} else {
|
|
||||||
result[flag] = joinedValues;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
result[flag] = nextArg;
|
result[flag] = nextArg;
|
||||||
i++; // Skip the value
|
|
||||||
}
|
}
|
||||||
|
i++; // Skip the value
|
||||||
} else {
|
} else {
|
||||||
result[flag] = null; // Boolean flag
|
result[flag] = null; // Boolean flag
|
||||||
}
|
}
|
||||||
@@ -147,23 +68,12 @@ export function parseSdkOptions(options: ClaudeOptions): ParsedSdkOptions {
|
|||||||
// Detect if --json-schema is present (for hasJsonSchema flag)
|
// Detect if --json-schema is present (for hasJsonSchema flag)
|
||||||
const hasJsonSchema = "json-schema" in extraArgs;
|
const hasJsonSchema = "json-schema" in extraArgs;
|
||||||
|
|
||||||
// Extract and merge allowedTools from all sources:
|
// Extract and merge allowedTools from both sources:
|
||||||
// 1. From extraArgs (parsed from claudeArgs - contains tag mode's tools)
|
// 1. From extraArgs (parsed from claudeArgs - contains tag mode's tools)
|
||||||
// - Check both camelCase (--allowedTools) and hyphenated (--allowed-tools) variants
|
|
||||||
// 2. From options.allowedTools (direct input - may be undefined)
|
// 2. From options.allowedTools (direct input - may be undefined)
|
||||||
// This prevents duplicate flags being overwritten when claudeArgs contains --allowedTools
|
// This prevents duplicate flags being overwritten when claudeArgs contains --allowedTools
|
||||||
const allowedToolsValues = [
|
const extraArgsAllowedTools = extraArgs["allowedTools"]
|
||||||
extraArgs["allowedTools"],
|
? extraArgs["allowedTools"].split(",").map((t) => t.trim())
|
||||||
extraArgs["allowed-tools"],
|
|
||||||
]
|
|
||||||
.filter(Boolean)
|
|
||||||
.join(ACCUMULATE_DELIMITER);
|
|
||||||
const extraArgsAllowedTools = allowedToolsValues
|
|
||||||
? allowedToolsValues
|
|
||||||
.split(ACCUMULATE_DELIMITER)
|
|
||||||
.flatMap((v) => v.split(","))
|
|
||||||
.map((t) => t.trim())
|
|
||||||
.filter(Boolean)
|
|
||||||
: [];
|
: [];
|
||||||
const directAllowedTools = options.allowedTools
|
const directAllowedTools = options.allowedTools
|
||||||
? options.allowedTools.split(",").map((t) => t.trim())
|
? options.allowedTools.split(",").map((t) => t.trim())
|
||||||
@@ -172,21 +82,10 @@ export function parseSdkOptions(options: ClaudeOptions): ParsedSdkOptions {
|
|||||||
...new Set([...extraArgsAllowedTools, ...directAllowedTools]),
|
...new Set([...extraArgsAllowedTools, ...directAllowedTools]),
|
||||||
];
|
];
|
||||||
delete extraArgs["allowedTools"];
|
delete extraArgs["allowedTools"];
|
||||||
delete extraArgs["allowed-tools"];
|
|
||||||
|
|
||||||
// Same for disallowedTools - check both camelCase and hyphenated variants
|
// Same for disallowedTools
|
||||||
const disallowedToolsValues = [
|
const extraArgsDisallowedTools = extraArgs["disallowedTools"]
|
||||||
extraArgs["disallowedTools"],
|
? extraArgs["disallowedTools"].split(",").map((t) => t.trim())
|
||||||
extraArgs["disallowed-tools"],
|
|
||||||
]
|
|
||||||
.filter(Boolean)
|
|
||||||
.join(ACCUMULATE_DELIMITER);
|
|
||||||
const extraArgsDisallowedTools = disallowedToolsValues
|
|
||||||
? disallowedToolsValues
|
|
||||||
.split(ACCUMULATE_DELIMITER)
|
|
||||||
.flatMap((v) => v.split(","))
|
|
||||||
.map((t) => t.trim())
|
|
||||||
.filter(Boolean)
|
|
||||||
: [];
|
: [];
|
||||||
const directDisallowedTools = options.disallowedTools
|
const directDisallowedTools = options.disallowedTools
|
||||||
? options.disallowedTools.split(",").map((t) => t.trim())
|
? options.disallowedTools.split(",").map((t) => t.trim())
|
||||||
@@ -195,17 +94,6 @@ export function parseSdkOptions(options: ClaudeOptions): ParsedSdkOptions {
|
|||||||
...new Set([...extraArgsDisallowedTools, ...directDisallowedTools]),
|
...new Set([...extraArgsDisallowedTools, ...directDisallowedTools]),
|
||||||
];
|
];
|
||||||
delete extraArgs["disallowedTools"];
|
delete extraArgs["disallowedTools"];
|
||||||
delete extraArgs["disallowed-tools"];
|
|
||||||
|
|
||||||
// Merge multiple --mcp-config values by combining their mcpServers objects
|
|
||||||
// The action prepends its config (github_comment, github_ci, etc.) as inline JSON,
|
|
||||||
// and users may provide their own config as inline JSON or file path
|
|
||||||
if (extraArgs["mcp-config"]) {
|
|
||||||
const mcpConfigValues = extraArgs["mcp-config"].split(ACCUMULATE_DELIMITER);
|
|
||||||
if (mcpConfigValues.length > 1) {
|
|
||||||
extraArgs["mcp-config"] = mergeMcpConfigs(mcpConfigValues);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build custom environment
|
// Build custom environment
|
||||||
const env: Record<string, string | undefined> = { ...process.env };
|
const env: Record<string, string | undefined> = { ...process.env };
|
||||||
@@ -249,18 +137,10 @@ export function parseSdkOptions(options: ClaudeOptions): ParsedSdkOptions {
|
|||||||
extraArgs,
|
extraArgs,
|
||||||
env,
|
env,
|
||||||
|
|
||||||
// Load settings from sources - prefer user's --setting-sources if provided, otherwise use all sources
|
// Load settings from all sources to pick up CLI-installed plugins, CLAUDE.md, etc.
|
||||||
// This ensures users can override the default behavior (e.g., --setting-sources user to avoid in-repo configs)
|
settingSources: ["user", "project", "local"],
|
||||||
settingSources: extraArgs["setting-sources"]
|
|
||||||
? (extraArgs["setting-sources"].split(
|
|
||||||
",",
|
|
||||||
) as SdkOptions["settingSources"])
|
|
||||||
: ["user", "project", "local"],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Remove setting-sources from extraArgs to avoid passing it twice
|
|
||||||
delete extraArgs["setting-sources"];
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sdkOptions,
|
sdkOptions,
|
||||||
showFullOutput,
|
showFullOutput,
|
||||||
|
|||||||
@@ -9,18 +9,6 @@ import type { ParsedSdkOptions } from "./parse-sdk-options";
|
|||||||
|
|
||||||
const EXECUTION_FILE = `${process.env.RUNNER_TEMP}/claude-execution-output.json`;
|
const EXECUTION_FILE = `${process.env.RUNNER_TEMP}/claude-execution-output.json`;
|
||||||
|
|
||||||
/**
|
|
||||||
* Result of running Claude via SDK
|
|
||||||
*/
|
|
||||||
export type RunClaudeResult = {
|
|
||||||
success: boolean;
|
|
||||||
executionFile: string;
|
|
||||||
conclusion: "success" | "failure";
|
|
||||||
structuredOutput?: string;
|
|
||||||
sessionId?: string;
|
|
||||||
error?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sanitizes SDK output to match CLI sanitization behavior
|
* Sanitizes SDK output to match CLI sanitization behavior
|
||||||
*/
|
*/
|
||||||
@@ -69,31 +57,13 @@ function sanitizeSdkOutput(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Input for runClaudeWithSdk - either a prompt string or file path
|
* Run Claude using the Agent SDK
|
||||||
*/
|
|
||||||
export type PromptInput =
|
|
||||||
| { type: "string"; prompt: string }
|
|
||||||
| { type: "file"; promptPath: string };
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Run Claude using the Agent SDK.
|
|
||||||
*
|
|
||||||
* @param promptInput - Either a direct prompt string or path to prompt file
|
|
||||||
* @param parsedOptions - Parsed SDK options
|
|
||||||
* @param options - Additional options
|
|
||||||
* @param options.setOutputs - Whether to set GitHub Action outputs (default: true for backwards compat)
|
|
||||||
* @returns Result of the execution
|
|
||||||
*/
|
*/
|
||||||
export async function runClaudeWithSdk(
|
export async function runClaudeWithSdk(
|
||||||
promptInput: PromptInput,
|
promptPath: string,
|
||||||
{ sdkOptions, showFullOutput, hasJsonSchema }: ParsedSdkOptions,
|
{ sdkOptions, showFullOutput, hasJsonSchema }: ParsedSdkOptions,
|
||||||
{ setOutputs = true }: { setOutputs?: boolean } = {},
|
): Promise<void> {
|
||||||
): Promise<RunClaudeResult> {
|
const prompt = await readFile(promptPath, "utf-8");
|
||||||
// Get prompt from string or file
|
|
||||||
const prompt =
|
|
||||||
promptInput.type === "string"
|
|
||||||
? promptInput.prompt
|
|
||||||
: await readFile(promptInput.promptPath, "utf-8");
|
|
||||||
|
|
||||||
if (!showFullOutput) {
|
if (!showFullOutput) {
|
||||||
console.log(
|
console.log(
|
||||||
@@ -104,13 +74,7 @@ export async function runClaudeWithSdk(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (promptInput.type === "file") {
|
console.log(`Running Claude with prompt from file: ${promptPath}`);
|
||||||
console.log(
|
|
||||||
`Running Claude with prompt from file: ${promptInput.promptPath}`,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
console.log(`Running Claude with prompt string (${prompt.length} chars)`);
|
|
||||||
}
|
|
||||||
// Log SDK options without env (which could contain sensitive data)
|
// Log SDK options without env (which could contain sensitive data)
|
||||||
const { env, ...optionsToLog } = sdkOptions;
|
const { env, ...optionsToLog } = sdkOptions;
|
||||||
console.log("SDK options:", JSON.stringify(optionsToLog, null, 2));
|
console.log("SDK options:", JSON.stringify(optionsToLog, null, 2));
|
||||||
@@ -133,47 +97,27 @@ export async function runClaudeWithSdk(
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("SDK execution error:", error);
|
console.error("SDK execution error:", error);
|
||||||
if (setOutputs) {
|
core.setOutput("conclusion", "failure");
|
||||||
core.setOutput("conclusion", "failure");
|
process.exit(1);
|
||||||
}
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
executionFile: EXECUTION_FILE,
|
|
||||||
conclusion: "failure",
|
|
||||||
error: String(error),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write execution file
|
// Write execution file
|
||||||
try {
|
try {
|
||||||
await writeFile(EXECUTION_FILE, JSON.stringify(messages, null, 2));
|
await writeFile(EXECUTION_FILE, JSON.stringify(messages, null, 2));
|
||||||
console.log(`Log saved to ${EXECUTION_FILE}`);
|
console.log(`Log saved to ${EXECUTION_FILE}`);
|
||||||
if (setOutputs) {
|
core.setOutput("execution_file", EXECUTION_FILE);
|
||||||
core.setOutput("execution_file", EXECUTION_FILE);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
core.warning(`Failed to write execution file: ${error}`);
|
core.warning(`Failed to write execution file: ${error}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!resultMessage) {
|
if (!resultMessage) {
|
||||||
if (setOutputs) {
|
core.setOutput("conclusion", "failure");
|
||||||
core.setOutput("conclusion", "failure");
|
|
||||||
}
|
|
||||||
core.error("No result message received from Claude");
|
core.error("No result message received from Claude");
|
||||||
return {
|
process.exit(1);
|
||||||
success: false,
|
|
||||||
executionFile: EXECUTION_FILE,
|
|
||||||
conclusion: "failure",
|
|
||||||
error: "No result message received from Claude",
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const isSuccess = resultMessage.subtype === "success";
|
const isSuccess = resultMessage.subtype === "success";
|
||||||
if (setOutputs) {
|
core.setOutput("conclusion", isSuccess ? "success" : "failure");
|
||||||
core.setOutput("conclusion", isSuccess ? "success" : "failure");
|
|
||||||
}
|
|
||||||
|
|
||||||
let structuredOutput: string | undefined;
|
|
||||||
|
|
||||||
// Handle structured output
|
// Handle structured output
|
||||||
if (hasJsonSchema) {
|
if (hasJsonSchema) {
|
||||||
@@ -182,64 +126,26 @@ export async function runClaudeWithSdk(
|
|||||||
"structured_output" in resultMessage &&
|
"structured_output" in resultMessage &&
|
||||||
resultMessage.structured_output
|
resultMessage.structured_output
|
||||||
) {
|
) {
|
||||||
structuredOutput = JSON.stringify(resultMessage.structured_output);
|
const structuredOutputJson = JSON.stringify(
|
||||||
if (setOutputs) {
|
resultMessage.structured_output,
|
||||||
core.setOutput("structured_output", structuredOutput);
|
);
|
||||||
}
|
core.setOutput("structured_output", structuredOutputJson);
|
||||||
core.info(
|
core.info(
|
||||||
`Set structured_output with ${Object.keys(resultMessage.structured_output as object).length} field(s)`,
|
`Set structured_output with ${Object.keys(resultMessage.structured_output as object).length} field(s)`,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const errorMsg = `--json-schema was provided but Claude did not return structured_output. Result subtype: ${resultMessage.subtype}`;
|
core.setFailed(
|
||||||
if (setOutputs) {
|
`--json-schema was provided but Claude did not return structured_output. Result subtype: ${resultMessage.subtype}`,
|
||||||
core.setFailed(errorMsg);
|
);
|
||||||
core.setOutput("conclusion", "failure");
|
core.setOutput("conclusion", "failure");
|
||||||
}
|
process.exit(1);
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
executionFile: EXECUTION_FILE,
|
|
||||||
conclusion: "failure",
|
|
||||||
error: errorMsg,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isSuccess) {
|
if (!isSuccess) {
|
||||||
const errors =
|
if ("errors" in resultMessage && resultMessage.errors) {
|
||||||
"errors" in resultMessage && resultMessage.errors
|
core.error(`Execution failed: ${resultMessage.errors.join(", ")}`);
|
||||||
? resultMessage.errors.join(", ")
|
}
|
||||||
: "Unknown error";
|
|
||||||
core.error(`Execution failed: ${errors}`);
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
executionFile: EXECUTION_FILE,
|
|
||||||
conclusion: "failure",
|
|
||||||
error: errors,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
executionFile: EXECUTION_FILE,
|
|
||||||
conclusion: "success",
|
|
||||||
structuredOutput,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wrapper for backwards compatibility - reads prompt from file path and exits on failure
|
|
||||||
*/
|
|
||||||
export async function runClaudeWithSdkFromFile(
|
|
||||||
promptPath: string,
|
|
||||||
parsedOptions: ParsedSdkOptions,
|
|
||||||
): Promise<void> {
|
|
||||||
const result = await runClaudeWithSdk(
|
|
||||||
{ type: "file", promptPath },
|
|
||||||
parsedOptions,
|
|
||||||
{ setOutputs: true },
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!result.success) {
|
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { unlink, writeFile, stat, readFile } from "fs/promises";
|
|||||||
import { createWriteStream } from "fs";
|
import { createWriteStream } from "fs";
|
||||||
import { spawn } from "child_process";
|
import { spawn } from "child_process";
|
||||||
import { parse as parseShellArgs } from "shell-quote";
|
import { parse as parseShellArgs } from "shell-quote";
|
||||||
import { runClaudeWithSdkFromFile } from "./run-claude-sdk";
|
import { runClaudeWithSdk } from "./run-claude-sdk";
|
||||||
import { parseSdkOptions } from "./parse-sdk-options";
|
import { parseSdkOptions } from "./parse-sdk-options";
|
||||||
|
|
||||||
const execAsync = promisify(exec);
|
const execAsync = promisify(exec);
|
||||||
@@ -205,7 +205,7 @@ export async function runClaude(promptPath: string, options: ClaudeOptions) {
|
|||||||
|
|
||||||
if (useAgentSdk) {
|
if (useAgentSdk) {
|
||||||
const parsedOptions = parseSdkOptions(options);
|
const parsedOptions = parseSdkOptions(options);
|
||||||
return runClaudeWithSdkFromFile(promptPath, parsedOptions);
|
return runClaudeWithSdk(promptPath, parsedOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = prepareRunConfig(promptPath, options);
|
const config = prepareRunConfig(promptPath, options);
|
||||||
|
|||||||
@@ -108,48 +108,6 @@ describe("parseSdkOptions", () => {
|
|||||||
expect(result.sdkOptions.extraArgs?.["allowedTools"]).toBeUndefined();
|
expect(result.sdkOptions.extraArgs?.["allowedTools"]).toBeUndefined();
|
||||||
expect(result.sdkOptions.extraArgs?.["model"]).toBe("claude-3-5-sonnet");
|
expect(result.sdkOptions.extraArgs?.["model"]).toBe("claude-3-5-sonnet");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should handle hyphenated --allowed-tools flag", () => {
|
|
||||||
const options: ClaudeOptions = {
|
|
||||||
claudeArgs: '--allowed-tools "Edit,Read,Write"',
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = parseSdkOptions(options);
|
|
||||||
|
|
||||||
expect(result.sdkOptions.allowedTools).toEqual(["Edit", "Read", "Write"]);
|
|
||||||
expect(result.sdkOptions.extraArgs?.["allowed-tools"]).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should accumulate multiple --allowed-tools flags (hyphenated)", () => {
|
|
||||||
// This is the exact scenario from issue #746
|
|
||||||
const options: ClaudeOptions = {
|
|
||||||
claudeArgs:
|
|
||||||
'--allowed-tools "Bash(git log:*)" "Bash(git diff:*)" "Bash(git fetch:*)" "Bash(gh pr:*)"',
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = parseSdkOptions(options);
|
|
||||||
|
|
||||||
expect(result.sdkOptions.allowedTools).toEqual([
|
|
||||||
"Bash(git log:*)",
|
|
||||||
"Bash(git diff:*)",
|
|
||||||
"Bash(git fetch:*)",
|
|
||||||
"Bash(gh pr:*)",
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should handle mixed camelCase and hyphenated allowedTools flags", () => {
|
|
||||||
const options: ClaudeOptions = {
|
|
||||||
claudeArgs: '--allowedTools "Edit,Read" --allowed-tools "Write,Glob"',
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = parseSdkOptions(options);
|
|
||||||
|
|
||||||
// Both should be merged - note: order depends on which key is found first
|
|
||||||
expect(result.sdkOptions.allowedTools).toContain("Edit");
|
|
||||||
expect(result.sdkOptions.allowedTools).toContain("Read");
|
|
||||||
expect(result.sdkOptions.allowedTools).toContain("Write");
|
|
||||||
expect(result.sdkOptions.allowedTools).toContain("Glob");
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("disallowedTools merging", () => {
|
describe("disallowedTools merging", () => {
|
||||||
@@ -176,129 +134,19 @@ describe("parseSdkOptions", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("mcp-config merging", () => {
|
|
||||||
test("should pass through single mcp-config in extraArgs", () => {
|
|
||||||
const options: ClaudeOptions = {
|
|
||||||
claudeArgs: `--mcp-config '{"mcpServers":{"server1":{"command":"cmd1"}}}'`,
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = parseSdkOptions(options);
|
|
||||||
|
|
||||||
expect(result.sdkOptions.extraArgs?.["mcp-config"]).toBe(
|
|
||||||
'{"mcpServers":{"server1":{"command":"cmd1"}}}',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should merge multiple mcp-config flags with inline JSON", () => {
|
|
||||||
// Simulates action prepending its config, then user providing their own
|
|
||||||
const options: ClaudeOptions = {
|
|
||||||
claudeArgs: `--mcp-config '{"mcpServers":{"github_comment":{"command":"node","args":["server.js"]}}}' --mcp-config '{"mcpServers":{"user_server":{"command":"custom","args":["run"]}}}'`,
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = parseSdkOptions(options);
|
|
||||||
|
|
||||||
const mcpConfig = JSON.parse(
|
|
||||||
result.sdkOptions.extraArgs?.["mcp-config"] as string,
|
|
||||||
);
|
|
||||||
expect(mcpConfig.mcpServers).toHaveProperty("github_comment");
|
|
||||||
expect(mcpConfig.mcpServers).toHaveProperty("user_server");
|
|
||||||
expect(mcpConfig.mcpServers.github_comment.command).toBe("node");
|
|
||||||
expect(mcpConfig.mcpServers.user_server.command).toBe("custom");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should merge three mcp-config flags", () => {
|
|
||||||
const options: ClaudeOptions = {
|
|
||||||
claudeArgs: `--mcp-config '{"mcpServers":{"server1":{"command":"cmd1"}}}' --mcp-config '{"mcpServers":{"server2":{"command":"cmd2"}}}' --mcp-config '{"mcpServers":{"server3":{"command":"cmd3"}}}'`,
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = parseSdkOptions(options);
|
|
||||||
|
|
||||||
const mcpConfig = JSON.parse(
|
|
||||||
result.sdkOptions.extraArgs?.["mcp-config"] as string,
|
|
||||||
);
|
|
||||||
expect(mcpConfig.mcpServers).toHaveProperty("server1");
|
|
||||||
expect(mcpConfig.mcpServers).toHaveProperty("server2");
|
|
||||||
expect(mcpConfig.mcpServers).toHaveProperty("server3");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should handle mcp-config file path when no inline JSON exists", () => {
|
|
||||||
const options: ClaudeOptions = {
|
|
||||||
claudeArgs: `--mcp-config /tmp/user-mcp-config.json`,
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = parseSdkOptions(options);
|
|
||||||
|
|
||||||
expect(result.sdkOptions.extraArgs?.["mcp-config"]).toBe(
|
|
||||||
"/tmp/user-mcp-config.json",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should merge inline JSON configs when file path is also present", () => {
|
|
||||||
// When action provides inline JSON and user provides a file path,
|
|
||||||
// the inline JSON configs should be merged (file paths cannot be merged at parse time)
|
|
||||||
const options: ClaudeOptions = {
|
|
||||||
claudeArgs: `--mcp-config '{"mcpServers":{"github_comment":{"command":"node"}}}' --mcp-config '{"mcpServers":{"github_ci":{"command":"node"}}}' --mcp-config /tmp/user-config.json`,
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = parseSdkOptions(options);
|
|
||||||
|
|
||||||
// The inline JSON configs should be merged
|
|
||||||
const mcpConfig = JSON.parse(
|
|
||||||
result.sdkOptions.extraArgs?.["mcp-config"] as string,
|
|
||||||
);
|
|
||||||
expect(mcpConfig.mcpServers).toHaveProperty("github_comment");
|
|
||||||
expect(mcpConfig.mcpServers).toHaveProperty("github_ci");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should handle mcp-config with other flags", () => {
|
|
||||||
const options: ClaudeOptions = {
|
|
||||||
claudeArgs: `--mcp-config '{"mcpServers":{"server1":{}}}' --model claude-3-5-sonnet --mcp-config '{"mcpServers":{"server2":{}}}'`,
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = parseSdkOptions(options);
|
|
||||||
|
|
||||||
const mcpConfig = JSON.parse(
|
|
||||||
result.sdkOptions.extraArgs?.["mcp-config"] as string,
|
|
||||||
);
|
|
||||||
expect(mcpConfig.mcpServers).toHaveProperty("server1");
|
|
||||||
expect(mcpConfig.mcpServers).toHaveProperty("server2");
|
|
||||||
expect(result.sdkOptions.extraArgs?.["model"]).toBe("claude-3-5-sonnet");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should handle real-world scenario: action config + user config", () => {
|
|
||||||
// This is the exact scenario from the bug report
|
|
||||||
const actionConfig = JSON.stringify({
|
|
||||||
mcpServers: {
|
|
||||||
github_comment: {
|
|
||||||
command: "node",
|
|
||||||
args: ["github-comment-server.js"],
|
|
||||||
},
|
|
||||||
github_ci: { command: "node", args: ["github-ci-server.js"] },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const userConfig = JSON.stringify({
|
|
||||||
mcpServers: {
|
|
||||||
my_custom_server: { command: "python", args: ["server.py"] },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const options: ClaudeOptions = {
|
|
||||||
claudeArgs: `--mcp-config '${actionConfig}' --mcp-config '${userConfig}'`,
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = parseSdkOptions(options);
|
|
||||||
|
|
||||||
const mcpConfig = JSON.parse(
|
|
||||||
result.sdkOptions.extraArgs?.["mcp-config"] as string,
|
|
||||||
);
|
|
||||||
// All servers should be present
|
|
||||||
expect(mcpConfig.mcpServers).toHaveProperty("github_comment");
|
|
||||||
expect(mcpConfig.mcpServers).toHaveProperty("github_ci");
|
|
||||||
expect(mcpConfig.mcpServers).toHaveProperty("my_custom_server");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("other extraArgs passthrough", () => {
|
describe("other extraArgs passthrough", () => {
|
||||||
|
test("should pass through mcp-config in extraArgs", () => {
|
||||||
|
const options: ClaudeOptions = {
|
||||||
|
claudeArgs: `--mcp-config '{"mcpServers":{}}' --allowedTools "Edit"`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = parseSdkOptions(options);
|
||||||
|
|
||||||
|
expect(result.sdkOptions.extraArgs?.["mcp-config"]).toBe(
|
||||||
|
'{"mcpServers":{}}',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test("should pass through json-schema in extraArgs", () => {
|
test("should pass through json-schema in extraArgs", () => {
|
||||||
const options: ClaudeOptions = {
|
const options: ClaudeOptions = {
|
||||||
claudeArgs: `--json-schema '{"type":"object"}'`,
|
claudeArgs: `--json-schema '{"type":"object"}'`,
|
||||||
|
|||||||
5
bun.lock
5
bun.lock
@@ -1,13 +1,12 @@
|
|||||||
{
|
{
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"configVersion": 0,
|
|
||||||
"workspaces": {
|
"workspaces": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@anthropic-ai/claude-code-action",
|
"name": "@anthropic-ai/claude-code-action",
|
||||||
"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.1.74",
|
"@anthropic-ai/claude-agent-sdk": "^0.1.52",
|
||||||
"@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 +36,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.1.74", "", { "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": "^3.24.1 || ^4.0.0" } }, "sha512-d6H3Oo625WAG3BrBFKJsuSshi4f0amc0kTJTm83LRPPFxn9kfq58FX4Oxxt+RUD9N3QumW9sQSEDnri20/F4qQ=="],
|
"@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.1.52", "", { "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": "^3.24.1" } }, "sha512-yF8N05+9NRbqYA/h39jQ726HTQFrdXXp7pEfDNKIJ2c4FdWvEjxBA/8ciZIebN6/PyvGDcbEp3yq2Co4rNpg6A=="],
|
||||||
|
|
||||||
"@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="],
|
"@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="],
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ You can authenticate with Claude using any of these four methods:
|
|||||||
3. Google Vertex AI with OIDC authentication
|
3. Google Vertex AI with OIDC authentication
|
||||||
4. Microsoft Foundry with OIDC authentication
|
4. Microsoft Foundry with OIDC authentication
|
||||||
|
|
||||||
For detailed setup instructions for AWS Bedrock and Google Vertex AI, see the [official documentation](https://code.claude.com/docs/en/github-actions#for-aws-bedrock:).
|
For detailed setup instructions for AWS Bedrock and Google Vertex AI, see the [official documentation](https://docs.anthropic.com/en/docs/claude-code/github-actions#using-with-aws-bedrock-%26-google-vertex-ai).
|
||||||
|
|
||||||
**Note**:
|
**Note**:
|
||||||
|
|
||||||
|
|||||||
@@ -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.1.74",
|
"@anthropic-ai/claude-agent-sdk": "^0.1.52",
|
||||||
"@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",
|
||||||
|
|||||||
@@ -563,9 +563,22 @@ ${getCommitInstructions(eventData, githubData, context, useCommitSigning)}
|
|||||||
${
|
${
|
||||||
eventData.claudeBranch
|
eventData.claudeBranch
|
||||||
? `
|
? `
|
||||||
When done with changes, provide a PR link:
|
When done with changes:
|
||||||
|
1. Run git log origin/${eventData.baseBranch}..HEAD and git diff origin/${eventData.baseBranch}...HEAD to understand ALL commits
|
||||||
|
2. Draft a PR summary analyzing ALL changes (not just the latest commit)
|
||||||
|
3. Provide a PR link:
|
||||||
[Create a PR](${GITHUB_SERVER_URL}/${context.repository}/compare/${eventData.baseBranch}...${eventData.claudeBranch}?quick_pull=1&title=<url-encoded-title>&body=<url-encoded-body>)
|
[Create a PR](${GITHUB_SERVER_URL}/${context.repository}/compare/${eventData.baseBranch}...${eventData.claudeBranch}?quick_pull=1&title=<url-encoded-title>&body=<url-encoded-body>)
|
||||||
Use THREE dots (...) between branches. URL-encode all parameters.`
|
Use THREE dots (...) between branches. URL-encode all parameters.
|
||||||
|
PR body format:
|
||||||
|
## Summary
|
||||||
|
<1-3 bullet points>
|
||||||
|
|
||||||
|
## Test plan
|
||||||
|
<Checklist of testing TODOs>
|
||||||
|
|
||||||
|
Fixes #<issue-number>
|
||||||
|
|
||||||
|
Generated with [Claude Code](https://claude.ai/code)`
|
||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -743,8 +756,13 @@ ${eventData.eventName === "issue_comment" || eventData.eventName === "pull_reque
|
|||||||
- Mark each subtask as completed as you progress.${getCommitInstructions(eventData, githubData, context, useCommitSigning)}
|
- Mark each subtask as completed as you progress.${getCommitInstructions(eventData, githubData, context, useCommitSigning)}
|
||||||
${
|
${
|
||||||
eventData.claudeBranch
|
eventData.claudeBranch
|
||||||
? `- Provide a URL to create a PR manually in this format:
|
? `- When creating a pull request, follow these steps:
|
||||||
[Create a PR](${GITHUB_SERVER_URL}/${context.repository}/compare/${eventData.baseBranch}...<branch-name>?quick_pull=1&title=<url-encoded-title>&body=<url-encoded-body>)
|
1. Use git log and git diff to understand the full commit history for the current branch (from the time it diverged from the base branch):
|
||||||
|
- Run: git log origin/${eventData.baseBranch}..HEAD
|
||||||
|
- Run: git diff origin/${eventData.baseBranch}...HEAD
|
||||||
|
2. Analyze ALL changes that will be included in the pull request, making sure to look at all relevant commits (NOT just the latest commit, but ALL commits that will be included in the pull request), and draft a pull request summary
|
||||||
|
3. Provide a URL to create a PR manually in this format:
|
||||||
|
[Create a PR](${GITHUB_SERVER_URL}/${context.repository}/compare/${eventData.baseBranch}...<branch-name>?quick_pull=1&title=<url-encoded-title>&body=<url-encoded-body>)
|
||||||
- IMPORTANT: Use THREE dots (...) between branch names, not two (..)
|
- IMPORTANT: Use THREE dots (...) between branch names, not two (..)
|
||||||
Example: ${GITHUB_SERVER_URL}/${context.repository}/compare/main...feature-branch (correct)
|
Example: ${GITHUB_SERVER_URL}/${context.repository}/compare/main...feature-branch (correct)
|
||||||
NOT: ${GITHUB_SERVER_URL}/${context.repository}/compare/main..feature-branch (incorrect)
|
NOT: ${GITHUB_SERVER_URL}/${context.repository}/compare/main..feature-branch (incorrect)
|
||||||
@@ -752,10 +770,16 @@ ${eventData.eventName === "issue_comment" || eventData.eventName === "pull_reque
|
|||||||
Example: Instead of "fix: update welcome message", use "fix%3A%20update%20welcome%20message"
|
Example: Instead of "fix: update welcome message", use "fix%3A%20update%20welcome%20message"
|
||||||
- The target-branch should be '${eventData.baseBranch}'.
|
- The target-branch should be '${eventData.baseBranch}'.
|
||||||
- The branch-name is the current branch: ${eventData.claudeBranch}
|
- The branch-name is the current branch: ${eventData.claudeBranch}
|
||||||
- The body should include:
|
- The PR body MUST follow this format:
|
||||||
- A clear description of the changes
|
## Summary
|
||||||
- Reference to the original ${eventData.isPR ? "PR" : "issue"}
|
<1-3 bullet points summarizing the changes>
|
||||||
- The signature: "Generated with [Claude Code](https://claude.ai/code)"
|
|
||||||
|
## Test plan
|
||||||
|
<Bulleted markdown checklist of TODOs for testing the pull request>
|
||||||
|
|
||||||
|
Fixes #<issue-number>
|
||||||
|
|
||||||
|
Generated with [Claude Code](https://claude.ai/code)
|
||||||
- Just include the markdown link with text "Create a PR" - do not add explanatory text before it like "You can create a PR using this link"`
|
- Just include the markdown link with text "Create a PR" - do not add explanatory text before it like "You can create a PR using this link"`
|
||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
@@ -841,78 +865,6 @@ f. If you are unable to complete certain steps, such as running a linter or test
|
|||||||
return promptContent;
|
return promptContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Result of generating prompt content
|
|
||||||
*/
|
|
||||||
export type PromptResult = {
|
|
||||||
promptContent: string;
|
|
||||||
allowedTools: string;
|
|
||||||
disallowedTools: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate prompt content and tool configurations.
|
|
||||||
* This function can be used directly without side effects (no file writes, no env vars).
|
|
||||||
*/
|
|
||||||
export function generatePromptContent(
|
|
||||||
mode: Mode,
|
|
||||||
modeContext: ModeContext,
|
|
||||||
githubData: FetchDataResult,
|
|
||||||
context: ParsedGitHubContext,
|
|
||||||
): PromptResult {
|
|
||||||
// Prepare the context for prompt generation
|
|
||||||
let claudeCommentId: string = "";
|
|
||||||
if (mode.name === "tag") {
|
|
||||||
if (!modeContext.commentId) {
|
|
||||||
throw new Error(
|
|
||||||
`${mode.name} mode requires a comment ID for prompt generation`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
claudeCommentId = modeContext.commentId.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
const preparedContext = prepareContext(
|
|
||||||
context,
|
|
||||||
claudeCommentId,
|
|
||||||
modeContext.baseBranch,
|
|
||||||
modeContext.claudeBranch,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Generate the prompt directly
|
|
||||||
const promptContent = generatePrompt(
|
|
||||||
preparedContext,
|
|
||||||
githubData,
|
|
||||||
context.inputs.useCommitSigning,
|
|
||||||
mode,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Get mode-specific tools
|
|
||||||
const modeAllowedTools = mode.getAllowedTools();
|
|
||||||
const modeDisallowedTools = mode.getDisallowedTools();
|
|
||||||
|
|
||||||
const hasActionsReadPermission = false;
|
|
||||||
const allowedTools = buildAllowedToolsString(
|
|
||||||
modeAllowedTools,
|
|
||||||
hasActionsReadPermission,
|
|
||||||
context.inputs.useCommitSigning,
|
|
||||||
);
|
|
||||||
const disallowedTools = buildDisallowedToolsString(
|
|
||||||
modeDisallowedTools,
|
|
||||||
modeAllowedTools,
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
promptContent,
|
|
||||||
allowedTools,
|
|
||||||
disallowedTools,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create prompt and write to file.
|
|
||||||
* This is the legacy function that writes files and sets environment variables.
|
|
||||||
* For the unified step, use generatePromptContent() instead.
|
|
||||||
*/
|
|
||||||
export async function createPrompt(
|
export async function createPrompt(
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
modeContext: ModeContext,
|
modeContext: ModeContext,
|
||||||
@@ -920,30 +872,66 @@ export async function createPrompt(
|
|||||||
context: ParsedGitHubContext,
|
context: ParsedGitHubContext,
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const result = generatePromptContent(
|
// Prepare the context for prompt generation
|
||||||
mode,
|
let claudeCommentId: string = "";
|
||||||
modeContext,
|
if (mode.name === "tag") {
|
||||||
githubData,
|
if (!modeContext.commentId) {
|
||||||
|
throw new Error(
|
||||||
|
`${mode.name} mode requires a comment ID for prompt generation`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
claudeCommentId = modeContext.commentId.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
const preparedContext = prepareContext(
|
||||||
context,
|
context,
|
||||||
|
claudeCommentId,
|
||||||
|
modeContext.baseBranch,
|
||||||
|
modeContext.claudeBranch,
|
||||||
|
);
|
||||||
|
|
||||||
|
await mkdir(`${process.env.RUNNER_TEMP || "/tmp"}/claude-prompts`, {
|
||||||
|
recursive: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Generate the prompt directly
|
||||||
|
const promptContent = generatePrompt(
|
||||||
|
preparedContext,
|
||||||
|
githubData,
|
||||||
|
context.inputs.useCommitSigning,
|
||||||
|
mode,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Log the final prompt to console
|
// Log the final prompt to console
|
||||||
console.log("===== FINAL PROMPT =====");
|
console.log("===== FINAL PROMPT =====");
|
||||||
console.log(result.promptContent);
|
console.log(promptContent);
|
||||||
console.log("=======================");
|
console.log("=======================");
|
||||||
|
|
||||||
// Write the prompt file
|
// Write the prompt file
|
||||||
await mkdir(`${process.env.RUNNER_TEMP || "/tmp"}/claude-prompts`, {
|
|
||||||
recursive: true,
|
|
||||||
});
|
|
||||||
await writeFile(
|
await writeFile(
|
||||||
`${process.env.RUNNER_TEMP || "/tmp"}/claude-prompts/claude-prompt.txt`,
|
`${process.env.RUNNER_TEMP || "/tmp"}/claude-prompts/claude-prompt.txt`,
|
||||||
result.promptContent,
|
promptContent,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Set environment variables
|
// Set allowed tools
|
||||||
core.exportVariable("ALLOWED_TOOLS", result.allowedTools);
|
const hasActionsReadPermission = false;
|
||||||
core.exportVariable("DISALLOWED_TOOLS", result.disallowedTools);
|
|
||||||
|
// Get mode-specific tools
|
||||||
|
const modeAllowedTools = mode.getAllowedTools();
|
||||||
|
const modeDisallowedTools = mode.getDisallowedTools();
|
||||||
|
|
||||||
|
const allAllowedTools = buildAllowedToolsString(
|
||||||
|
modeAllowedTools,
|
||||||
|
hasActionsReadPermission,
|
||||||
|
context.inputs.useCommitSigning,
|
||||||
|
);
|
||||||
|
const allDisallowedTools = buildDisallowedToolsString(
|
||||||
|
modeDisallowedTools,
|
||||||
|
modeAllowedTools,
|
||||||
|
);
|
||||||
|
|
||||||
|
core.exportVariable("ALLOWED_TOOLS", allAllowedTools);
|
||||||
|
core.exportVariable("DISALLOWED_TOOLS", allDisallowedTools);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
core.setFailed(`Create prompt failed with error: ${error}`);
|
core.setFailed(`Create prompt failed with error: ${error}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
|||||||
@@ -1,186 +0,0 @@
|
|||||||
#!/usr/bin/env bun
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unified entry point for the Claude action.
|
|
||||||
*
|
|
||||||
* This combines the prepare and run phases into a single step,
|
|
||||||
* passing data directly in-memory instead of via files and outputs.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import * as core from "@actions/core";
|
|
||||||
import { setupGitHubToken } from "../github/token";
|
|
||||||
import { checkWritePermissions } from "../github/validation/permissions";
|
|
||||||
import { createOctokit } from "../github/api/client";
|
|
||||||
import { parseGitHubContext, isEntityContext } from "../github/context";
|
|
||||||
import { getMode } from "../modes/registry";
|
|
||||||
import { prepare } from "../prepare";
|
|
||||||
import { collectActionInputsPresence } from "./collect-inputs";
|
|
||||||
import {
|
|
||||||
runClaudeWithSdk,
|
|
||||||
setupClaudeCodeSettings,
|
|
||||||
installPlugins,
|
|
||||||
parseSdkOptions,
|
|
||||||
} from "../../base-action/src/lib";
|
|
||||||
|
|
||||||
async function run() {
|
|
||||||
try {
|
|
||||||
// ============================================
|
|
||||||
// PHASE 1: PREPARE
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
collectActionInputsPresence();
|
|
||||||
|
|
||||||
// Parse GitHub context first to enable mode detection
|
|
||||||
const context = parseGitHubContext();
|
|
||||||
|
|
||||||
// Auto-detect mode based on context
|
|
||||||
const mode = getMode(context);
|
|
||||||
|
|
||||||
// Setup GitHub token
|
|
||||||
const githubToken = await setupGitHubToken();
|
|
||||||
const octokit = createOctokit(githubToken);
|
|
||||||
|
|
||||||
// Check write permissions (only for entity contexts)
|
|
||||||
if (isEntityContext(context)) {
|
|
||||||
const githubTokenProvided = !!process.env.OVERRIDE_GITHUB_TOKEN;
|
|
||||||
const hasWritePermissions = await checkWritePermissions(
|
|
||||||
octokit.rest,
|
|
||||||
context,
|
|
||||||
context.inputs.allowedNonWriteUsers,
|
|
||||||
githubTokenProvided,
|
|
||||||
);
|
|
||||||
if (!hasWritePermissions) {
|
|
||||||
throw new Error(
|
|
||||||
"Actor does not have write permissions to the repository",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check trigger conditions
|
|
||||||
const containsTrigger = mode.shouldTrigger(context);
|
|
||||||
|
|
||||||
// Debug logging
|
|
||||||
console.log(`Mode: ${mode.name}`);
|
|
||||||
console.log(`Context prompt: ${context.inputs?.prompt || "NO PROMPT"}`);
|
|
||||||
console.log(`Trigger result: ${containsTrigger}`);
|
|
||||||
|
|
||||||
// Set output for action.yml to check
|
|
||||||
core.setOutput("contains_trigger", containsTrigger.toString());
|
|
||||||
|
|
||||||
if (!containsTrigger) {
|
|
||||||
console.log("No trigger found, skipping remaining steps");
|
|
||||||
core.setOutput("GITHUB_TOKEN", githubToken);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run mode.prepare() - returns prompt, commentId, branchInfo, etc.
|
|
||||||
const prepareResult = await prepare({
|
|
||||||
context,
|
|
||||||
octokit,
|
|
||||||
mode,
|
|
||||||
githubToken,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set outputs that may be needed by subsequent steps
|
|
||||||
core.setOutput("GITHUB_TOKEN", githubToken);
|
|
||||||
if (prepareResult.commentId) {
|
|
||||||
core.setOutput("claude_comment_id", prepareResult.commentId.toString());
|
|
||||||
}
|
|
||||||
core.setOutput(
|
|
||||||
"CLAUDE_BRANCH",
|
|
||||||
prepareResult.branchInfo.claudeBranch || "",
|
|
||||||
);
|
|
||||||
core.setOutput("BASE_BRANCH", prepareResult.branchInfo.baseBranch);
|
|
||||||
|
|
||||||
// Get system prompt from mode if available
|
|
||||||
let appendSystemPrompt: string | undefined;
|
|
||||||
if (mode.getSystemPrompt) {
|
|
||||||
const modeContext = mode.prepareContext(context, {
|
|
||||||
commentId: prepareResult.commentId,
|
|
||||||
baseBranch: prepareResult.branchInfo.baseBranch,
|
|
||||||
claudeBranch: prepareResult.branchInfo.claudeBranch,
|
|
||||||
});
|
|
||||||
appendSystemPrompt = mode.getSystemPrompt(modeContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================
|
|
||||||
// PHASE 2: SETUP
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
// Setup Claude Code settings
|
|
||||||
await setupClaudeCodeSettings(
|
|
||||||
process.env.INPUT_SETTINGS,
|
|
||||||
undefined, // homeDir
|
|
||||||
);
|
|
||||||
|
|
||||||
// Install Claude Code plugins if specified
|
|
||||||
await installPlugins(
|
|
||||||
process.env.INPUT_PLUGIN_MARKETPLACES,
|
|
||||||
process.env.INPUT_PLUGINS,
|
|
||||||
process.env.INPUT_PATH_TO_CLAUDE_CODE_EXECUTABLE,
|
|
||||||
);
|
|
||||||
|
|
||||||
// ============================================
|
|
||||||
// PHASE 3: EXECUTE
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
// Get prompt content from prepare result
|
|
||||||
const promptContent = prepareResult.promptContent;
|
|
||||||
if (!promptContent) {
|
|
||||||
throw new Error("No prompt content generated by prepare phase");
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("===== PROMPT CONTENT =====");
|
|
||||||
console.log(`Prompt length: ${promptContent.length} chars`);
|
|
||||||
console.log("==========================");
|
|
||||||
|
|
||||||
// Build SDK options from environment and prepare result
|
|
||||||
const sdkOptions = parseSdkOptions({
|
|
||||||
claudeArgs: process.env.INPUT_CLAUDE_ARGS,
|
|
||||||
allowedTools:
|
|
||||||
prepareResult.allowedTools || process.env.INPUT_ALLOWED_TOOLS,
|
|
||||||
disallowedTools:
|
|
||||||
prepareResult.disallowedTools || process.env.INPUT_DISALLOWED_TOOLS,
|
|
||||||
maxTurns: process.env.INPUT_MAX_TURNS,
|
|
||||||
mcpConfig: process.env.INPUT_MCP_CONFIG,
|
|
||||||
systemPrompt: process.env.INPUT_SYSTEM_PROMPT,
|
|
||||||
appendSystemPrompt:
|
|
||||||
appendSystemPrompt || process.env.INPUT_APPEND_SYSTEM_PROMPT,
|
|
||||||
claudeEnv: process.env.INPUT_CLAUDE_ENV,
|
|
||||||
fallbackModel: process.env.INPUT_FALLBACK_MODEL,
|
|
||||||
model: process.env.ANTHROPIC_MODEL,
|
|
||||||
pathToClaudeCodeExecutable:
|
|
||||||
process.env.INPUT_PATH_TO_CLAUDE_CODE_EXECUTABLE,
|
|
||||||
showFullOutput: process.env.INPUT_SHOW_FULL_OUTPUT,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Run Claude with prompt string directly
|
|
||||||
const execResult = await runClaudeWithSdk(
|
|
||||||
{ type: "string", prompt: promptContent },
|
|
||||||
sdkOptions,
|
|
||||||
{ setOutputs: true },
|
|
||||||
);
|
|
||||||
|
|
||||||
// Set additional outputs
|
|
||||||
core.setOutput("execution_file", execResult.executionFile);
|
|
||||||
core.setOutput("conclusion", execResult.conclusion);
|
|
||||||
if (execResult.structuredOutput) {
|
|
||||||
core.setOutput("structured_output", execResult.structuredOutput);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!execResult.success) {
|
|
||||||
core.setFailed(`Claude execution failed: ${execResult.error}`);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
||||||
core.setFailed(`Run step failed with error: ${errorMessage}`);
|
|
||||||
core.setOutput("prepare_error", errorMessage);
|
|
||||||
core.setOutput("conclusion", "failure");
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (import.meta.main) {
|
|
||||||
run();
|
|
||||||
}
|
|
||||||
@@ -95,15 +95,16 @@ export const agentMode: Mode = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate prompt content - use the user's prompt directly
|
// Create prompt directory
|
||||||
|
await mkdir(`${process.env.RUNNER_TEMP || "/tmp"}/claude-prompts`, {
|
||||||
|
recursive: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Write the prompt file - use the user's prompt directly
|
||||||
const promptContent =
|
const promptContent =
|
||||||
context.inputs.prompt ||
|
context.inputs.prompt ||
|
||||||
`Repository: ${context.repository.owner}/${context.repository.repo}`;
|
`Repository: ${context.repository.owner}/${context.repository.repo}`;
|
||||||
|
|
||||||
// Also write file for backwards compatibility with current flow
|
|
||||||
await mkdir(`${process.env.RUNNER_TEMP || "/tmp"}/claude-prompts`, {
|
|
||||||
recursive: true,
|
|
||||||
});
|
|
||||||
await writeFile(
|
await writeFile(
|
||||||
`${process.env.RUNNER_TEMP || "/tmp"}/claude-prompts/claude-prompt.txt`,
|
`${process.env.RUNNER_TEMP || "/tmp"}/claude-prompts/claude-prompt.txt`,
|
||||||
promptContent,
|
promptContent,
|
||||||
@@ -161,8 +162,6 @@ export const agentMode: Mode = {
|
|||||||
claudeBranch: claudeBranch,
|
claudeBranch: claudeBranch,
|
||||||
},
|
},
|
||||||
mcpConfig: ourMcpConfig,
|
mcpConfig: ourMcpConfig,
|
||||||
promptContent,
|
|
||||||
// Agent mode doesn't use the same allowed/disallowed tools mechanism as tag mode
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -10,11 +10,7 @@ import {
|
|||||||
fetchGitHubData,
|
fetchGitHubData,
|
||||||
extractTriggerTimestamp,
|
extractTriggerTimestamp,
|
||||||
} from "../../github/data/fetcher";
|
} from "../../github/data/fetcher";
|
||||||
import {
|
import { createPrompt, generateDefaultPrompt } from "../../create-prompt";
|
||||||
createPrompt,
|
|
||||||
generateDefaultPrompt,
|
|
||||||
generatePromptContent,
|
|
||||||
} from "../../create-prompt";
|
|
||||||
import { isEntityContext } from "../../github/context";
|
import { isEntityContext } from "../../github/context";
|
||||||
import type { PreparedContext } from "../../create-prompt/types";
|
import type { PreparedContext } from "../../create-prompt/types";
|
||||||
import type { FetchDataResult } from "../../github/data/fetcher";
|
import type { FetchDataResult } from "../../github/data/fetcher";
|
||||||
@@ -108,22 +104,13 @@ export const tagMode: Mode = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create prompt
|
// Create prompt file
|
||||||
const modeContext = this.prepareContext(context, {
|
const modeContext = this.prepareContext(context, {
|
||||||
commentId,
|
commentId,
|
||||||
baseBranch: branchInfo.baseBranch,
|
baseBranch: branchInfo.baseBranch,
|
||||||
claudeBranch: branchInfo.claudeBranch,
|
claudeBranch: branchInfo.claudeBranch,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Generate prompt content - returns data instead of writing file
|
|
||||||
const promptResult = generatePromptContent(
|
|
||||||
tagMode,
|
|
||||||
modeContext,
|
|
||||||
githubData,
|
|
||||||
context,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Also write file for backwards compatibility with current flow
|
|
||||||
await createPrompt(tagMode, modeContext, githubData, context);
|
await createPrompt(tagMode, modeContext, githubData, context);
|
||||||
|
|
||||||
const userClaudeArgs = process.env.CLAUDE_ARGS || "";
|
const userClaudeArgs = process.env.CLAUDE_ARGS || "";
|
||||||
@@ -201,9 +188,6 @@ export const tagMode: Mode = {
|
|||||||
commentId,
|
commentId,
|
||||||
branchInfo,
|
branchInfo,
|
||||||
mcpConfig: ourMcpConfig,
|
mcpConfig: ourMcpConfig,
|
||||||
promptContent: promptResult.promptContent,
|
|
||||||
allowedTools: promptResult.allowedTools,
|
|
||||||
disallowedTools: promptResult.disallowedTools,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -97,10 +97,4 @@ export type ModeResult = {
|
|||||||
currentBranch: string;
|
currentBranch: string;
|
||||||
};
|
};
|
||||||
mcpConfig: string;
|
mcpConfig: string;
|
||||||
/** Generated prompt content for Claude */
|
|
||||||
promptContent?: string;
|
|
||||||
/** Comma-separated list of allowed tools */
|
|
||||||
allowedTools?: string;
|
|
||||||
/** Comma-separated list of disallowed tools */
|
|
||||||
disallowedTools?: string;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,12 +10,6 @@ export type PrepareResult = {
|
|||||||
currentBranch: string;
|
currentBranch: string;
|
||||||
};
|
};
|
||||||
mcpConfig: string;
|
mcpConfig: string;
|
||||||
/** Generated prompt content for Claude */
|
|
||||||
promptContent?: string;
|
|
||||||
/** Comma-separated list of allowed tools */
|
|
||||||
allowedTools?: string;
|
|
||||||
/** Comma-separated list of disallowed tools */
|
|
||||||
disallowedTools?: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PrepareOptions = {
|
export type PrepareOptions = {
|
||||||
|
|||||||
@@ -177,7 +177,6 @@ describe("Agent Mode", () => {
|
|||||||
claudeBranch: undefined,
|
claudeBranch: undefined,
|
||||||
},
|
},
|
||||||
mcpConfig: expect.any(String),
|
mcpConfig: expect.any(String),
|
||||||
promptContent: expect.any(String),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Clean up
|
// Clean up
|
||||||
|
|||||||
Reference in New Issue
Block a user