Compare commits

..

1 Commits

Author SHA1 Message Date
Claude
a86ef08036 fix: prevent command injection in workflow example files
Fixes command injection vulnerabilities in example workflow files by using
environment variables instead of direct template expansion in shell commands.

This prevents malicious branch names containing command substitution syntax
like $(cmd) from being executed by the shell.

Files fixed:
- examples/ci-failure-auto-fix.yml: github.event.workflow_run.head_branch
- examples/test-failure-analysis.yml: github.event.workflow_run.name and head_branch
2025-12-16 01:37:30 +00:00
22 changed files with 181 additions and 462 deletions

View 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 }}

View File

@@ -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"

View File

@@ -93,10 +93,6 @@ inputs:
description: "Force tag mode with tracking comments for pull_request and issue events. Only applicable to pull_request (opened, synchronize, ready_for_review, reopened) and issue (opened, edited, labeled, assigned) events." description: "Force tag mode with tracking comments for pull_request and issue events. Only applicable to pull_request (opened, synchronize, ready_for_review, reopened) and issue (opened, edited, labeled, assigned) events."
required: false required: false
default: "false" default: "false"
include_fix_links:
description: "Include 'Fix this' links in PR code review feedback that open Claude Code with context to fix the identified issue"
required: false
default: "true"
path_to_claude_code_executable: path_to_claude_code_executable:
description: "Optional path to a custom Claude Code executable. If provided, skips automatic installation and uses this executable instead. WARNING: Using an older version may cause problems if the action begins taking advantage of new Claude Code features. This input is typically not needed unless you're debugging something specific or have unique needs in your environment." description: "Optional path to a custom Claude Code executable. If provided, skips automatic installation and uses this executable instead. WARNING: Using an older version may cause problems if the action begins taking advantage of new Claude Code features. This input is typically not needed unless you're debugging something specific or have unique needs in your environment."
required: false required: false
@@ -184,7 +180,6 @@ runs:
BOT_ID: ${{ inputs.bot_id }} BOT_ID: ${{ inputs.bot_id }}
BOT_NAME: ${{ inputs.bot_name }} BOT_NAME: ${{ inputs.bot_name }}
TRACK_PROGRESS: ${{ inputs.track_progress }} TRACK_PROGRESS: ${{ inputs.track_progress }}
INCLUDE_FIX_LINKS: ${{ inputs.include_fix_links }}
ADDITIONAL_PERMISSIONS: ${{ inputs.additional_permissions }} ADDITIONAL_PERMISSIONS: ${{ inputs.additional_permissions }}
CLAUDE_ARGS: ${{ inputs.claude_args }} CLAUDE_ARGS: ${{ inputs.claude_args }}
ALL_INPUTS: ${{ toJson(inputs) }} ALL_INPUTS: ${{ toJson(inputs) }}
@@ -203,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.76" CLAUDE_CODE_VERSION="2.0.70"
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..."
@@ -252,7 +247,6 @@ runs:
# Model configuration # Model configuration
GITHUB_TOKEN: ${{ steps.prepare.outputs.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ steps.prepare.outputs.GITHUB_TOKEN }}
GH_TOKEN: ${{ steps.prepare.outputs.GITHUB_TOKEN }}
NODE_VERSION: ${{ env.NODE_VERSION }} NODE_VERSION: ${{ env.NODE_VERSION }}
DETAILED_PERMISSION_MESSAGES: "1" DETAILED_PERMISSION_MESSAGES: "1"
@@ -302,7 +296,6 @@ runs:
CLAUDE_COMMENT_ID: ${{ steps.prepare.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.prepare.outputs.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ steps.prepare.outputs.GITHUB_TOKEN }}
GH_TOKEN: ${{ steps.prepare.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.prepare.outputs.CLAUDE_BRANCH }} CLAUDE_BRANCH: ${{ steps.prepare.outputs.CLAUDE_BRANCH }}

View File

@@ -124,7 +124,7 @@ runs:
PATH_TO_CLAUDE_CODE_EXECUTABLE: ${{ inputs.path_to_claude_code_executable }} PATH_TO_CLAUDE_CODE_EXECUTABLE: ${{ inputs.path_to_claude_code_executable }}
run: | run: |
if [ -z "$PATH_TO_CLAUDE_CODE_EXECUTABLE" ]; then if [ -z "$PATH_TO_CLAUDE_CODE_EXECUTABLE" ]; then
CLAUDE_CODE_VERSION="2.0.76" CLAUDE_CODE_VERSION="2.0.70"
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..."

View File

@@ -6,7 +6,7 @@
"name": "@anthropic-ai/claude-code-base-action", "name": "@anthropic-ai/claude-code-base-action",
"dependencies": { "dependencies": {
"@actions/core": "^1.10.1", "@actions/core": "^1.10.1",
"@anthropic-ai/claude-agent-sdk": "^0.1.76", "@anthropic-ai/claude-agent-sdk": "^0.1.70",
"shell-quote": "^1.8.3", "shell-quote": "^1.8.3",
}, },
"devDependencies": { "devDependencies": {
@@ -27,7 +27,7 @@
"@actions/io": ["@actions/io@1.1.3", "", {}, "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="], "@actions/io": ["@actions/io@1.1.3", "", {}, "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="],
"@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.1.76", "", { "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-s7RvpXoFaLXLG7A1cJBAPD8ilwOhhc/12fb5mJXRuD561o4FmPtQ+WRfuy9akMmrFRfLsKv8Ornw3ClGAPL2fw=="], "@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.1.70", "", { "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-4jpFPDX8asys6skO1r3Pzh0Fe9nbND2ASYTWuyFB5iN9bWEL6WScTFyGokjql3M2TkEp9ZGuB2YYpTCdaqT9Sw=="],
"@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="], "@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="],

View File

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

View File

@@ -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,

View File

@@ -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"}'`,

View File

@@ -7,7 +7,7 @@
"dependencies": { "dependencies": {
"@actions/core": "^1.10.1", "@actions/core": "^1.10.1",
"@actions/github": "^6.0.1", "@actions/github": "^6.0.1",
"@anthropic-ai/claude-agent-sdk": "^0.1.76", "@anthropic-ai/claude-agent-sdk": "^0.1.70",
"@modelcontextprotocol/sdk": "^1.11.0", "@modelcontextprotocol/sdk": "^1.11.0",
"@octokit/graphql": "^8.2.2", "@octokit/graphql": "^8.2.2",
"@octokit/rest": "^21.1.1", "@octokit/rest": "^21.1.1",
@@ -37,7 +37,7 @@
"@actions/io": ["@actions/io@1.1.3", "", {}, "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="], "@actions/io": ["@actions/io@1.1.3", "", {}, "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="],
"@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.1.76", "", { "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-s7RvpXoFaLXLG7A1cJBAPD8ilwOhhc/12fb5mJXRuD561o4FmPtQ+WRfuy9akMmrFRfLsKv8Ornw3ClGAPL2fw=="], "@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.1.70", "", { "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-4jpFPDX8asys6skO1r3Pzh0Fe9nbND2ASYTWuyFB5iN9bWEL6WScTFyGokjql3M2TkEp9ZGuB2YYpTCdaqT9Sw=="],
"@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="], "@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="],

View File

@@ -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**:

View File

@@ -21,7 +21,6 @@ This action supports the following GitHub events ([learn more GitHub event trigg
- `issues` - When issues are opened or assigned - `issues` - When issues are opened or assigned
- `pull_request_review` - When PR reviews are submitted - `pull_request_review` - When PR reviews are submitted
- `pull_request_review_comment` - When comments are made on PR reviews - `pull_request_review_comment` - When comments are made on PR reviews
- `push` - When commits are pushed to a branch
- `repository_dispatch` - Custom events triggered via API - `repository_dispatch` - Custom events triggered via API
- `workflow_dispatch` - Manual workflow triggers (coming soon) - `workflow_dispatch` - Manual workflow triggers (coming soon)
@@ -121,42 +120,3 @@ For more control over Claude's behavior, use the `claude_args` input to pass CLI
``` ```
This provides full access to Claude Code CLI capabilities while maintaining the simplified action interface. This provides full access to Claude Code CLI capabilities while maintaining the simplified action interface.
## Auto-Rebase PRs on Push
Automatically keep PRs up to date when the main branch is updated:
```yaml
name: Auto-Rebase PRs
on:
push:
branches: [main]
permissions:
contents: write
pull-requests: write
id-token: write
jobs:
rebase-prs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: anthropics/claude-code-action@v1
with:
prompt: |
Find all open PRs that are behind main and merge main into them.
For each PR:
1. Check out the PR branch
2. Merge main into the branch
3. Push the updated branch
Skip any PRs with merge conflicts - just report them.
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
```
This workflow triggers whenever commits are pushed to main and uses Claude to automatically merge main into any stale PR branches, keeping them up to date.

View File

@@ -58,7 +58,6 @@ jobs:
| `claude_code_oauth_token` | Claude Code OAuth token (alternative to anthropic_api_key) | No\* | - | | `claude_code_oauth_token` | Claude Code OAuth token (alternative to anthropic_api_key) | No\* | - |
| `prompt` | Instructions for Claude. Can be a direct prompt or custom template for automation workflows | No | - | | `prompt` | Instructions for Claude. Can be a direct prompt or custom template for automation workflows | No | - |
| `track_progress` | Force tag mode with tracking comments. Only works with specific PR/issue events. Preserves GitHub context | No | `false` | | `track_progress` | Force tag mode with tracking comments. Only works with specific PR/issue events. Preserves GitHub context | No | `false` |
| `include_fix_links` | Include 'Fix this' links in PR code review feedback that open Claude Code with context to fix the identified issue | No | `true` |
| `claude_args` | Additional [arguments to pass directly to Claude CLI](https://docs.claude.com/en/docs/claude-code/cli-reference#cli-flags) (e.g., `--max-turns 10 --model claude-4-0-sonnet-20250805`) | No | "" | | `claude_args` | Additional [arguments to pass directly to Claude CLI](https://docs.claude.com/en/docs/claude-code/cli-reference#cli-flags) (e.g., `--max-turns 10 --model claude-4-0-sonnet-20250805`) | No | "" |
| `base_branch` | The base branch to use for creating new branches (e.g., 'main', 'develop') | No | - | | `base_branch` | The base branch to use for creating new branches (e.g., 'main', 'develop') | No | - |
| `use_sticky_comment` | Use just one comment to deliver PR comments (only applies for pull_request event workflows) | No | `false` | | `use_sticky_comment` | Use just one comment to deliver PR comments (only applies for pull_request event workflows) | No | `false` |

View File

@@ -35,8 +35,11 @@ jobs:
- name: Create fix branch - name: Create fix branch
id: branch id: branch
env:
SOURCE_BRANCH: ${{ github.event.workflow_run.head_branch }}
RUN_ID: ${{ github.run_id }}
run: | run: |
BRANCH_NAME="claude-auto-fix-ci-${{ github.event.workflow_run.head_branch }}-${{ github.run_id }}" BRANCH_NAME="claude-auto-fix-ci-${SOURCE_BRANCH}-${RUN_ID}"
git checkout -b "$BRANCH_NAME" git checkout -b "$BRANCH_NAME"
echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT

View File

@@ -53,6 +53,8 @@ jobs:
fromJSON(steps.detect.outputs.structured_output).confidence >= 0.7 fromJSON(steps.detect.outputs.structured_output).confidence >= 0.7
env: env:
GH_TOKEN: ${{ github.token }} GH_TOKEN: ${{ github.token }}
WORKFLOW_NAME: ${{ github.event.workflow_run.name }}
HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
run: | run: |
OUTPUT='${{ steps.detect.outputs.structured_output }}' OUTPUT='${{ steps.detect.outputs.structured_output }}'
CONFIDENCE=$(echo "$OUTPUT" | jq -r '.confidence') CONFIDENCE=$(echo "$OUTPUT" | jq -r '.confidence')
@@ -63,8 +65,8 @@ jobs:
echo "" echo ""
echo "Triggering automatic retry..." echo "Triggering automatic retry..."
gh workflow run "${{ github.event.workflow_run.name }}" \ gh workflow run "$WORKFLOW_NAME" \
--ref "${{ github.event.workflow_run.head_branch }}" --ref "$HEAD_BRANCH"
# Low confidence flaky detection - skip retry # Low confidence flaky detection - skip retry
- name: Low confidence detection - name: Low confidence detection
@@ -83,13 +85,14 @@ jobs:
if: github.event.workflow_run.event == 'pull_request' if: github.event.workflow_run.event == 'pull_request'
env: env:
GH_TOKEN: ${{ github.token }} GH_TOKEN: ${{ github.token }}
HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
run: | run: |
OUTPUT='${{ steps.detect.outputs.structured_output }}' OUTPUT='${{ steps.detect.outputs.structured_output }}'
IS_FLAKY=$(echo "$OUTPUT" | jq -r '.is_flaky') IS_FLAKY=$(echo "$OUTPUT" | jq -r '.is_flaky')
CONFIDENCE=$(echo "$OUTPUT" | jq -r '.confidence') CONFIDENCE=$(echo "$OUTPUT" | jq -r '.confidence')
SUMMARY=$(echo "$OUTPUT" | jq -r '.summary') SUMMARY=$(echo "$OUTPUT" | jq -r '.summary')
pr_number=$(gh pr list --head "${{ github.event.workflow_run.head_branch }}" --json number --jq '.[0].number') pr_number=$(gh pr list --head "$HEAD_BRANCH" --json number --jq '.[0].number')
if [ -n "$pr_number" ]; then if [ -n "$pr_number" ]; then
if [ "$IS_FLAKY" = "true" ]; then if [ "$IS_FLAKY" = "true" ]; then

View File

@@ -12,7 +12,7 @@
"dependencies": { "dependencies": {
"@actions/core": "^1.10.1", "@actions/core": "^1.10.1",
"@actions/github": "^6.0.1", "@actions/github": "^6.0.1",
"@anthropic-ai/claude-agent-sdk": "^0.1.76", "@anthropic-ai/claude-agent-sdk": "^0.1.70",
"@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",

View File

@@ -734,13 +734,7 @@ ${eventData.eventName === "issue_comment" || eventData.eventName === "pull_reque
- Reference specific code sections with file paths and line numbers${eventData.isPR ? `\n - AFTER reading files and analyzing code, you MUST call mcp__github_comment__update_claude_comment to post your review` : ""} - Reference specific code sections with file paths and line numbers${eventData.isPR ? `\n - AFTER reading files and analyzing code, you MUST call mcp__github_comment__update_claude_comment to post your review` : ""}
- Formulate a concise, technical, and helpful response based on the context. - Formulate a concise, technical, and helpful response based on the context.
- Reference specific code with inline formatting or code blocks. - Reference specific code with inline formatting or code blocks.
- Include relevant file paths and line numbers when applicable.${ - Include relevant file paths and line numbers when applicable.
eventData.isPR && context.githubContext?.inputs.includeFixLinks
? `
- When identifying issues that could be fixed, include an inline link: [Fix this →](https://claude.ai/code?q=<URI_ENCODED_INSTRUCTIONS>&repo=${context.repository})
The query should be URI-encoded and include enough context for Claude Code to understand and fix the issue (file path, line numbers, branch name, what needs to change).`
: ""
}
- ${eventData.isPR ? `IMPORTANT: Submit your review feedback by updating the Claude comment using mcp__github_comment__update_claude_comment. This will be displayed as your PR review.` : `Remember that this feedback must be posted to the GitHub comment using mcp__github_comment__update_claude_comment.`} - ${eventData.isPR ? `IMPORTANT: Submit your review feedback by updating the Claude comment using mcp__github_comment__update_claude_comment. This will be displayed as your PR review.` : `Remember that this feedback must be posted to the GitHub comment using mcp__github_comment__update_claude_comment.`}
B. For Straightforward Changes: B. For Straightforward Changes:

View File

@@ -6,7 +6,6 @@ import type {
PullRequestEvent, PullRequestEvent,
PullRequestReviewEvent, PullRequestReviewEvent,
PullRequestReviewCommentEvent, PullRequestReviewCommentEvent,
PushEvent,
WorkflowRunEvent, WorkflowRunEvent,
} from "@octokit/webhooks-types"; } from "@octokit/webhooks-types";
import { CLAUDE_APP_BOT_ID, CLAUDE_BOT_LOGIN } from "./constants"; import { CLAUDE_APP_BOT_ID, CLAUDE_BOT_LOGIN } from "./constants";
@@ -66,7 +65,6 @@ const AUTOMATION_EVENT_NAMES = [
"repository_dispatch", "repository_dispatch",
"schedule", "schedule",
"workflow_run", "workflow_run",
"push",
] as const; ] as const;
// Derive types from constants for better maintainability // Derive types from constants for better maintainability
@@ -97,7 +95,6 @@ type BaseContext = {
allowedBots: string; allowedBots: string;
allowedNonWriteUsers: string; allowedNonWriteUsers: string;
trackProgress: boolean; trackProgress: boolean;
includeFixLinks: boolean;
}; };
}; };
@@ -114,15 +111,14 @@ export type ParsedGitHubContext = BaseContext & {
isPR: boolean; isPR: boolean;
}; };
// Context for automation events (workflow_dispatch, repository_dispatch, schedule, workflow_run, push) // Context for automation events (workflow_dispatch, repository_dispatch, schedule, workflow_run)
export type AutomationContext = BaseContext & { export type AutomationContext = BaseContext & {
eventName: AutomationEventName; eventName: AutomationEventName;
payload: payload:
| WorkflowDispatchEvent | WorkflowDispatchEvent
| RepositoryDispatchEvent | RepositoryDispatchEvent
| ScheduleEvent | ScheduleEvent
| WorkflowRunEvent | WorkflowRunEvent;
| PushEvent;
}; };
// Union type for all contexts // Union type for all contexts
@@ -154,7 +150,6 @@ export function parseGitHubContext(): GitHubContext {
allowedBots: process.env.ALLOWED_BOTS ?? "", allowedBots: process.env.ALLOWED_BOTS ?? "",
allowedNonWriteUsers: process.env.ALLOWED_NON_WRITE_USERS ?? "", allowedNonWriteUsers: process.env.ALLOWED_NON_WRITE_USERS ?? "",
trackProgress: process.env.TRACK_PROGRESS === "true", trackProgress: process.env.TRACK_PROGRESS === "true",
includeFixLinks: process.env.INCLUDE_FIX_LINKS === "true",
}, },
}; };
@@ -238,13 +233,6 @@ export function parseGitHubContext(): GitHubContext {
payload: context.payload as unknown as WorkflowRunEvent, payload: context.payload as unknown as WorkflowRunEvent,
}; };
} }
case "push": {
return {
...commonFields,
eventName: "push",
payload: context.payload as unknown as PushEvent,
};
}
default: default:
throw new Error(`Unsupported event type: ${context.eventName}`); throw new Error(`Unsupported event type: ${context.eventName}`);
} }
@@ -286,12 +274,6 @@ export function isIssuesAssignedEvent(
return isIssuesEvent(context) && context.eventAction === "assigned"; return isIssuesEvent(context) && context.eventAction === "assigned";
} }
export function isPushEvent(
context: GitHubContext,
): context is AutomationContext & { payload: PushEvent } {
return context.eventName === "push";
}
// Type guard to check if context is an entity context (has entityNumber and isPR) // Type guard to check if context is an entity context (has entityNumber and isPR)
export function isEntityContext( export function isEntityContext(
context: GitHubContext, context: GitHubContext,

View File

@@ -37,7 +37,6 @@ describe("prepareMcpConfig", () => {
allowedBots: "", allowedBots: "",
allowedNonWriteUsers: "", allowedNonWriteUsers: "",
trackProgress: false, trackProgress: false,
includeFixLinks: true,
}, },
}; };

View File

@@ -25,7 +25,6 @@ const defaultInputs = {
allowedBots: "", allowedBots: "",
allowedNonWriteUsers: "", allowedNonWriteUsers: "",
trackProgress: false, trackProgress: false,
includeFixLinks: true,
}; };
const defaultRepository = { const defaultRepository = {

View File

@@ -1,7 +1,6 @@
import { describe, expect, it } from "bun:test"; import { describe, expect, it } from "bun:test";
import { detectMode } from "../../src/modes/detector"; import { detectMode } from "../../src/modes/detector";
import type { GitHubContext } from "../../src/github/context"; import type { GitHubContext } from "../../src/github/context";
import { isPushEvent } from "../../src/github/context";
describe("detectMode with enhanced routing", () => { describe("detectMode with enhanced routing", () => {
const baseContext = { const baseContext = {
@@ -26,7 +25,6 @@ describe("detectMode with enhanced routing", () => {
allowedBots: "", allowedBots: "",
allowedNonWriteUsers: "", allowedNonWriteUsers: "",
trackProgress: false, trackProgress: false,
includeFixLinks: true,
}, },
}; };
@@ -258,65 +256,4 @@ describe("detectMode with enhanced routing", () => {
expect(detectMode(context)).toBe("tag"); expect(detectMode(context)).toBe("tag");
}); });
}); });
describe("Push Events", () => {
it("should use agent mode for push events", () => {
const context: GitHubContext = {
...baseContext,
eventName: "push",
payload: {} as any,
inputs: { ...baseContext.inputs, prompt: "Merge main into stale PRs" },
};
expect(detectMode(context)).toBe("agent");
});
it("should throw error when track_progress is used with push event", () => {
const context: GitHubContext = {
...baseContext,
eventName: "push",
payload: {} as any,
inputs: { ...baseContext.inputs, trackProgress: true },
};
expect(() => detectMode(context)).toThrow(
/track_progress is only supported /,
);
});
});
describe("isPushEvent type guard", () => {
it("should return true for push events", () => {
const context: GitHubContext = {
...baseContext,
eventName: "push",
payload: {} as any,
};
expect(isPushEvent(context)).toBe(true);
});
it("should return false for non-push events", () => {
const issueContext: GitHubContext = {
...baseContext,
eventName: "issues",
eventAction: "opened",
payload: { issue: { number: 1, body: "Test" } } as any,
entityNumber: 1,
isPR: false,
};
expect(isPushEvent(issueContext)).toBe(false);
});
it("should return false for workflow_dispatch events", () => {
const context: GitHubContext = {
...baseContext,
eventName: "workflow_dispatch",
payload: {} as any,
};
expect(isPushEvent(context)).toBe(false);
});
});
}); });

View File

@@ -60,15 +60,6 @@ describe("Mode Registry", () => {
expect(mode.name).toBe("agent"); expect(mode.name).toBe("agent");
}); });
test("getMode auto-detects agent for push event", () => {
const pushContext = createMockAutomationContext({
eventName: "push",
});
const mode = getMode(pushContext);
expect(mode).toBe(agentMode);
expect(mode.name).toBe("agent");
});
test("getMode auto-detects agent for repository_dispatch with client_payload", () => { test("getMode auto-detects agent for repository_dispatch with client_payload", () => {
const contextWithPayload = createMockAutomationContext({ const contextWithPayload = createMockAutomationContext({
eventName: "repository_dispatch", eventName: "repository_dispatch",

View File

@@ -73,7 +73,6 @@ describe("checkWritePermissions", () => {
allowedBots: "", allowedBots: "",
allowedNonWriteUsers: "", allowedNonWriteUsers: "",
trackProgress: false, trackProgress: false,
includeFixLinks: true,
}, },
}); });