Compare commits

...
764 Commits
Author SHA1 Message Date
GitHub Actions 3b8197d3d4 chore: bump Claude Code to 2.1.274 and Agent SDK to 0.3.274 2026-09-17 00:12:40 +00:00
GitHub Actions 7b0b255830 chore: bump Claude Code to 2.1.273 and Agent SDK to 0.3.273 2026-09-15 20:23:44 +00:00
GitHub Actions bf38e86e58 chore: bump Claude Code to 2.1.272 and Agent SDK to 0.3.272 2026-09-15 00:43:27 +00:00
GitHub Actions 51db78a4b8 chore: bump Claude Code to 2.1.271 and Agent SDK to 0.3.271 2026-09-14 22:13:27 +00:00
GitHub Actions 9cdae7f0d9 chore: bump Claude Code to 2.1.270 and Agent SDK to 0.3.270 2026-09-12 19:46:49 +00:00
GitHub Actions 56cf60fde4 chore: bump Claude Code to 2.1.269 and Agent SDK to 0.3.269 2026-09-11 19:18:51 +00:00
GitHub Actions 0a8d3c9443 chore: bump Claude Code to 2.1.268 and Agent SDK to 0.3.268 2026-09-10 20:39:16 +00:00
GitHub Actions 19dda84776 chore: bump Claude Code to 2.1.267 and Agent SDK to 0.3.267 2026-09-09 20:11:01 +00:00
GitHub Actions 5ccc3a35a6 chore: bump Claude Code to 2.1.266 and Agent SDK to 0.3.266 2026-09-08 23:56:12 +00:00
GitHub Actions 0d0e0876d3 chore: bump Claude Code to 2.1.265 and Agent SDK to 0.3.265 2026-09-08 20:38:21 +00:00
GitHub Actions 9c5ddab2e6 chore: bump Claude Code to 2.1.263 and Agent SDK to 0.3.263 2026-09-06 02:55:18 +00:00
GitHub Actions d75b94d5ad chore: bump Claude Code to 2.1.261 and Agent SDK to 0.3.261 2026-09-04 19:58:53 +00:00
GitHub Actions ef8bb1e43b chore: bump Claude Code to 2.1.260 and Agent SDK to 0.3.260 2026-09-03 23:48:49 +00:00
GitHub Actions fa2b2666b7 chore: bump Claude Code to 2.1.259 and Agent SDK to 0.3.259 2026-09-02 22:34:41 +00:00
GitHub Actions 8251c103ac chore: bump Claude Code to 2.1.258 and Agent SDK to 0.3.258 2026-09-01 22:33:40 +00:00
GitHub Actions 781d62e9d5 chore: bump Claude Code to 2.1.257 and Agent SDK to 0.3.257 2026-09-01 17:54:28 +00:00
GitHub Actions 833fb0f8c9 chore: bump Claude Code to 2.1.252 and Agent SDK to 0.3.252 2026-08-31 19:47:59 +00:00
GitHub Actions a874e9ecd7 chore: bump Claude Code to 2.1.251 and Agent SDK to 0.3.251 2026-08-28 18:20:42 +00:00
GitHub Actions a60f3e1db3 chore: bump Claude Code to 2.1.250 and Agent SDK to 0.3.250 2026-08-28 00:50:11 +00:00
GitHub Actions e8c2d7c16c chore: bump Claude Code to 2.1.248 and Agent SDK to 0.3.248 2026-08-27 22:12:44 +00:00
GitHub Actions 70fec18385 chore: bump Claude Code to 2.1.247 and Agent SDK to 0.3.247 2026-08-27 00:09:24 +00:00
GitHub Actions 1f291e1cfe chore: bump Claude Code to 2.1.246 and Agent SDK to 0.3.246 2026-08-25 22:32:51 +00:00
Muhammad Abdullah khanandGitHub 76ac41a83e fix: encode branch names in GitHub links (#1713)
* fix: encode branch names in GitHub links

* style: format branch URL helper
2026-08-25 11:02:49 -07:00
Jeremy SchoemakerandGitHub 8ef9699156 fix: bound download_job_log against a stalled log fetch (#1719)
The download_job_log MCP tool called
client.actions.downloadJobLogsForWorkflowRun() with no timeout and no
AbortController. @octokit/rest@21 runs on Node's native fetch, which has
no default timeout, and Octokit only cancels a request when the caller
passes request.signal. If the log blob fetch stalls, that await never
resolves and never rejects.

This tool is always enabled in tag mode (src/modes/tag/index.ts), so a
"fix the failing CI" run that calls get_ci_status -> get_workflow_run_details
-> download_job_log can hang on this one await with nothing to recover it.
It's headless, so the run only ends when the Actions job-level
timeout-minutes kills it, burning the whole job budget with the tracking
comment stuck at "Claude Code is working...".

Sibling fetch in src/github/utils/image-downloader.ts (fetchImage) already
got this treatment in #1625 via a timeout-driven AbortController. Same
shape of call: fetch a GitHub-hosted resource by ID from untrusted PR/CI
content. This mirrors that fix for github-actions-server.ts.

Extracted the download+write logic into an exported downloadJobLog()
function (with an injectable timeoutMs) so the timeout path is directly
testable, and guarded the module's entrypoint side effects with
import.meta.main, matching the pattern already used by the other
entrypoints in src/entrypoints/.
2026-08-25 08:46:02 -07:00
Yauheni PapovichandGitHub 791545dab1 fix: allow parentheses in valid branch names (#1710)
Accept parentheses while preserving existing branch-name security checks.

Add regression coverage for scoped branch names.

Refs anthropics/claude-code-action#1709
2026-08-25 08:45:50 -07:00
2d7a787fbd fix: use paths in delete_files prompt example (#1702)
* fix: use paths param in delete_files prompt example

The tag-mode prompt told the model to call delete_files with
"files", but the MCP tool schema and handler expect "paths".

Co-authored-by: Cursor <cursoragent@cursor.com>

* test: prove delete_files prompt against the live MCP schema

The old {files} payload is rejected by the same Zod shape the
tool registers; the generated prompt example now parses cleanly.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test: cover delete_files schema edges and the sibling commit_files tool

Confirm the old files-only payload still fails, types and required
fields are enforced, and commit_files was not inverted by the fix.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: RESILIENCE Agentic Solutions <286555414+WeAreResilience@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-25 08:45:33 -07:00
Mohammed AlkindiandGitHub b58c16b325 chore: add .gitattributes to normalize line endings (#1708)
On Windows, git's default core.autocrlf=true checks out CRLF, and the contributor commands in CONTRIBUTING.md then fail: bun run format:check reports 191 files because Prettier defaults to endOfLine "lf", and three tests that match LF-terminated content fail.

All tracked blobs are already LF, so this changes checkout behavior only and produces no renormalization diff. Every CI job runs on ubuntu-latest, where it is a no-op.
2026-08-25 08:45:13 -07:00
GitHub Actions 16b3b310c3 chore: bump Claude Code to 2.1.245 and Agent SDK to 0.3.245 2026-08-25 05:14:05 +00:00
GitHub Actions 6bcfb8263a chore: bump Claude Code to 2.1.241 and Agent SDK to 0.3.241 2026-08-25 03:43:35 +00:00
GitHub Actions b62c7454dc chore: bump Claude Code to 2.1.241 and Agent SDK to 0.3.243 2026-08-25 03:38:24 +00:00
GitHub Actions e5ad3c7725 chore: bump Claude Code to 2.1.243 and Agent SDK to 0.3.243 2026-08-24 23:41:35 +00:00
GitHub Actions c81e3bc69d chore: bump Claude Code to 2.1.241 and Agent SDK to 0.3.241 2026-08-23 00:53:06 +00:00
GitHub Actions 24dcd50c05 chore: bump Claude Code to 2.1.240 and Agent SDK to 0.3.240 2026-08-22 14:45:56 +00:00
GitHub Actions dcb57747bf chore: bump Claude Code to 2.1.239 and Agent SDK to 0.3.239 2026-08-21 19:55:45 +00:00
492d2d78ee fix: teach claude_args --allowedTools in the signed prompt (#1704)
allowed_tools was removed in v1.0. The tag-mode prompt still named it
as the way to enable Bash under commit signing.

Co-authored-by: RESILIENCE Agentic Solutions <286555414+WeAreResilience@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-20 16:17:53 -07:00
2ca5fb4027 fix: surface resolved model limits (#1608)
Signed-off-by: ulofiai <monsterking@tutamail.com>
Co-authored-by: ulofiai <monsterking@tutamail.com>
2026-08-20 16:17:36 -07:00
f3f2789f0a fix(mcp): recognize mcp__github aggregate selector for GitHub MCP server initialization (#1657)
* fix(mcp): accept shorthand selectors for GitHub MCP server initialization

## Problem

Signed-off-by: anish <anishesg@users.noreply.github.com>

* address review feedback: fix prettier formatting

Signed-off-by: anish <anishesg@users.noreply.github.com>

---------

Signed-off-by: anish <anishesg@users.noreply.github.com>
Co-authored-by: anish <anishesg@users.noreply.github.com>
2026-08-20 16:17:25 -07:00
Gautam SharmaandGitHub 6a5f1d8e0a fix(cleanup): keep the base-branch config revert out of the auto-commit (#1677)
restoreConfigFromBase replaces .claude/, CLAUDE.md and the other sensitive
paths with the PR base branch's versions, then deliberately unstages them so
the revert does not reach a commit. checkAndCommitOrDeleteBranch then ran a
bare `git add -A`, which staged them again and pushed a silent revert of the
PR author's own config onto their branch, under a commit message that says
only "Auto-commit: Save uncommitted changes from Claude".

restoreConfigFromBase now returns the paths it restored. run.ts threads them
through updateCommentLink into checkAndCommitOrDeleteBranch, which excludes
them via pathspec from both the staging and the git status check.

The exclusion is driven by what was actually restored rather than applied
unconditionally. This path also runs for issues, where no restore happens and
Claude may legitimately have been asked to edit CLAUDE.md or
.claude/settings.json; excluding those there would silently drop the work —
trading one silent-data-loss bug for another. Reverting the fix, dropping the
status scoping, and switching to an unconditional exclusion each fail the new
tests.

The status check is scoped the same way as the staging: when the reverted
config is the only dirty entry there is no real work, so the branch is now
correctly treated as empty and deleted instead of receiving a pure revert.

Reachable on a closed or merged PR where Claude left uncommitted changes with
use_commit_signing false — the only combination where a restore has run and
claudeBranch is set.

Fixes #1669
2026-08-20 16:17:03 -07:00
HyunSooandGitHub 39ad3c8977 fix(github): honor GITHUB_GRAPHQL_URL for the GraphQL client (#1575)
The REST client honors GITHUB_API_URL, but the GraphQL client derived its
base URL from GITHUB_API_URL as well and ignored the standard
GITHUB_GRAPHQL_URL variable that GitHub Actions provides. On standard GitHub
Enterprise Server this still worked because @octokit/graphql rewrites a
".../api/v3" REST base to ".../api/graphql", but any deployment whose GraphQL
endpoint is not derivable from the REST base (custom proxy, separate host)
sent GraphQL requests to the wrong URL.

Honor GITHUB_GRAPHQL_URL independently and fall back to GITHUB_API_URL when it
is unset, so behavior is unchanged for github.com and standard GHES. A single
trailing "/graphql" is stripped because @octokit/graphql appends its own.

Add wire-level regression tests that run the real client factory in a fresh
process and assert the final request URLs and Authorization headers;
constructor-option assertions are insufficient because @octokit/graphql
rewrites the path after the client is constructed.
2026-08-20 15:45:21 -07:00
GitHub Actions 3f854a8fb5 chore: bump Claude Code to 2.1.238 and Agent SDK to 0.3.238 2026-08-20 20:33:55 +00:00
GitHub Actions 5ee796a55f chore: bump Claude Code to 2.1.237 and Agent SDK to 0.3.237 2026-08-20 00:54:23 +00:00
Tem RevilandGitHub cff8d3c8f0 fix(git-config): neutralize checkout credential in include-based config (#1526)
configureGitAuth() removed the actions/checkout auth header with
`git config --unset-all http.<server>/.extraheader`, which only edits the
repo-local config. Since actions/checkout v6.0.0 (backported to v5.0.1 and
v4.3.1) the header is written to a separate file under RUNNER_TEMP and
pulled in via include.path, so --unset-all on the local config is a no-op:
the code logged "No existing authentication headers to remove" while the
checkout credential (usually the workflow GITHUB_TOKEN) stayed usable by
git for the rest of the job.

Also clear the header from every included file, so it is neutralized under
both the pre-v6 (local) and v6+ (include) layouts. Includes that do not
define the header are left untouched.

Fixes #1510
2026-08-19 17:23:23 -07:00
GitHub Actions e2a4b761cd chore: bump Claude Code to 2.1.236 and Agent SDK to 0.3.236 2026-08-19 20:05:23 +00:00
65b50df083 fix(github): match bot actors in comment filters using GraphQL __typename (#1616)
`exclude_comments_by_actor` and `include_comments_by_actor` never matched
any bot. Both the documented `*[bot]` wildcard and exact entries such as
`dependabot[bot]` silently did nothing.

GitHub's GraphQL API returns the bare login for App actors ("dependabot"),
while REST and the GitHub UI append a suffix ("dependabot[bot]"). Filter
patterns are written in the suffixed form, so matching a GraphQL login
against them could never succeed and `actor.endsWith("[bot]")` was dead
code.

Request `__typename` on the Actor-typed author selections and normalize
App actors to their suffixed name via `resolveActorName()` before matching.
Normalizing at the filter boundary fixes the wildcard and exact-match cases
together, and leaves the author names shown in the prompt unchanged.

The existing test mocked `login: "scanner[bot]"`, a payload GraphQL never
produces, which is why the gap was invisible. It now mocks the real shape
(`__typename: "Bot", login: "scanner"`) and fails without this fix.

The commit author selection is left alone: it is a GitCommit, not an Actor.

Fixes #1514

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 17:24:26 -07:00
JuwanandGitHub 0a80d21df7 fix: strip unused ALL_INPUTS environment variable from Claude subprocess env (#1692) 2026-08-18 17:23:38 -07:00
JuwanandGitHub 54eadc2f72 fix(security): unify secret redaction in public comment outputs (#1693)
Ensure all public issue, PR, and inline comments apply redactSecrets() in addition to sanitizeContent() before submitting payloads to the GitHub API. This aligns public comment output with error log and step-summary redaction policies, preventing potential leakage of Anthropic API keys, AWS credentials, Slack tokens, JWTs, and GitHub tokens.
2026-08-18 17:23:11 -07:00
GitHub Actions d40ddef4c0 chore: bump Claude Code to 2.1.235 and Agent SDK to 0.3.235 2026-08-18 20:39:22 +00:00
GitHub Actions 459ad358ae chore: bump Claude Code to 2.1.234 and Agent SDK to 0.3.234 2026-08-17 20:23:54 +00:00
Abhinav Kumar SinghandGitHub d721746d68 fix: bound image attachment downloads (#1625) 2026-08-14 16:47:46 -07:00
5da4c76dde fix: bump shell-quote to 1.8.4 to remediate CVE-2026-9277 (#1557)
shell-quote 1.8.3 (root and base-action dependency) is affected by
CVE-2026-9277, a CRITICAL severity vulnerability. 1.8.4 contains the fix.

Co-authored-by: Ashwin Bhat <ashwin@anthropic.com>
2026-08-14 16:46:34 -07:00
Ashwin BhatandGitHub a2cac87e27 ci: skip Claude-backed test jobs on fork PRs (#1655)
* ci: skip Claude-backed test jobs on fork PRs

Jobs that run the action against the Claude API authenticate via workload
identity federation, which fork PRs cannot mint an OIDC token for, so they
always failed on external contributions. Gate each such job on the PR head
repo matching the base repo; push and workflow_dispatch runs are unaffected.

No-Verification-Needed: CI workflow config only, exercised by Actions on the PR

* test: pin the bare remote's initial branch in fetch-depth test

The shallow-clone case created its bare remote with a plain git init, so
HEAD pointed at whatever init.defaultBranch resolves to (master on CI)
while the test only pushed main. git clone --depth=1 implies
--single-branch, and with a dangling remote HEAD it produces an empty,
non-shallow clone, so the is-shallow assertion failed on runners whose
default branch is not main.

No-Verification-Needed: test-only change
2026-08-14 16:40:34 -07:00
Rishav NaskarandGitHub b49813d0e7 feat(context): include diffHunk in PR review comment context (#1584)
* feat(context): include diffHunk in PR review comment context

Review comments arrived with only path and line, so the code they were
written against was missing from the prompt. Fetch diffHunk in the PR
GraphQL query and render it under the comment as a diff block.

The hunk is PR-authored content, so it goes through sanitizeContent like
the comment body. Comments without a hunk are unchanged.

Fixes #855

* test(formatter): cover outdated review comments with an empty diff hunk

GitHub returns diffHunk: "" (not null) for comments whose line no longer
exists in the diff, so the render guard has to reject empty strings too.
Found running the real query against anthropics/claude-code-action#1025.
2026-08-14 16:32:44 -07:00
9678fce999 fix(base-action): add ~/.local/bin to $GITHUB_PATH after auto-install (#1643)
## Problem

Signed-off-by: anish <anishesg@users.noreply.github.com>
Co-authored-by: anish <anishesg@users.noreply.github.com>
2026-08-14 16:31:36 -07:00
Madan kumarandGitHub ed186becce fix: only limit fetch depth when the checkout is already shallow (#1647)
restoreConfigFromBase and setupBranch pass --depth to every git fetch. On a
checkout made with fetch-depth: 0 that does not just cap the download: it
truncates the history already present and marks the repository shallow, which
drops the merge base with the base branch. `git log origin/<base>..HEAD` then
silently includes commits that are already merged, and
`git diff origin/<base>...HEAD` fails with "no merge base" — the two commands
the prompt tells Claude to run to scope its work to the PR.

Gate the flag on `git rev-parse --is-shallow-repository`, so a checkout that is
already shallow (the fetch-depth: 1 default) keeps the same depth behaviour and
the fetch savings it was added for, while a full checkout stays full.

Fixes #1642
2026-08-14 16:31:26 -07:00
05ee4b30d7 Harden delete_files MCP tool: validate paths within repo root (#1636)
Mirror the path validation already performed by the commit_files tool.
delete_files previously only normalized absolute paths against CWD and
passed relative paths through unchecked; it now runs each path through
validatePathWithinRepo, rejecting "../" traversal and symlinked escapes
for consistency and defense-in-depth.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-14 16:31:15 -07:00
GitHub Actions 9d7150bc8a chore: bump Claude Code to 2.1.233 and Agent SDK to 0.3.233 2026-08-14 22:21:44 +00:00
GitHub Actions e63208cb98 chore: bump Claude Code to 2.1.232 and Agent SDK to 0.3.232 2026-08-13 23:30:40 +00:00
GitHub Actions dc33e8a15b chore: bump Claude Code to 2.1.231 and Agent SDK to 0.3.231 2026-08-13 08:39:40 +00:00
GitHub Actions c58ad32088 chore: bump Claude Code to 2.1.229 and Agent SDK to 0.3.229 2026-08-12 20:57:42 +00:00
Henrique PiresandGitHub dfb8fc798e fix(mcp): detect binary files by content instead of extension allowlist (#1633) 2026-08-11 16:35:46 -07:00
a2489efcb9 fix(summary): keep every text block in structured tool results (#1619)
formatResultContent recognized structured tool output shaped like
`[{ type: "text", text: "..." }]` but read only `parsedContent[0].text`.
When a tool result split its output across several text blocks, the step
summary showed the first and silently dropped the rest, so extra findings,
file paths and follow-up instructions vanished from the rendered
Claude Code Report while remaining in the execution transcript.

Collect the text from every block instead of just the first. Blocks of other
types, such as images, are skipped rather than stringified into the summary.

Fixes #1572

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 16:35:38 -07:00
Abhinav Kumar SinghandGitHub 8b8745859f fix: paginate GitHub Actions MCP responses (#1629) 2026-08-11 16:35:34 -07:00
GitHub Actions 239e3a7308 chore: bump Claude Code to 2.1.228 and Agent SDK to 0.3.228 2026-08-11 20:03:37 +00:00
GitHub Actions 5ef2e550a4 chore: bump Claude Code to 2.1.227 and Agent SDK to 0.3.227 2026-08-10 23:16:28 +00:00
GitHub Actions 6b082c4193 chore: bump Claude Code to 2.1.226 and Agent SDK to 0.3.226 2026-08-08 02:48:25 +00:00
GitHub Actions 7ff6806c8e chore: bump Claude Code to 2.1.225 and Agent SDK to 0.3.225 2026-08-08 01:14:59 +00:00
NickNojiriandGitHub 751e003832 fix(branch): collapse empty path segments in branch_name_template (#1539)
A branch_name_template that places {{description}} (or another variable)
next to a slash crashes the run when the variable resolves to an empty
string. An issue/PR title with no ASCII-alphanumeric content — emoji-only,
CJK-only, or punctuation-only — makes extractDescription() return "", so a
template like "{{prefix}}{{description}}/{{entityNumber}}" produces
"claude//123". validateBranchName rejects consecutive (and leading/trailing)
slashes, and the thrown error propagates uncaught out of setupBranch,
aborting the entire run.

Normalize the templated result before the empty-result check: collapse runs
of slashes and drop any leading/trailing slash. Single-slash and dash
separators are untouched, so existing template behavior is unchanged; a
template that collapses to empty still falls back to the default format.

This is distinct from the {{label}} sanitization tracked in #1491 (and its
open PRs), which deliberately leave {{description}} alone — so this path
remained broken. Fixes the whole empty-segment class regardless of variable.

Adds regression tests for emoji-only and CJK-only titles, a trailing empty
segment, and a direct validateBranchName assertion proving the run no longer
aborts.
2026-08-07 07:59:52 -07:00
Rishav NaskarandGitHub b704dd3960 fix(branch): validate generated branch name under commit signing (#1582)
The non-signing path validated newBranch before checkout, but the
use_commit_signing path passed it straight to the file ops server, so an
invalid branch_name_template surfaced only as a 422 "Reference name is
not valid" on the first commit.

Validate once after the name is resolved so both paths fail early with
the same message.

Fixes #1573
2026-08-07 07:59:43 -07:00
Minh VuandGitHub ecf573bd65 fix: expose conclusion output (#1549) 2026-08-07 07:58:52 -07:00
Minh VuandGitHub 7764306e92 fix: stop retrying deterministic ref updates (#1551) 2026-08-07 07:58:19 -07:00
Minh VuandGitHub 5dd098c551 ci: pass allowed tools through claude args (#1552) 2026-08-07 07:57:29 -07:00
4c4309a064 fix(cache): disable setup-bun cache to avoid 5-retry HTML-error burn (#1580)
The upstream oven-sh/setup-bun action saves with a deterministic key
(Bun version) that isn't ref-aware. On every second-and-subsequent run
against the same PR ref, the GitHub cache API rejects the duplicate
key+ref with a 409 (HTML body), and @actions/cache treats the unparsable
response as transient and burns ~20-30s on 5 retries before warning.

The 35 MB Bun binary downloads in 2-3s, so disabling the cache is a net
wallclock win and removes the noisy warning that fires on every PR push
after the first.

Closes #1252

Co-authored-by: Mukunda Rao Katta <mukunda.vjcs6@gmail.com>
2026-08-07 07:57:18 -07:00
Minh VuandGitHub c4190dbd78 docs: update base action inputs (#1550) 2026-08-07 07:56:51 -07:00
Jeremy SchoemakerandGitHub 9a2db97708 docs: fix broken Bedrock anchor in cloud-providers.md (#1579)
The link pointed at `#for-aws-bedrock:`, which does not exist on the
github-actions docs page, so it landed readers at the top of a long page
instead of the Bedrock section.

The current heading id is `#using-with-amazon-bedrock-and-google-cloud`.
2026-08-07 07:56:43 -07:00
Sahil GuptaandGitHub 2df67d2c33 fix: match label_trigger case-insensitively (#1576)
label_trigger used a case-sensitive exact comparison, so a workflow
configured with label_trigger: "claude-task" did not fire when an issue
received a label named "Claude-Task" (the same label name with different
casing).

GitHub label names are unique without regard to case, so comparing without
case is unambiguous. It also matches the trigger_phrase check in the same
function, which is already case-insensitive.

Compare labelName and labelTrigger with toLowerCase(), and add a test
covering a mixed-case label.

Fixes #1571
2026-08-07 07:55:32 -07:00
Takaki SatoandGitHub d573b167d3 fix: support labeled action for pull_request events in track_progress (#1586)
Adds "labeled" to the valid pull_request actions for track_progress,
mirroring the existing support for issue events. Previously, adding
a label to a PR (e.g. to trigger a label-driven Claude review) would
fail validation even though the same pattern works for issues.

Fixes #1585
2026-08-07 07:55:20 -07:00
leepokaiandGitHub 0a5f191964 fix: handle null files field from GraphQL on very large PRs (#1593)
GitHub's GraphQL API returns files: null (with no errors entry, and
changedFiles misreported as 0) when a PR's diff is too large to compute.
The unguarded pullRequest.files.nodes dereference in the fetcher crashed
the action with 'TypeError: null is not an object', and the formatter had
the same latent crash on prData.files.nodes.length.

Widen the GitHubPullRequest type to files | null so the compiler enforces
guards, degrade gracefully in the fetcher with a warning, and render the
file count as unavailable (not '0 files') in the formatter.

Fixes #1587
2026-08-07 07:55:10 -07:00
ulofiaiandGitHub 6ef6450f51 fix: enforce max turns from claude args (#1607) 2026-08-07 07:55:06 -07:00
GitHub Actions 1623c36729 chore: bump Claude Code to 2.1.224 and Agent SDK to 0.3.224 2026-08-07 04:02:00 +00:00
Ashwin BhatandGitHub 96e281f4d9 Run checkout auth cleanup when API commit signing is enabled (#1597)
* Run checkout auth cleanup when API commit signing is enabled

* Derive git-config test expectations from GITHUB_SERVER_URL

No-Verification-Needed: test-only change
2026-08-06 10:18:34 -07:00
Ashwin BhatandGitHub e1fc925862 Scope the config snapshot to files inside the working tree (#1596)
* Scope config snapshot to files inside the working tree

* Record excluded snapshot entries as placeholders instead of links

* Limit linked snapshot content to unmodified tracked files and tracked directories

File targets reached through a link are included only when their content is
unchanged from HEAD, and directory targets only when they contain tracked
files; anything else is recorded as a single placeholder. Adds tests for a
sensitive path that links to a tracked directory, links to untracked
directories, and links to tracked files modified after checkout.
2026-08-06 10:17:40 -07:00
Ashwin BhatandGitHub 0aee57ab82 Redact common credential patterns from published run output (#1595)
* Redact common credential patterns from published run output

* Handle color codes and escape sequences ahead of redacted values

Vendor-prefixed formats no longer require a leading word boundary, so a
value that follows an ANSI SGR terminator or a serialized JSON escape is
still matched. AWS key ids keep a boundary but also accept those cases.
sanitizeContent goes back to GitHub-only redaction for inbound content,
and the failure annotation is redacted like the tracking comment.

* Coerce non-string text content before redacting tool results

No-Verification-Needed: one-line coercion in a formatting helper plus regression test
2026-08-06 10:17:25 -07:00
GitHub Actions c038e4dcde chore: bump Claude Code to 2.1.223 and Agent SDK to 0.3.223 2026-08-06 00:53:11 +00:00
Ashwin BhatandGitHub 4c04887769 Invoke the formatter directly from the format hook (#1594)
* Invoke the formatter directly from the format hook

The PostToolUse format hook now runs prettier directly with a pinned
version and --no-config instead of going through the package.json
"format" script, so the hook resolves the same way regardless of the
scripts and formatter config in the checked-out tree. Output matches
the previous "bun run format" (both .prettierrc files are empty).

Also documents which paths the action restores from the PR base branch
and recommends keeping base-branch hooks self-contained.

No-Verification-Needed: config, comment, and doc-only change

* Qualify the self-contained hook guidance for Bun-only runners

Note in docs/security.md and the restore-config JSDoc that bunx runs the
tool under node when node is on PATH, but on a Bun-only runner Bun runs
the script itself and reads bunfig.toml (preload etc.) from the
checkout, so that file and .npmrc are runtime config from the PR head.

No-Verification-Needed: comment- and doc-only change

* Exclude .claude-pr from prettier

No-Verification-Needed: prettierignore-only change
2026-08-05 15:38:47 -07:00
GitHub Actions 9db594c7a0 chore: bump Claude Code to 2.1.222 and Agent SDK to 0.3.222 2026-08-04 22:40:25 +00:00
Ashwin BhatandGitHub acb0385805 Check collaborator permissions for workflow_run events (#1590)
The write-permission gate previously only ran for issue/PR entity
events. Apply it to workflow_run events as well, checking both the
workflow actor and the actor recorded on the upstream run when they
differ. allowed_non_write_users and the github_token override behave
the same as for entity events. Document the behavior for workflow_run
pipelines.
2026-08-04 10:05:34 -07:00
Ashwin BhatandGitHub b80a0f042f Match downloaded images to their source URLs by asset identifier (#1588)
* Match downloaded images to source URLs by asset identifier

* Derive the signed URL asset identifier from the parsed path
2026-08-04 10:04:47 -07:00
Ashwin BhatandGitHub 6fb6bb6858 Pin bun config for MCP server processes (#1589) 2026-08-04 10:03:43 -07:00
Ashwin BhatandGitHub b2963b9127 Derive trigger timestamps for issues and pull_request events (#1592)
* Derive trigger timestamps for issues and pull_request events

For issues labeled/assigned triggers, look up the matching event in the
issue's event history to get the exact time of the label/assignment,
falling back to the payload's updated_at/created_at when the lookup
fails. issues opened uses issue.created_at; pull_request opened uses
pull_request.created_at and other pull_request actions use updated_at.

* Ignore issue label/assign events older than the payload snapshot

A matching labeled/assigned event that predates the webhook payload's
issue.updated_at cannot be the event that fired the webhook, so fall
back to the payload timestamps instead of adopting it as the boundary.
2026-08-03 19:15:50 -07:00
GitHub Actions 86180fa9e4 chore: bump Claude Code to 2.1.221 and Agent SDK to 0.3.221 2026-08-04 00:15:10 +00:00
GitHub Actions be7b93b190 chore: bump Claude Code to 2.1.220 and Agent SDK to 0.3.220 2026-07-25 01:36:28 +00:00
GitHub Actions e0cf66d1d2 chore: bump Claude Code to 2.1.219 and Agent SDK to 0.3.219 2026-07-24 17:14:37 +00:00
GitHub Actions 44423bdec7 chore: bump Claude Code to 2.1.218 and Agent SDK to 0.3.218 2026-07-22 21:27:29 +00:00
KeisukeYamashitaandGitHub b00a3414fd fix: share one exchanged WIF credential across spawned Claude processes (#1407)
* fix: share one exchanged WIF credential across spawned Claude processes

GitHub OIDC tokens are single-use at the Anthropic token-exchange
endpoint (the same jti cannot be exchanged twice). With plugins
configured, the action spawns several short-lived claude processes
(plugin marketplace add, one plugin install per plugin, then the main
query). Each resolved federation from bare env vars and exchanged the
same identity-token file independently: the first exchange succeeded
and every later process got 401 (jti_reused), which the main query
retried for ~3 minutes before failing the job.

The SDK only enables its on-disk credentials cache when federation is
loaded from a profile config file, not from bare env vars. Write a
profile pointing at the identity-token file and select it via
ANTHROPIC_CONFIG_DIR / ANTHROPIC_PROFILE so the first process exchanges
once and the rest reuse the cached access token. The env vars are kept
as a fallback for CLIs that predate profile support.

* fix: scope the WIF credential cache per federation config

Address review feedback on the shared-credentials-cache fix:

- Embed a fingerprint of the federation inputs (rule, org, service
  account, workspace, base URL, scope) in the config dir name. The SDK
  cache reuses a token on expires_at alone and RUNNER_TEMP is per-job,
  so a later step with different federation inputs would silently reuse
  the first step's token. service_account_id and scope are included
  beyond the reviewed list because both are sent in the exchange
  request body and change which credential is minted.
- Skip the action-managed profile with a warning when the operator has
  already set ANTHROPIC_CONFIG_DIR or ANTHROPIC_PROFILE.
- Shrink the profile to the minimal file-backed form; the CLI's bundled
  SDK gap-fills the federation fields from the env vars the action
  already exports (verified against the pinned 2.1.173 binary).
- Remove the token dir in stop() so the identity token and the cached
  exchanged credential don't outlive the step.
- Document that cache sharing relies on the plugin subprocesses
  spawning sequentially.
2026-07-22 07:02:41 -07:00
GitHub Actions fa7e2f0a29 chore: bump Claude Code to 2.1.217 and Agent SDK to 0.3.217 2026-07-21 21:35:48 +00:00
GitHub Actions b76a0776ae chore: bump Claude Code to 2.1.216 and Agent SDK to 0.3.216 2026-07-20 22:14:27 +00:00
GitHub Actions af0559ee4f chore: bump Claude Code to 2.1.215 and Agent SDK to 0.3.215 2026-07-19 02:56:28 +00:00
GitHub Actions 3553f84341 chore: bump Claude Code to 2.1.214 and Agent SDK to 0.3.214 2026-07-18 01:20:51 +00:00
GitHub Actions 700e7f8316 chore: bump Claude Code to 2.1.212 and Agent SDK to 0.3.212 2026-07-17 00:27:04 +00:00
PaarthandGitHub 3e807ec379 fix: handle null comment/review author from deleted accounts (#1490)
GitHub's GraphQL author field is null when the account behind a
comment, review, PR, or issue has been deleted (the ghost user). The
action typed author as non-null and read author.login directly, so a
single comment from a deleted account threw and was swallowed into a
generic 'Failed to fetch PR/issue data', failing the entire run.

Make author nullable on the four affected types and fall back to
'ghost' at each login read. With the type nullable, tsc flags every
dereference, so all sites are covered.
2026-07-15 21:00:22 -07:00
2988cbe14a docs: map custom_instructions to --append-system-prompt (#1480) (#1484)
* docs: map custom_instructions to --append-system-prompt (#1480)

The v1 migration guide mapped the v0 `custom_instructions` input to
`claude_args: --system-prompt`, but these have different semantics:
`custom_instructions` *appended* to Claude Code's default system prompt,
while `--system-prompt` *replaces* it entirely. Users who followed the
guide silently lost the whole built-in system prompt (tool-usage guidance,
sub-agent conventions, etc.), keeping only their few custom lines.

Fixes #1480:
- Map `custom_instructions` -> `--append-system-prompt` (matches v0 append
  semantics) in the deprecated-inputs table, the migration example, and the
  checklist.
- Correct the claude_args options table: `--system-prompt` replaces the
  entire prompt; add an `--append-system-prompt` row for append behavior.

Docs-only; no code changes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs: fix remaining custom_instructions migration references (#1480)

Update usage.md, faq.md, and configuration.md to map custom_instructions
to --append-system-prompt, matching the migration-guide fix. The
override_prompt row is left unchanged since replacement semantics may be
intended there.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 20:35:32 -07:00
a1c0599a9c fix(format): filter out thinking_tokens system messages from step summary (#1479)
## Summary

Signed-off-by: anish <anishesg@users.noreply.github.com>
Co-authored-by: anish <anishesg@users.noreply.github.com>
2026-07-15 20:27:23 -07:00
5bfa96a5b0 fix: allow leading underscore in branch names (valid per git-check-ref-format) (#1486)
Branch names starting with an underscore (e.g. _release/v1.2.3) are valid
per git check-ref-format but were rejected by validateBranchName's
first-character whitelist. Since setupBranch validates a PR's baseRefName
after checkout, the action failed on every open PR targeting such a
branch. A leading underscore carries no option-injection risk (only a
leading dash does, which is still rejected separately).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 20:27:12 -07:00
Jianke LINandGitHub 214a70611b fix: map claude_args model to SDK options (#1474) 2026-07-15 20:22:15 -07:00
5f509a1c1f fix(sanitizer): strip alt text from reference-style markdown images (#1488)
stripMarkdownImageAltText removed alt text from inline images
(![alt](url)) but not reference-style images (![alt][ref]), because the
regex requires the "](" of the inline form. Alt text is a
hidden-instruction channel that reaches the prompt via sanitizeContent,
so the reference-style form let it survive.

Add a matching replace for the reference-style form (![alt][ref] ->
![][ref]), preserving the [ref] label so the image definition still
resolves. Adds regression tests.

Co-authored-by: Contributor <you@example.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 20:21:12 -07:00
PaarthandGitHub e64308ff97 fix: sanitize {{label}} in branch name templates (#1492)
A scoped label like area:permissions was substituted into the branch
name verbatim, producing a ":" that validateBranchName rejects. Because
the branch setup block catches that error and calls process.exit(1), the
whole run died. {{description}} was already sanitized via extractDescription;
{{label}} was the only free-text variable that skipped it.

Add a sanitizeLabel helper (replaces invalid-char runs with a hyphen so
scoped labels stay readable) and apply it before substitution, falling back
to entityType when a label sanitizes to empty. Adds regression tests that
also assert the result passes validateBranchName.
2026-07-15 20:21:02 -07:00
58dc33d9ad test: cover prepareContext validation error branches (#1460)
* test: cover prepareContext validation error branches

create-prompt.test.ts exercised only happy paths; the ~20 validation
guards in prepareContext (missing PR_NUMBER, unsupported event type,
unsupported issue action, missing claude branch, etc.) had no coverage.

Adds a "prepareContext validation errors" block asserting the thrown
messages for the reachable guards, using the existing createMockContext
helper.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test: cover comments/common link and body builders

`src/github/operations/comments/common.ts` had no direct test coverage,
though its exports are live code used by create-initial.ts and
update-with-branch.ts. This adds unit tests for all four exports:
SPINNER_HTML, createJobRunLink, createBranchLink, and createCommentBody.

Assertions are built from the imported GITHUB_SERVER_URL so they hold on
GHES as well as github.com. Pure test additions — no production changes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 20:20:53 -07:00
evol1228andGitHub ae650f8355 docs: fix dead example links in custom-automations.md (#1513) 2026-07-15 20:18:08 -07:00
GitHub Actions 1298632ce7 chore: bump Claude Code to 2.1.211 and Agent SDK to 0.3.211 2026-07-15 23:10:52 +00:00
GitHub Actions 1253134445 chore: bump Claude Code to 2.1.210 and Agent SDK to 0.3.210 2026-07-14 23:46:09 +00:00
NickNojiriandGitHub 4f07c81564 fix(sanitizer): redact GitHub user-to-server (ghu_) tokens (#1502)
redactGitHubTokens covers ghp_, gho_, ghs_, ghr_, and github_pat_
tokens but misses ghu_ (GitHub App user-to-server tokens), one of the
documented GitHub token prefixes. A ghu_ token appearing in issue or
PR content passed through sanitization unredacted.

Add the ghu_ pattern, mirroring the existing 40-character token
patterns, with unit tests including the git-credential URL form.
2026-07-14 13:06:18 -07:00
GitHub Actions f1bd27ca5b chore: bump Claude Code to 2.1.209 and Agent SDK to 0.3.209 2026-07-14 06:36:42 +00:00
GitHub Actions a08f8913d5 chore: bump Claude Code to 2.1.208 and Agent SDK to 0.3.208 2026-07-14 01:11:18 +00:00
972a512078 fix(sdk): fail step when result has is_error:true despite success subtype (#1496)
Treat subtype success with is_error:true as a failed run so CI does not
show a misleading green check when the review never actually ran.

Fixes #1495

Co-authored-by: syf2211 <syf2211@users.noreply.github.com>
2026-07-13 09:01:51 -07:00
GitHub Actions e90deca476 chore: bump Claude Code to 2.1.207 and Agent SDK to 0.3.207 2026-07-11 00:52:42 +00:00
GitHub Actions 536f2c32a3 chore: bump Claude Code to 2.1.206 and Agent SDK to 0.3.206 2026-07-09 23:35:14 +00:00
GitHub Actions 37b464ce72 chore: bump Claude Code to 2.1.205 and Agent SDK to 0.3.205 2026-07-08 21:22:46 +00:00
GitHub Actions ba0aafd430 chore: bump Claude Code to 2.1.204 and Agent SDK to 0.3.204 2026-07-08 00:28:40 +00:00
GitHub Actions 0fe28cdb64 chore: bump Claude Code to 2.1.203 and Agent SDK to 0.3.203 2026-07-07 21:07:00 +00:00
GitHub Actions f87768c6d2 chore: bump Claude Code to 2.1.202 and Agent SDK to 0.3.202 2026-07-06 22:52:16 +00:00
Ashwin BhatandGitHub 58a2944bbc chore: fix prettier formatting (#1463) 2026-07-04 09:31:09 -07:00
Akhilesh AroraandGitHub beb753ed72 fix: propagate curl failures in install pipeline (#1241)
installClaudeCode() pipes `curl -fsSL | bash -s --`. Bash exits with
the status of the last command, so when curl fails (429 rate limit,
403, or connection error) `bash -s` still exits 0 on empty stdin and
the action logs "Claude Code installed successfully". The 3-attempt
retry loop never triggers because the first attempt looks successful,
and the run later dies with "Executable not found in $PATH: claude".

Prefix the pipeline with `set -o pipefail;` so curl's non-zero exit
propagates through the pipe and the retry loop can actually kick in.
Extracted into buildInstallCommand() with regression tests covering
both the old buggy shape and the fixed one.
2026-07-03 22:38:53 -07:00
JerryLeeandGitHub 235b39bf21 fix: preserve repeated add-dir flags in claude args (#1256) 2026-07-03 22:38:30 -07:00
石岳峰andGitHub d060ddc963 fix(restore): handle symlinked CLAUDE.md paths during config snapshot (#1441)
When snapshotting PR-authored sensitive paths into .claude-pr/, cpSync with
dereference:true throws ENOENT if a symlink target is missing on the PR head
(e.g. .claude/CLAUDE.md -> ../AGENTS.md). Fall back to copying the symlink
itself so restoreConfigFromBase can continue and restore trusted base versions.

Fixes #1398
2026-07-03 22:38:23 -07:00
tarunag10andGitHub 0f07aee435 Use modern noreply email for co-author trailers (#1369) 2026-07-03 22:38:02 -07:00
a221ad2dd9 Drop buffered inline comment when it is posted live (#1405) (#1412)
When classify_inline_comments is enabled, create_inline_comment buffers calls
without confirmed=true. The model frequently re-issues the call with confirmed=true
after reading the buffered reply, which posts the comment live but leaves the
original buffered entry behind. The post-session replay step then posts it again,
so every inline comment lands twice.

Reconcile the buffer on a live post: after a confirmed comment is created, remove
any buffered entry matching the same path, line, startLine and body so it cannot be
replayed. Extracts the reconciliation into src/mcp/inline-comment-buffer.ts (the MCP
server module starts a server on import) and adds unit tests.

Co-authored-by: archievi <13202986+archievi@users.noreply.github.com>
2026-07-03 22:37:36 -07:00
GitHub Actions 558b1d6cab chore: bump Claude Code to 2.1.201 and Agent SDK to 0.3.201 2026-07-03 23:51:09 +00:00
GitHub Actions 01872ccc02 chore: bump Claude Code to 2.1.200 and Agent SDK to 0.3.200 2026-07-03 16:53:31 +00:00
GitHub Actions 769e3bdff9 chore: bump Claude Code to 2.1.199 and Agent SDK to 0.3.199 2026-07-02 23:36:20 +00:00
GitHub Actions 6c0083bb72 chore: bump Claude Code to 2.1.198 and Agent SDK to 0.3.198 2026-07-01 20:46:29 +00:00
846d5d8993 Add agent-approval-check composite action (#1429)
* Add agent-approval-check composite action

Require N human approvals on PRs that contain agent-authored commits.
Posts an agent-approval-check commit status that repos mark as a
required check on protected branches.

This is a sanitized port of the check Anthropic runs internally on
every agent-authored PR — same detection rules, /approve <sha>
comment flow, sibling-PR-same-SHA guard, and fail-closed semantics,
with the Anthropic-specific path exemptions and kill-switch removed
and config moved to action inputs.

Co-Authored-By: Claude <noreply@anthropic.com>

* Drop stray internal acronym from comment

* agent-approval-check: require write-access approvers, pin deps, pagination + doc fixes

🏠 Remote-Dev: homespace

* agent-approval-check: prettier

🏠 Remote-Dev: homespace

* agent-approval-check: verify approver write permission via REST; commits(last:100); docstring

🏠 Remote-Dev: homespace

* agent-approval-check: use headRefOid; drop pull_request_review trigger and correct threat-model docs

🏠 Remote-Dev: homespace

* agent-approval-check: stale-notification wording, no-retry-on-4xx, docstring API-call count

🏠 Remote-Dev: homespace

* agent-approval-check: fail-closed sibling guard on commits-ordering edge; drop stale 'reviewed' from README

🏠 Remote-Dev: homespace

* agent-approval-check: drop hardcoded API-call counts from logs; clarify author write-access requirement in README

🏠 Remote-Dev: homespace

* agent-approval-check: count all agent-email commits (close-reopen bypass); validate REQUIRED_APPROVALS>=1; exempt_head_branches warning

🏠 Remote-Dev: homespace

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Octavian Guzu <oct@anthropic.com>
2026-06-30 14:09:57 -07:00
GitHub Actions fad22eb3fa chore: bump Claude Code to 2.1.197 and Agent SDK to 0.3.197 2026-06-30 17:57:23 +00:00
GitHub Actions 4633baf526 chore: bump Claude Code to 2.1.196 and Agent SDK to 0.3.196 2026-06-29 23:27:52 +00:00
GitHub Actions a92e7c70a4 chore: bump Claude Code to 2.1.195 and Agent SDK to 0.3.195 2026-06-26 21:30:09 +00:00
tarunag10andGitHub f8076dc008 fix: bound app token revocation cleanup (#1437) 2026-06-25 20:57:44 -07:00
GitHub Actions 5211368122 chore: bump Claude Code to 2.1.193 and Agent SDK to 0.3.193 2026-06-25 21:46:38 +00:00
GitHub Actions 428971d2ec chore: bump Claude Code to 2.1.191 and Agent SDK to 0.3.191 2026-06-24 21:59:09 +00:00
GitHub Actions 74eedf1a18 chore: bump Claude Code to 2.1.190 and Agent SDK to 0.3.190 2026-06-24 15:55:36 +00:00
GitHub Actions 80b3182633 chore: bump Claude Code to 2.1.187 and Agent SDK to 0.3.187 2026-06-23 21:05:06 +00:00
360be9c8fc fix: allow @ in branch names (valid per git-check-ref-format) (#1411)
`validateBranchName` rejects branch names containing `@`, even though
`git check-ref-format` permits `@` and GitHub itself accepts such
branches. PRs whose head or base branch contains an `@` fail validation
in-process before any git operation, so the action errors out
immediately.

Branch names with `@` show up in real workflows: ticket conventions
like "TICKET-123@add-feature" (#998), leading-prefix conventions like
"@hotfix/...", and agent tooling that appends "@<sessionid>" (#1305).
There is no workaround other than renaming the branch, which is often
not under the user's control.

Branch names are never passed through a shell (git calls use
execFileSync argv arrays), so `@` carries no injection risk. This is
the same reasoning used to add `#` in #1167, `+` in #1248, and `,` in
#1310. The bare name "@" (HEAD shorthand in git revision syntax) and
the "@{" reflog sequence are still rejected.

- Add `@` to the validateBranchName whitelist regex, including the
  leading position (the leading-character rule blocks option injection
  via `-`, which `@` cannot cause)
- Reject the bare name "@" with a dedicated check
- Update the surrounding comment, JSDoc, and error message to match
- Add test cases for @-containing names and bare "@"

Fixes #998

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-06-22 14:42:36 -07:00
e452eb9dce test: cover format-turns content-type fallbacks and system_other handling (#1421)
Adds unit tests for previously-uncovered branches in
src/entrypoints/format-turns.ts:

- detectContentType: malformed-JSON fall-through (objects and arrays)
  and the default python classification for non-python/non-js code
- formatResultContent: non-string inputs (number, plain object)
- groupTurnsNaturally / formatGroupedContent: the system_other path
  for non-init system turns

Tests only; no source changes. format-turns.ts line coverage rises
from ~86% and the file's non-CLI logic is now fully exercised.

Co-authored-by: hk <solanamobilech@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 14:41:41 -07:00
Steven Zimmerman, CPAandGitHub 6b8063043e fix: filter PR reviews and inline review comments to trigger time (#1385)
Issue/PR comments (#512) and the issue/PR body (#710) are filtered to the
trigger timestamp so content created or edited after an authorized trigger
cannot be injected into Claude's prompt (TOCTOU protection). Reviews and
inline review comments were not: fetchGitHubData returned reviewData filtered
by actor only, and formatReviewComments renders it into the prompt, so a
review submitted or edited after the trigger reached Claude verbatim.

filterReviewsToTriggerTime already existed (added alongside the comment filter
in #512) but was only wired to the image-download list, never to the returned
reviewData.

Filter reviewData.nodes through filterReviewsToTriggerTime and each review's
inline comments through filterCommentsToTriggerTime, alongside the existing
actor filter, then build the review image-processing lists from those
already-filtered nodes (removing a now-redundant second filter pass).
Strengthen the two integration tests to assert post-trigger and edited-after
reviews/comments are dropped.
2026-06-22 14:41:33 -07:00
GitHub Actions 30544b6743 chore: bump Claude Code to 2.1.186 and Agent SDK to 0.3.186 2026-06-22 20:38:23 +00:00
GitHub Actions 2fee155104 chore: bump Claude Code to 2.1.185 and Agent SDK to 0.3.185 2026-06-20 21:00:13 +00:00
GitHub Actions 51705da45e chore: bump Claude Code to 2.1.183 and Agent SDK to 0.3.183 2026-06-19 01:21:31 +00:00
GitHub Actions 806af32823 chore: bump Claude Code to 2.1.181 and Agent SDK to 0.3.181 2026-06-17 22:08:47 +00:00
Ryan NoonanandGitHub 0a08a86780 fix: skip workflow validation token exchange failures (#1417) 2026-06-17 13:53:35 -07:00
GitHub Actions 9dd8b95a39 chore: bump Claude Code to 2.1.179 and Agent SDK to 0.3.179 2026-06-16 20:23:28 +00:00
GitHub Actions 4d7e1f0cd8 chore: bump Claude Code to 2.1.178 and Agent SDK to 0.3.178 2026-06-15 21:37:45 +00:00
3d9f0dc7dc fix(mcp): align allowed-tools parser with SDK option parser (#1373)
parseAllowedTools (used to decide which GitHub MCP servers to install)
hand-rolled a regex parse of claude_args, while the tools actually
granted to Claude are parsed by parseClaudeArgsToExtraArgs in
base-action/src/parse-sdk-options.ts using shell-quote. The two parsers
diverged on two inputs (#1357):

- Multiple values after a single flag: for
  `--allowedTools "Read" "Grep" "mcp__github__get_commit"` the regex
  captured only "Read", so the github MCP server was not installed even
  though mcp__github__get_commit was granted — tool calls then failed.
- Commented-out lines: the regex counted tools on `#`-prefixed lines
  that the SDK parser strips, installing servers that were never used.

Reimplement parseAllowedTools on the same shell-quote tokenizer and the
same "accumulating flag consumes all consecutive non-flag values"
semantics, stripping comment lines first, so the install decision agrees
with the tools that are actually granted. Unquoted glob patterns (e.g.
`mcp__github__*`), which shell-quote yields as glob objects, are
recovered to their literal text to preserve existing behavior.

Closes #1357

Co-authored-by: bymle <229636660+bymle@users.noreply.github.com>
2026-06-13 22:49:34 -07:00
a5e5d3b82e fix(parse-sdk-options): prevent shell-quote from collapsing unquoted Bash(X:*) rules to bare Bash (#1350)
* fix(parse-sdk-options): prevent shell-quote from collapsing unquoted Bash(X:*) rules to bare Bash

shell-quote's parse() tokenizes unquoted `(`, `)` as control operators
and barewords containing `*` as glob ops, all returned as non-string
objects. parseClaudeArgsToExtraArgs filtered those out, so an unquoted
`--allowedTools View,Bash(gh:*),Bash(cat:*)` collapsed to bare `Bash` —
silently widening scoped permission rules to unrestricted Bash(*).

Escape shell control metachars to Unicode private-use placeholders
before parse() and restore after; extract .pattern from glob ops.
Preserves existing quote/whitespace handling.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* ci: retrigger

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-13 22:45:14 -07:00
GitHub Actions d5726de019 chore: bump Claude Code to 2.1.177 and Agent SDK to 0.3.177 2026-06-13 01:25:48 +00:00
GitHub Actions 56fa348258 chore: bump Claude Code to 2.1.176 and Agent SDK to 0.3.176 2026-06-12 21:54:44 +00:00
Ashwin BhatandGitHub 82d95d45af Add pr-stamp-sweep review workflow (#1409)
* Add pr-stamp-sweep review workflow

Adds a reusable workflow script that fans out one reviewer agent per
candidate PR to judge whether it can be approved as-is, then runs an
adversarial verification pass on each approval recommendation before
including it in the final list.

Reviewers read pre-fetched PR dossiers from /tmp/claude/pr-sweep/<n>.md
(metadata, body, comments, full diff) rather than calling gh directly,
and verify diff claims against the local checkout. PR numbers are
passed via args; the script fails fast if none are provided.

* Sharpen security checklist in reviewer prompts

Replace the general security-history note with explicit attack classes
both reviewer stages must check: prompt injection (untrusted content
reaching Claude's context, including via tool output), code execution
(shell commands, eval/spawn, workflow expressions), path traversal,
and credential exposure.
2026-06-11 21:54:48 -07:00
GitHub Actions 0cb4f3e5e7 chore: bump Claude Code to 2.1.175 and Agent SDK to 0.3.175 2026-06-12 04:24:54 +00:00
8551f4b0aa fix(image-downloader): detect image type from magic bytes (#1396)
GitHub serves pasted attachments from /user-attachments/assets/<uuid>
with no file extension, so getImageExtension() silently defaulted to
".png". When the bytes are actually JPEG/GIF/WebP the downloaded file is
mislabeled, and the Read tool then sends a base64 image whose declared
media_type doesn't match its magic bytes — which the Anthropic API
rejects with `400 invalid_request_error` ("image was specified using the
image/png media type, but the image appears to be a image/jpeg image").

Sniff the real format from the buffer's magic bytes after download and
only fall back to the URL-based extension when the signature is
unrecognized. Adds a regression test for a JPEG at an extensionless URL.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 21:23:35 -07:00
looooownandGitHub eba921ff6f docs: fix execution file parsing example (#1297) 2026-06-11 21:19:57 -07:00
36617bd48b fix(sanitizer): match attribute quotes by type to avoid mangling content (#1371)
stripHiddenAttributes used the pattern `["'][^"']*["']` for each quoted
attribute, which matches an opening quote of either type and stops at the
first quote of either type. When a value contained the other quote
character — e.g. an apostrophe inside a double-quoted attribute like
`title="We'll do it"` — the match terminated at the apostrophe, so the
wrong span was removed and the surrounding text was corrupted (e.g.
`<Tooltip title="We'll do it" placement="top">` became
`<Tooltipll do it" placement="top">`).

This surfaced via the github_inline_comment MCP tool: suggestion blocks
whose code lines contain quotes were mangled before posting (#1366).

Match each quoted form per quote type (`"[^"]*"` and `'[^']*'`),
mirroring stripMarkdownLinkTitles, so a value may freely contain the
other quote character. The unquoted fallback is unchanged.

Closes #1366

Co-authored-by: bymle <229636660+bymle@users.noreply.github.com>
2026-06-11 21:19:44 -07:00
24b915648e Include labels in formatContext() output for issues and PRs (#1298)
The formatted_context block sent to the agent omitted labels for both
issues and pull requests, even though the GraphQL queries already
fetched them. This caused agents to incorrectly report "no labels"
when labels existed, breaking any workflow that routes on label state
(e.g., drift-fix routing on drift:* labels in a Drift Watcher pattern).

Changes:
- Add 'PR Labels:' line to PR context output
- Add 'Issue Labels:' line to issue context output
- Both emit 'none' when no labels are present (explicit > omitted)
- Bump labels(first: 1) → labels(first: 100) in both queries; the
  previous cap meant only one label would appear even after the
  formatter fix
- Update existing tests + add 'with labels' tests for both PR and
  issue branches

Discovered while building a GitHub Actions workflow that uses this
action for drift-watcher routing in an internal seed framework
(joshpayne-joby/slim-routines#6). The agent self-diagnosed the gap
by inspecting this action's source — a satisfying full-loop.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-11 21:19:15 -07:00
Kyungil ParkandGitHub 9441a7fe22 fix: clear stale claude-prompts dir before each write (#1288)
Previously, prompt files at `${RUNNER_TEMP}/claude-prompts/` from a prior
invocation could persist on non-ephemeral self-hosted runners (where the
documented `RUNNER_TEMP` cleanup contract is not reliably honored). In
particular, `claude-user-request.txt` is only written by `create-prompt`
when a user request exists; an `agent`-mode invocation does not overwrite
it, so a stale value left by an earlier mention-mode job in another repo
would leak into a later agent-mode job's effective context on the same
runner agent.

Fix: `rm -rf` the directory before `mkdir` in both write sites
(`src/create-prompt/index.ts`, `src/modes/agent/index.ts`). Idempotent,
safe on hosted runners (where the dir is already empty), and self-heals
on self-hosted runners.

Closes #1287
2026-06-11 21:18:57 -07:00
KiwiandGitHub b371255139 pin setup-bun path for post steps (#1365)
Signed-off-by: kiwigitops <kiwisclubco@gmail.com>
2026-06-11 21:18:24 -07:00
Stephen CobbeandGitHub 84d317e8f9 fix: break SDK iterator after result message to prevent hang (#1339)
In some workflow contexts — reliably reproducible for us on
pull_request-triggered runs of this action — the Claude Agent SDK
query() async iterator does not close after the terminal result
message is emitted. The for-await loop in runClaudeWithSdk blocks
indefinitely after Claude has finished its work, until the workflow's
timeout-minutes cap kills the job.

Symptoms observed in production (4× in our scan-reviewer workflow):
- Claude completes successfully: SDK emits { type: "result",
  subtype: "success", ... } with the cost / turns / duration set.
- The action then sits with zero log output for the rest of
  timeout-minutes (we measured 18-19 min of dead time after result).
- The job is cancelled at timeout. writeExecutionFile is never
  called → no claude-execution-output.json → cost-tracker and other
  post-steps see nothing.
- Run shows as cancelled, even though Claude did its work and any
  verdict it posted via gh tools already landed.

Author-mode (workflow_dispatch) runs from the same codebase
terminate cleanly the same day, so the hang is specific to certain
event triggers.

By SDK contract the result message is terminal — no further messages
follow. Break out of the loop immediately after capturing it,
regardless of whether the upstream iterator ever closes. If the SDK
is later fixed to close cleanly in all contexts, this break becomes
a no-op.
2026-06-11 21:17:47 -07:00
Nikita KirsanovandGitHub cd59d5df0d fix: fall back to inherited env for auth when inputs are empty (#1342)
The "Run Claude Code Action" step maps the auth inputs into env
unconditionally:

    ANTHROPIC_API_KEY: ${{ inputs.anthropic_api_key }}
    CLAUDE_CODE_OAUTH_TOKEN: ${{ inputs.claude_code_oauth_token }}

When the input is empty — whether because the caller supplied auth via
the step `env:` block (as reported in #676) or because the `with:` value
resolves empty in some runner/secret configurations — this assignment
overwrites the inherited env value with an empty string. validate-env
then fails with the misleading "Either ANTHROPIC_API_KEY or
CLAUDE_CODE_OAUTH_TOKEN is required" error even though the caller did
provide a token.

Fall back to the inherited env var when the input is empty, mirroring the
existing `${{ env.X }}` pattern already used a few lines below for
ANTHROPIC_BASE_URL / ANTHROPIC_CUSTOM_HEADERS. The input still takes
precedence; nothing changes for workflows that pass auth via `with:`.

Fixes #676
2026-06-11 21:17:22 -07:00
8046d850b5 docs(faq): correct rebase FAQ to match actual behavior (#1370)
The "Why won't Claude rebase my branch?" FAQ told users they could
enable rebasing by passing `--allowedTools "Bash(git rebase:*)"` via
claude_args. This does not work: the system prompt built in
src/create-prompt/index.ts unconditionally instructs Claude that it
cannot merge, rebase, or perform branch operations beyond creating and
pushing commits, so Claude declines rebase requests regardless of the
allowed tools.

Update the FAQ to describe the actual behavior and point users to the
real workaround (rebase locally or via the Claude Code CLI).

Closes #1286

Co-authored-by: bymle <229636660+bymle@users.noreply.github.com>
2026-06-11 21:16:55 -07:00
ee2b19d882 test: add unit tests for parseGitHubContext and context type guards (#1404)
* test: add unit tests for parseGitHubContext and context type guards

Covers all supported webhook event types (entity and automation),
the pull_request_target normalization, isPR detection for comments
on pull requests, env-derived input defaults and parsing, and the
nine type guard functions. Raises src/github/context.ts line
coverage from 26.5% to 100%.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: assert all env-derived inputs to close mutation testing gaps

Mutation testing (StrykerJS, 138 mutants) showed 22 surviving mutants
in the env input parsing of parseGitHubContext: fields like
trackProgress, includeFixLinks, allowedBots and the comment actor
filters were never asserted. Asserting every input field in both the
defaults and the explicit-values tests, plus covering the optional
chaining on payload.repository, brings the mutation score for
src/github/context.ts from 84.06% to 100%.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 21:16:45 -07:00
GitHub Actions ebcdfe6dc6 chore: bump Claude Code to 2.1.174 and Agent SDK to 0.3.174 2026-06-12 01:17:25 +00:00
GitHub Actions 0f97b95b65 chore: bump Claude Code to 2.1.173 and Agent SDK to 0.3.173 2026-06-11 05:43:00 +00:00
GitHub Actions eee73e2ae5 chore: bump Claude Code to 2.1.172 and Agent SDK to 0.3.172 2026-06-10 21:01:40 +00:00
232c9a15f4 Drop --tsconfig-override from Bun invocations to avoid runtime crash (#1315)
* Drop --tsconfig-override from Bun invocations to avoid runtime crash

Passing --tsconfig-override to `bun run` triggers a Bun runtime bug
("Internal error: directory mismatch for directory .../tsconfig.json")
that aborts the action with exit code 1 before any work is done.

Bun already auto-discovers the action's own tsconfig.json by walking up
the directory tree from the entry file, so the override is redundant —
the workspace's tsconfig is never an ancestor of the action checkout.
Dropping the flag preserves tsconfig resolution while avoiding the crash.

Refs: oven-sh/bun#25730

https://claude.ai/code/session_01L763e4S7zBnzDqmYYEJS1A

* Fix prettier formatting in create-prompt/index.ts

Removes redundant outer parentheses that were tripping format:check.
Pre-existing on main; unrelated to the action.yml change but needed
to keep CI green on this branch.

https://claude.ai/code/session_01L763e4S7zBnzDqmYYEJS1A

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-09 14:12:16 -07:00
GitHub Actions 11ba60486e chore: bump Claude Code to 2.1.170 and Agent SDK to 0.3.170 2026-06-09 17:27:26 +00:00
GitHub Actions 593d7a5c4e chore: bump Claude Code to 2.1.169 and Agent SDK to 0.3.169 2026-06-08 21:57:59 +00:00
GitHub Actions fbda2eb1bd chore: bump Claude Code to 2.1.168 and Agent SDK to 0.3.168 2026-06-06 23:42:46 +00:00
GitHub Actions 64de744025 chore: bump Claude Code to 2.1.167 and Agent SDK to 0.3.167 2026-06-06 01:34:14 +00:00
GitHub Actions 410165836e chore: bump Claude Code to 2.1.166 and Agent SDK to 0.3.166 2026-06-06 00:56:12 +00:00
GitHub Actions 41ea7642c1 chore: bump Claude Code to 2.1.165 and Agent SDK to 0.3.165 2026-06-05 05:45:57 +00:00
GitHub Actions 0b1b620029 chore: bump Claude Code to 2.1.163 and Agent SDK to 0.3.163 2026-06-04 21:54:01 +00:00
GitHub Actions 70a6e5256e chore: bump Claude Code to 2.1.162 and Agent SDK to 0.3.162 2026-06-03 21:32:44 +00:00
GitHub Actions 36a69b6a90 chore: bump Claude Code to 2.1.161 and Agent SDK to 0.3.161 2026-06-02 21:59:11 +00:00
bfad70d6a1 ci: bump checkout and setup-bun in test workflows to Node 24 releases (#1379)
actions/checkout v4 and oven-sh/setup-bun v2.0.2 run on the deprecated
Node 20 action runtime and emit a deprecation warning on every run.
Bump to checkout v6.0.2 and setup-bun v2.2.0 (both Node 24).

Co-authored-by: ant-kurt <209710463+ant-kurt@users.noreply.github.com>
2026-06-02 12:03:04 -07:00
dc081a3809 chore: bump actions/setup-node from v4.4.0 to v6.4.0 (Node.js 24) (#1377)
* chore: bump actions/setup-node from v4.4.0 to v6.4.0 (Node.js 24)

setup-node v4 runs on the deprecated Node.js 20 action runtime, producing
a deprecation warning on every workflow run that uses base-action. v6 runs
on Node 24. This only changes the action's own runtime — the node-version
it installs for user code is unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Disable setup-node v5+ automatic package-manager caching

Preserves v4 behavior: caching only when use_node_cache=true.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: ant-kurt <209710463+ant-kurt@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 12:02:35 -07:00
Ashwin BhatandGitHub 420335da51 Add workload identity federation support to base-action (#1378)
* Add workload identity federation support to base-action

Move the workload identity module into base-action so the standalone
action can fetch and refresh the GitHub OIDC identity token itself, and
expose the same federation inputs as the outer action. Switch the
base-action test workflows from the anthropic_api_key secret to the
federation repo variables and grant them id-token: write.

* Verify MCP test tool invocation instead of init connection status

MCP servers can connect asynchronously, so the init event may report a
server as pending. Check that the server is registered at init, then
assert the test tool was actually called and returned its response.
Also pass the MCP config through claude_args --mcp-config, replacing the
removed mcp_config input.
2026-06-02 11:52:23 -07:00
GitHub Actions 7f37f2e373 chore: bump Claude Code to 2.1.160 and Agent SDK to 0.3.160 2026-06-02 02:11:04 +00:00
GitHub Actions fb53c379a0 chore: bump Claude Code to 2.1.159 and Agent SDK to 0.3.159 2026-05-31 19:43:36 +00:00
GitHub Actions c5c315c8a1 chore: bump Claude Code to 2.1.158 and Agent SDK to 0.3.158 2026-05-30 02:42:43 +00:00
GitHub Actions f809dea0ba chore: bump Claude Code to 2.1.157 and Agent SDK to 0.3.157 2026-05-29 20:21:24 +00:00
GitHub Actions 0fb1b8f303 chore: bump Claude Code to 2.1.156 and Agent SDK to 0.3.156 2026-05-29 01:43:14 +00:00
GitHub Actions 3d4c9fde8e chore: bump Claude Code to 2.1.154 and Agent SDK to 0.3.154 2026-05-28 18:02:04 +00:00
GitHub Actions 324957b26b chore: bump Claude Code to 2.1.153 and Agent SDK to 0.3.153 2026-05-28 00:52:58 +00:00
GitHub Actions 73c91f04a8 chore: bump Claude Code to 2.1.152 and Agent SDK to 0.3.152 2026-05-27 01:31:47 +00:00
GitHub Actions 787c5a0ce9 chore: bump Claude Code to 2.1.150 and Agent SDK to 0.3.150 2026-05-23 04:04:43 +00:00
Ashwin BhatandGitHub 4257c8e059 Use workload identity federation for Claude auth in CI workflows (#1344)
Switch claude.yml, claude-review.yml, and issue-triage.yml from the
anthropic_api_key secret to the workload identity federation inputs.
The federation rule, organization, and service account IDs are read
from repository variables; issue-triage.yml additionally gains the
id-token: write permission required to request the OIDC token.
2026-05-22 15:46:06 -07:00
GitHub Actions bbfaf8e1ff chore: bump Claude Code to 2.1.149 and Agent SDK to 0.3.149 2026-05-22 22:10:12 +00:00
GitHub Actions 4481e6d3c7 chore: bump Claude Code to 2.1.148 and Agent SDK to 0.3.148 2026-05-22 01:17:47 +00:00
Ashwin BhatandGitHub 661a6fefbd Add Workload Identity Federation (OIDC) authentication support (#1338)
* Add workload identity federation auth support

Adds anthropic_federation_rule_id, anthropic_organization_id,
anthropic_service_account_id, anthropic_workspace_id, and
anthropic_oidc_audience inputs. When the federation rule and organization
are set, the action fetches the workflow's GitHub Actions OIDC token,
writes it to a file in RUNNER_TEMP, keeps it refreshed during execution,
and points the Claude Code CLI at it via ANTHROPIC_IDENTITY_TOKEN_FILE so
the CLI can exchange it for a short-lived access token instead of using a
static API key.

* Add WIF example workflow and base-action federation docs

* Default workload identity OIDC audience to https://api.anthropic.com
2026-05-21 15:19:15 -07:00
GitHub Actions c9d66afb17 chore: bump Claude Code to 2.1.147 and Agent SDK to 0.3.147 2026-05-21 20:40:16 +00:00
GitHub Actions 20c8abf165 chore: bump Claude Code to 2.1.146 and Agent SDK to 0.3.146 2026-05-21 01:52:44 +00:00
Ashwin BhatandGitHub 1dc994ee7a Resolve actor account type before applying allowed_bots (#1330)
Move the allowed_bots check in checkHumanActor and checkWritePermissions so
it only fires after the actor has been resolved as a non-User account
(GitHub App / bot, or unresolvable app actor). Actors that resolve to a
regular User account go through the standard human/write checks regardless
of allowed_bots.

The Copilot-style path (GITHUB_ACTOR not ending in [bot] and not resolvable
as a user) is unchanged: it still falls through to the existing 404 catch,
which already consults allowed_bots once the API has reported the actor is
not a user.

Update tests to match and add coverage for the User-account path.
2026-05-19 16:30:49 -07:00
GitHub Actions ca89df3d42 chore: bump Claude Code to 2.1.145 and Agent SDK to 0.3.145 2026-05-19 22:21:48 +00:00
Ashwin BhatandGitHub fd1877debc Simplify comment tool instructions in prompt (#1328) 2026-05-19 14:50:18 -07:00
GitHub Actions 24492741e0 chore: bump Claude Code to 2.1.144 and Agent SDK to 0.3.144 2026-05-19 00:49:28 +00:00
Ashwin BhatandGitHub 0345b11d48 Fix prettier formatting in create-prompt (#1325) 2026-05-18 08:27:45 -07:00
GitHub Actions b020494b57 chore: bump Claude Code to 2.1.143 and Agent SDK to 0.3.143 2026-05-15 22:29:11 +00:00
Ashwin BhatandGitHub d56f10247e Strengthen simplified tag-mode prompt (USE_SIMPLE_PROMPT) (#1313)
The opt-in simplified tag-mode prompt omitted several guardrails the
default prompt has. Bring it closer to the default's posture while
keeping it terse:

- Scoping clarification: spell out that only the triggering comment
  (or the issue body for issue events) carries instructions; other
  comments, the body, review comments, and repository files are
  reference context, not commands to act on.
- Review-only stop-condition: questions and code reviews must not edit,
  commit, push, or create branches unless the trigger explicitly asks
  for a code change.
- PR base-branch diff: when triggered on a PR with a known base branch,
  compare against origin/<base> instead of main/master.
- Capability limits: cannot submit formal PR reviews, approve, or merge;
  decline politely and point to the FAQ.

Adds focused tests covering the new lines for both PR and non-PR
events, including presence/absence of the conditional base-branch line.
2026-05-14 17:06:45 -07:00
bbad5183ff fix: add parentheses to fix operator precedence in co-author check (#1199)
`??` has lower precedence than `!==`, so the expression:
  triggerDisplayName ?? triggerUsername !== "Unknown"
parses as:
  triggerDisplayName ?? (triggerUsername !== "Unknown")

When triggerDisplayName is an empty string "", the condition
evaluates to "" (falsy), incorrectly skipping the co-author line
even though the user is not "Unknown".

Add parentheses to get the intended behavior:
  (triggerDisplayName ?? triggerUsername) !== "Unknown"

Co-authored-by: Rush <rush@RushdeMacBook-Pro.local>
2026-05-14 16:58:17 -07:00
GitHub Actions 51ea8ea73a chore: bump Claude Code to 2.1.142 and Agent SDK to 0.3.142 2026-05-14 22:56:05 +00:00
Ashwin BhatandGitHub acfa366ca8 chore: bump pinned Bun to 1.3.14 (#1312)
* chore: bump pinned Bun to 1.3.14

* style: apply prettier to actor/permissions files
2026-05-14 18:55:04 -04:00
9eb125afe3 fix: handle non-user actors (e.g. Copilot) in permission and actor checks (#1144)
GitHub Apps like Copilot SWE Agent set GITHUB_ACTOR to a value (e.g.
"Copilot") that is neither a valid GitHub user nor ends with "[bot]".
This caused two independent crashes:

1. checkWritePermissions (permissions.ts): called the collaborator
   permission API which returns 404 "is not a user" for non-user actors.
2. checkHumanActor (actor.ts): called the Users API first, which 404s,
   before ever reaching the allowed_bots check.

Fix both by:
- Checking allowed_bots BEFORE making API calls, so known bots skip the
  API entirely.
- In permissions.ts, catching "is not a user" 404 errors and falling
  back to the allowed_bots list instead of crashing.
- In actor.ts, catching 404 errors and providing a clear error message
  telling the user to add the bot to allowed_bots.

Closes #900, #903, #1018, #1133

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-14 15:38:34 -07:00
JerryLeeandGitHub 1450f658d3 fix: write execution file when SDK throws (#1255) 2026-05-14 15:37:28 -07:00
Christian VanandGitHub 0756f6ef2b fix: exclude .claude-pr snapshot from git staging (#1277) 2026-05-14 15:36:57 -07:00
Matan BaruchandGitHub f4d6a11de1 fix: dereference symlinks when snapshotting sensitive paths to .claude-pr/ (#1186)
`cpSync` defaults to `dereference: false`, which means it tries to
recreate symlinks at the destination rather than copying file contents.
When a sensitive path (e.g. CLAUDE.md) is a symlink, this fails with
`ENOENT: no such file or directory, symlink` because `cpSync` attempts
to call `symlink()` without ensuring the parent `.claude-pr/` directory
exists first.

Adding `dereference: true` fixes this by following symlinks and copying
the actual file contents, which is also the correct semantic behavior —
review agents need to inspect the real content, not a symlink that may
not resolve correctly from `.claude-pr/`.

Fixes the action crash when repositories use symlinked CLAUDE.md
(e.g. CLAUDE.md -> AGENTS.md).
2026-05-14 15:36:09 -07:00
ricoandGitHub bf6d40e068 fix: allow , in branch names (#1310)
`validateBranchName` rejects branch names containing a comma, even
though `git check-ref-format` permits commas and GitHub itself accepts
them. PRs whose head branch contains a `,` fail validation in-process
before any git operation, so the action errors out immediately.

Branch names with commas show up in real workflows when names are
derived from titles, place names, or external identifiers (e.g.
"feature/paris,france"). There is no workaround other than renaming
the branch, which is often not under the user's control.

All git calls in this file use execFileSync with an argv array, so no
shell interpretation occurs and `,` carries no injection risk. This is
the same reasoning used to add `#` in #1167 and `+` in #1248.

- Add `,` to the validateBranchName whitelist regex
- Update the surrounding comment and error message to match
- Add a test case covering commas in title-derived branch names

Fixes #1300
2026-05-14 15:34:32 -07:00
GitHub Actions 86eb26bf01 chore: bump Claude Code to 2.1.141 and Agent SDK to 0.2.141 2026-05-13 23:19:48 +00:00
GitHub Actions f4fb5c6cdc chore: bump Claude Code to 2.1.140 and Agent SDK to 0.2.140 2026-05-12 21:10:29 +00:00
GitHub Actions dde2242db6 chore: bump Claude Code to 2.1.139 and Agent SDK to 0.2.139 2026-05-11 18:44:48 +00:00
GitHub Actions 476e359e62 chore: bump Claude Code to 2.1.138 and Agent SDK to 0.2.138 2026-05-09 06:34:03 +00:00
GitHub Actions ad67978e5e chore: bump Claude Code to 2.1.137 and Agent SDK to 0.2.137 2026-05-09 00:11:30 +00:00
GitHub Actions 034cbdb008 chore: bump Claude Code to 2.1.136 and Agent SDK to 0.2.136 2026-05-08 18:39:38 +00:00
GitHub Actions 939ae9c056 chore: bump Claude Code to 2.1.133 and Agent SDK to 0.2.133 2026-05-07 23:49:29 +00:00
Octavian GuzuandGitHub e9c374db23 Update HackerOne links in SECURITY.md (#1268)
* Update HackerOne links in SECURITY.md

🏠 Remote-Dev: homespace

* Rename VDP heading to Anthropic Bug Bounty

🏠 Remote-Dev: homespace
2026-05-07 11:21:40 +01:00
GitHub Actions 9db782c3a1 chore: bump Claude Code to 2.1.132 and Agent SDK to 0.2.132 2026-05-06 22:09:09 +00:00
GitHub Actions 62238ddb33 chore: bump Claude Code to 2.1.131 and Agent SDK to 0.2.131 2026-05-06 07:48:07 +00:00
GitHub Actions 7d7d3055f1 chore: bump Claude Code to 2.1.129 and Agent SDK to 0.2.129 2026-05-06 01:40:52 +00:00
GitHub Actions 2cc1ac1331 chore: bump Claude Code to 2.1.128 and Agent SDK to 0.2.128 2026-05-04 23:02:42 +00:00
Justin BisignanoandGitHub 38f25dd747 fix: make trigger_phrase match case-insensitive (#1279)
GitHub @-mention autocomplete inserts @Claude (capitalized) when users
pick the bot from the dropdown, but the trigger regex had no 'i' flag,
so the action would log 'No trigger was met for @claude' and exit. The
workflow's outer 'if: contains(...)' gate is case-insensitive, so the
job runs and looks like it silently ignored the user.

The regex was case-sensitive since the initial commit with no test
asserting either behavior; the existing tests focus on word-boundary
precision (email@claude.com etc.), not case.
2026-05-02 10:10:30 -07:00
GitHub Actions fefa07e9c6 chore: bump Claude Code to 2.1.126 and Agent SDK to 0.2.126 2026-05-01 02:05:59 +00:00
GitHub Actions ef50f123a3 chore: bump Claude Code to 2.1.123 and Agent SDK to 0.2.123 2026-04-29 03:29:24 +00:00
GitHub Actions b3c0320e7e chore: bump Claude Code to 2.1.122 and Agent SDK to 0.2.122 2026-04-28 22:05:53 +00:00
Octavian GuzuandGitHub c93e8fe879 docs: pull_request_target guidance and base-action trust model (#1250)
* docs: add pull_request_target/workflow_run guidance and base-action trust model

Adds a security.md section on safe checkout patterns under
pull_request_target/workflow_run, and a trust-model section to the
base-action README clarifying that callers are responsible for the
working directory and prompt being trusted.

🏠 Remote-Dev: homespace

* docs: refine PRT/workflow_run guidance — root checkout + workflow_run ref

Second example now checks out the base ref at the workspace root before
the head-ref subdirectory checkout (this action expects a git repo at
the root). Adds the workflow_run ref form, drops the PRT-specific
gh-pr-diff hint from the first example, and generalises the closing
line to cover both event types.

🏠 Remote-Dev: homespace

* docs: use actions/checkout@v6 in examples (consistency)

🏠 Remote-Dev: homespace
2026-04-28 10:01:48 -07:00
GitHub Actions 11a9dadd19 chore: bump Claude Code to 2.1.121 and Agent SDK to 0.2.121 2026-04-28 00:31:46 +00:00
GitHub Actions 567fe954a4 chore: bump Claude Code to 2.1.119 and Agent SDK to 0.2.119 2026-04-25 01:55:30 +00:00
GitHub Actions 2da6cfae68 chore: bump Claude Code to 2.1.120 and Agent SDK to 0.2.120 2026-04-25 00:15:05 +00:00
GitHub Actions e58dfa5555 chore: bump Claude Code to 2.1.119 and Agent SDK to 0.2.119 2026-04-23 23:24:21 +00:00
6ee201f023 fix: allow + in branch names (generated by Claude Code EnterWorktree) (#1248)
Claude Code's EnterWorktree tool converts "/" to "+" when generating
branch names from worktree names (e.g. EnterWorktree("feat/foo") creates
branch "worktree-feat+foo"). The strict whitelist in validateBranchName
rejected these names, causing claude-code-action to fail on any PR opened
from an EnterWorktree-generated branch.

Since all git calls use execFileSync (not shell interpolation), "+" carries
no command injection risk — the same rationale used for allowing "#".
Git itself permits "+" in branch names per git-check-ref-format.

Fixes: https://github.com/anthropics/claude-code-action/issues/1244

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 22:17:44 -07:00
GitHub Actions b4d6741327 chore: bump Claude Code to 2.1.118 and Agent SDK to 0.2.118 2026-04-23 00:42:34 +00:00
GitHub Actions 4e5d8b13ca chore: bump Claude Code to 2.1.117 and Agent SDK to 0.2.117 2026-04-22 00:04:56 +00:00
GitHub Actions 5d5c10a4f3 chore: bump Claude Code to 2.1.116 and Agent SDK to 0.2.116 2026-04-20 22:18:45 +00:00
Octavian GuzuandGitHub 632a368e81 docs: nit updates to security.md (#1240)
🏠 Remote-Dev: homespace
2026-04-20 15:00:35 +01:00
Ashwin BhatandGitHub 4c682d8b65 chore: bump oven-sh/setup-bun to v2.2.0 (Node.js 24) (#1238)
setup-bun v2.1.2 runs on Node.js 20, which GitHub will stop supporting
on June 2, 2026. v2.2.0 updates the action runtime to Node.js 24.

Fixes #1237
2026-04-19 17:53:46 -07:00
GitHub Actions 38ec876110 chore: bump Claude Code to 2.1.114 and Agent SDK to 0.2.114 2026-04-18 01:38:24 +00:00
Ashwin BhatandGitHub 0d2971c794 fix: pass install.sh binary path explicitly to Agent SDK (#1235)
Agent SDK 0.2.113 dropped vendor/ripgrep and now ships native binaries
via per-platform optionalDependencies. Two breakages:

- action.yml chmod'd vendor/ripgrep which no longer exists, failing the
  Install Dependencies step with find exit 1.
- The SDK auto-resolves its bundled binary by trying the -musl platform
  package before the glibc one. bun install does not respect the
  package.json libc field and installs both on glibc Linux, so the SDK
  picks the musl binary and spawn fails with ENOENT.

Remove the obsolete ripgrep chmod. Make installClaudeCode() return the
install.sh binary path and pass it explicitly as
pathToClaudeCodeExecutable so the SDK skips auto-resolution entirely.
2026-04-17 15:50:46 -07:00
GitHub Actions c68f82cb11 chore: bump Claude Code to 2.1.113 and Agent SDK to 0.2.113 2026-04-17 19:40:20 +00:00
78758edf84 chore: bump model version in workflows (#1227)
https://claude.ai/code/session_01Mc8dExn9NcARLohJ1XG5kv

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-16 15:25:28 -07:00
GitHub Actions c3d45e8e94 chore: bump Claude Code to 2.1.112 and Agent SDK to 0.2.112 2026-04-16 20:00:08 +00:00
GitHub Actions 931e620273 chore: bump Claude Code to 2.1.111 and Agent SDK to 0.2.111 2026-04-16 15:22:22 +00:00
GitHub Actions 905d4eb99a chore: bump Claude Code to 2.1.110 and Agent SDK to 0.2.110 2026-04-15 22:06:40 +00:00
GitHub Actions 5fb899572b chore: bump Claude Code to 2.1.109 and Agent SDK to 0.2.109 2026-04-15 04:05:34 +00:00
c3bf66dbc2 fix: handle fork PRs by fetching via refs/pull/N/head (#962) (#963)
When a PR originates from a fork, `git fetch origin <branch>` fails
because the branch only exists on the fork's remote.

Fix: detect cross-repository PRs via the `isCrossRepository` GraphQL
field and fetch using `pull/<number>/head:<branch>` refspec instead,
which is the standard GitHub mechanism for accessing fork PR branches.

Changes:
- Add `isCrossRepository` and `headRepository` to PR GraphQL query
- Add corresponding fields to GitHubPullRequest type
- Branch checkout uses pull ref for fork PRs
- Update test fixtures with new fields

Co-authored-by: User <user@example.com>
2026-04-14 20:33:04 -07:00
GitHub Actions 3943183052 chore: bump Claude Code to 2.1.108 and Agent SDK to 0.2.108 2026-04-14 19:16:11 +00:00
GitHub Actions 65f29cf68e chore: bump Claude Code to 2.1.107 and Agent SDK to 0.2.107 2026-04-14 06:14:35 +00:00
GitHub Actions 1c8b699d43 chore: bump Claude Code to 2.1.105 and Agent SDK to 0.2.105 2026-04-13 21:56:13 +00:00
Octavian GuzuandGitHub ff49ec5fd6 Prepend system bin dirs to PATH when allowed_non_write_users is set (#1208)
Ensures later steps resolve standard tools like git and tar from /usr/bin
regardless of what setup actions added earlier in the job. Also strengthens
the PAT guidance in security.md.

🏠 Remote-Dev: homespace
2026-04-12 21:51:15 +01:00
GitHub Actions 25474bfe8b chore: bump Claude Code to 2.1.104 and Agent SDK to 0.2.104 2026-04-12 03:21:43 +00:00
GitHub Actions b47fd721da chore: bump Claude Code to 2.1.101 and Agent SDK to 0.2.101 2026-04-10 19:06:59 +00:00
GitHub Actions c26cb6427d chore: bump Claude Code to 2.1.100 and Agent SDK to 0.2.98 2026-04-10 05:15:24 +00:00
GitHub Actions 657fb7c9c9 chore: bump Claude Code to 2.1.98 and Agent SDK to 0.2.98 2026-04-09 19:21:28 +00:00
GitHub Actions 2ff1acb3ee chore: bump Claude Code to 2.1.97 and Agent SDK to 0.2.97 2026-04-08 21:55:30 +00:00
Octavian GuzuandGitHub b2fdd80112 Use pinned bun binary for post-steps when allowed_non_write_users is set (#1190)
Copies the bun binary into $GITHUB_ACTION_PATH/bin before the claude step
runs and uses that copy in the two post-steps that invoke bun. Falls back
to PATH-resolved bun when allowed_non_write_users is empty.

🏠 Remote-Dev: homespace
2026-04-08 10:20:15 +01:00
GitHub Actions 26ddc358fe chore: bump Claude Code to 2.1.96 and Agent SDK to 0.2.96 2026-04-08 04:40:59 +00:00
GitHub Actions 398370690e chore: bump Claude Code to 2.1.94 and Agent SDK to 0.2.94 2026-04-07 21:22:37 +00:00
6cad158a17 security: reject PATH_TO_CLAUDE_CODE_EXECUTABLE with control characters (#1185)
dirname() preserves embedded newlines, so a value like
`/usr/bin/claude\n/attacker/path` writes two lines to GITHUB_PATH,
injecting an attacker-controlled directory into PATH for all subsequent
workflow steps.

Validate the input immediately after reading it and throw if it
contains any control characters (0x00-0x1f, 0x7f). This is fail-closed
rather than silent stripping — a path with control characters is always
misconfigured or malicious.

Fixes #1160

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 17:26:08 -07:00
0f1fe5ef85 fix: forward MCP_TIMEOUT, MCP_TOOL_TIMEOUT, MAX_MCP_OUTPUT_TOKENS to action step (#1162)
These three env vars are read directly from process.env by the Claude CLI
subprocess to configure MCP server behavior. Users setting them in their
workflow had no reliable way to make them reach the CLI:

- Job-level env: shadowed by the step's explicit env: block
- Step-level env: on the calling workflow step is not inherited by
  composite action steps
- GITHUB_ENV from a prior step: same shadowing problem
- settings input: writes to ~/.claude/settings.json, not process.env

The fix is to add explicit ${{ env.VAR }} passthrough lines for all three
vars, matching the existing pattern already used for OTEL_*, AWS_*, and
Vertex configuration (lines 271-317). No TypeScript changes are needed;
the forwarding chain in parse-sdk-options.ts is already correct.

Fixes #1152

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 10:37:03 -07:00
Ashwin BhatandGitHub 6e2bd52842 fix: pin bun runtime config and improve log hygiene (#1174)
* fix: pin bun runtime config and improve log hygiene

* snapshot all SENSITIVE_PATHS to .claude-pr/, not just .claude/
2026-04-05 07:42:02 -07:00
Ashwin BhatandGitHub 3534c326a5 chore: fix prettier formatting in parse-sdk-options.test.ts (#1176) 2026-04-04 23:10:12 -07:00
Ashwin BhatandGitHub 6685b26dfb chore: fix prettier formatting (#1171) 2026-04-04 20:56:18 -07:00
5150ea9643 fix: snapshot PR's .claude/ to .claude-pr/ before security restore (#1172)
When a PR modifies files under .claude/, the security restore in
restoreConfigFromBase() overwrites them with the base branch version —
correct for execution safety, but it means review agents never see what
the PR actually changes.

Before deleting the PR-controlled .claude/ tree, copy it to .claude-pr/.
Review agents can read .claude-pr/ to inspect the PR's hooks, MCP
configs, settings, and CLAUDE.md without those files ever being executed.
The snapshot is taken before the security delete so it captures the full
PR-authored version.

Fixes #1134.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 20:47:27 -07:00
eb8baa46af fix: strip shell comment lines before parsing claude_args (#1055)
shell-quote treats # as a shell comment character, swallowing all
subsequent content including flags on new lines. Strip comment lines
(lines starting with #) before passing input to shell-quote.

Fixes #802

Co-authored-by: VoidChecksum <Admin@CyberNord>
2026-04-04 20:26:13 -07:00
f328a5c889 fix: prevent hang in restoreConfigFromBase on repos with .gitmodules (#1166)
When a PR head contains `.gitmodules`, git's default
`fetch.recurseSubmodules=on-demand` config causes `git fetch` to attempt
submodule object fetches. In CI (no credentials), this blocks indefinitely
waiting for auth — producing ~4-hour hangs reported in #1088.

Two changes, both defence-in-depth:

1. Delete SENSITIVE_PATHS *before* fetching. The attacker-controlled
   `.gitmodules` is absent during the network operation, so git never
   sees a submodule config to follow regardless of git settings.

2. Pass `--no-recurse-submodules` to the fetch. Suppresses submodule
   fetching explicitly, independent of any git config on the runner.

The original order (fetch-then-delete) was a brief window where
`.gitmodules` from the PR head could influence the fetch. Reordering
also tightens the security property: if `git checkout` below fails, the
attacker-controlled file is already gone rather than present during fetch.

Fixes #1088.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 20:21:28 -07:00
b15d4751a6 fix: allow # in branch names for PR checkout and base restore (#1167)
`validateBranchName` used a strict whitelist that excluded `#`,
causing the action to fail on PRs from branches like `put-back-arm64-#2`
with "Invalid branch name" — even though the branch already exists in
git and `#` is permitted by git-check-ref-format.

The validation was designed to prevent command injection. However, every
git call in the action uses `execFileSync`, which bypasses the shell
entirely and passes arguments directly to the kernel's execve. There is
no shell to interpret `#` as a metacharacter, so the strict whitelist was
over-blocking valid names with no security benefit.

Add `#` to the whitelist pattern, and update the JSDoc and error message
to reflect the allowed character set.

Fixes #1137.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 20:17:46 -07:00
d5db8208f9 fix: restore ripgrep execute bits after bun install --production (#1163)
bun install --production strips execute bits from vendored binaries
(bun bug). The Claude Agent SDK ships rg binaries in:

  node_modules/@anthropic-ai/claude-agent-sdk/vendor/ripgrep/
    {x64,arm64}-{linux,darwin}/rg
    {x64,arm64}-win32/rg.exe

After bun --production, all of these lose +x, causing EACCES when the
SDK tries to spawn ripgrep. The fix is a targeted find(1) that restores
+x on the rg binaries immediately after bun install.

Design notes:
- -type f excludes symlinks (symlink attack safety, no || true needed)
- -name "rg" naturally excludes rg.exe on Windows (find returns nothing,
  chmod never called — safe and correct on all platforms)
- .node audio-capture files use dlopen, not exec — no +x needed there
- Fails loudly if the binary path is missing (no || true) so a SDK
  packaging change is immediately visible rather than silently broken

Fixes #1140

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 20:15:31 -07:00
d8af4e9f01 fix: skip retries for non-retryable errors in retryWithBackoff (#1082)
Add shouldRetry predicate to RetryOptions so callers can abort retries
for errors that will never succeed (e.g. 401 WorkflowValidationSkipError).

Previously, retryWithBackoff retried all errors blindly, wasting ~35s on
deterministic failures like workflow validation 401s.

Fixes #1081

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-04 20:14:47 -07:00
chyipinandGitHub f37c786ad3 Strip OIDC token request env vars from Claude session (#1011)
When id-token: write permission is enabled, ACTIONS_ID_TOKEN_REQUEST_URL
and ACTIONS_ID_TOKEN_REQUEST_TOKEN are passed to the Claude session via
the process.env spread in parseSdkOptions(). This allows Claude to mint
new OIDC tokens, which is an unintended capability.

This commit deletes these two variables from the env object before passing
it to the Claude SDK. The OIDC flow in token.ts reads directly from
process.env and runs before parseSdkOptions(), so it is unaffected.

Fixes #1010
2026-04-04 20:13:05 -07:00
21b0f0f9aa fix: use correct fallback type for reviewData in fetcher (#1034)
The reviewData variable is typed as `{ nodes: GitHubReview[] } | null`,
but the fallback value was `[]` (a plain array). When
`pullRequest.reviews` is null/undefined, `reviewData` becomes `[]`,
causing `reviewData.nodes` to return `undefined` instead of `[]`.

This leads to silent failures in downstream code that iterates over
`reviewData.nodes`, such as `filterReviewsToTriggerTime` and
`filterCommentsByActor`.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 20:12:05 -07:00
Mario Yuri Mota LaraandGitHub 27f549ae64 docs: document include/exclude_comments_by_actor inputs (#1130)
* docs: document include_comments_by_actor and exclude_comments_by_actor inputs

These inputs were added in #812 but never documented in usage.md or
security.md. This adds them to the inputs table in usage.md and
references comment filtering as a prompt injection mitigation in
security.md.

Fixes #972

* docs: clarify wildcard support is limited to *[bot] pattern

Address review feedback: "Supports wildcards" was misleading since
only the *[bot] pattern is supported, not general glob matching.
2026-04-04 20:10:29 -07:00
David DworkenandGitHub 263993d836 Use env vars for workflow_run context values in example workflows (#1125)
* Use env vars for workflow_run context values in example workflows

* Add security note to ci-failure-auto-fix example about trust requirements
2026-04-04 20:10:11 -07:00
85133eeab2 fix: skip token revocation when no token was acquired (#918)
Add a check for non-empty github_token output before attempting to
revoke the app token in the cleanup step. When the prepare phase fails
(e.g., unsupported event type with track_progress), no token is
acquired, causing the cleanup curl to send an empty Bearer token
and produce a confusing "Bad credentials" 401 error.

Fixes #858

Co-authored-by: Dave-London <hello@os4us.org>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 20:09:21 -07:00
GitHub Actions 1eddb334cf chore: bump Claude Code to 2.1.92 and Agent SDK to 0.2.92 2026-04-04 00:45:34 +00:00
GitHub Actions 0432df8bfe chore: bump Claude Code to 2.1.91 and Agent SDK to 0.2.91 2026-04-03 00:19:01 +00:00
Octavian GuzuandGitHub ba026a3e56 Pass env to execFileSync git calls (#1151)
Bun's execFileSync without an explicit env option spawns with the process
startup environment, dropping runtime process.env mutations. The credential
helper reads GH_TOKEN which is set at runtime, so git fetch in the
restore-config path failed with empty password.

Fixes #1139

🏠 Remote-Dev: homespace
2026-04-02 21:52:02 +01:00
Octavian GuzuandGitHub c95e735eb1 Fix subprocess isolation install step never running (#1148)
env context isn't available in composite-action if: conditions.
Move opt-out check into run: body.

🏠 Remote-Dev: homespace
2026-04-02 14:05:08 +01:00
GitHub Actions 58dbe8ed68 chore: bump Claude Code to 2.1.90 and Agent SDK to 0.2.90 2026-04-01 23:57:02 +00:00
Ashwin BhatandGitHub c281e17d7f fix: fall back to repo default_branch instead of hardcoded "main" (#1143)
* fix: fall back to repo default_branch instead of hardcoded "main"

When no explicit base_branch input is provided, the action previously
fell back to a hardcoded "main", which fails on repositories whose
default branch is named differently (e.g. "master", "develop").

This reads repository.default_branch from the GitHub event payload
(populated once in parseGitHubContext) and uses it as the fallback in
all three callsites: agent/index.ts, run.ts, and update-comment-link.ts.
Explicit env/input precedence is preserved; "main" remains only as a
last-resort defensive fallback if the payload somehow lacks the field.

* test: drop unused BASE_BRANCH env handling from default_branch test

agent/index.ts no longer reads process.env.BASE_BRANCH directly (it now
goes through context.inputs.baseBranch which is set on the mock context),
so saving/clearing/restoring that env var in the regression test is dead
code.
2026-04-01 14:48:46 -07:00
Ashwin BhatandGitHub 408a40e7c2 Pin Claude Code to 2.1.87 (#1142)
* Revert "chore: bump Claude Code to 2.1.89 and Agent SDK to 0.2.89"

This reverts commit bee87b3258.

* Revert "chore: bump Claude Code to 2.1.88 and Agent SDK to 0.2.88"

This reverts commit 7225f045c6.
2026-04-01 11:29:30 -07:00
GitHub Actions bee87b3258 chore: bump Claude Code to 2.1.89 and Agent SDK to 0.2.89 2026-04-01 01:13:44 +00:00
Octavian GuzuandGitHub 32156b120b Add subprocess isolation setup and git credential helper (#1132)
- Add optional bubblewrap setup step for Linux subprocess isolation
  when allowed_non_write_users is configured
- Use git credential helper instead of embedding token in remote URL
- edit-issue-labels.sh: read issue number from workflow event payload
  instead of CLI arg
- Add CLAUDE_CODE_SCRIPT_CAPS env for per-script call limit config
- docs/security.md: note recommended github_token configuration

🏠 Remote-Dev: homespace
2026-03-31 12:36:51 +01:00
GitHub Actions 7225f045c6 chore: bump Claude Code to 2.1.88 and Agent SDK to 0.2.88 2026-03-31 00:35:26 +00:00
GitHub Actions 88c168b39e chore: bump Claude Code to 2.1.87 and Agent SDK to 0.2.87 2026-03-29 02:29:10 +00:00
GitHub Actions e7b588b6ea chore: bump Claude Code to 2.1.86 and Agent SDK to 0.2.86 2026-03-27 21:50:59 +00:00
GitHub Actions 094bd24d57 chore: bump Claude Code to 2.1.85 and Agent SDK to 0.2.85 2026-03-26 22:51:40 +00:00
GitHub Actions 3ac52d0da9 chore: bump Claude Code to 2.1.84 and Agent SDK to 0.2.84 2026-03-26 00:37:42 +00:00
GitHub Actions 0ee1beea58 chore: bump Claude Code to 2.1.83 and Agent SDK to 0.2.83 2026-03-25 06:35:03 +00:00
Octavian GuzuandGitHub ff9acae588 Auto-set subprocess env scrub when allowed_non_write_users is configured (#1093)
* Auto-set CLAUDE_CODE_SUBPROCESS_ENV_SCRUB when allowed_non_write_users is configured

Sets the env var automatically whenever allowed_non_write_users is
non-empty, so downstream workflows don't need to add it manually.
Updates the input description and docs/security.md to note the behavior.

🏠 Remote-Dev: homespace

* Fall back to inherited env when allowed_non_write_users is unset

🏠 Remote-Dev: homespace

* Let workflow/job env override the auto-set scrub flag

Env var takes priority so users can opt in/out via CLAUDE_CODE_SUBPROCESS_ENV_SCRUB
at job or workflow level independently of allowed_non_write_users.

🏠 Remote-Dev: homespace
2026-03-23 12:10:02 +00:00
GitHub Actions 6062f37096 chore: bump Claude Code to 2.1.81 and Agent SDK to 0.2.81 2026-03-20 22:30:13 +00:00
GitHub Actions df37d2f076 chore: bump Claude Code to 2.1.79 and Agent SDK to 0.2.79 2026-03-18 22:39:18 +00:00
David DworkenandGitHub 1ba15be4f0 Remove redundant git status/diff/log from tag mode allowlist (#1075) 2026-03-18 09:06:33 -07:00
kashyap muraliandGitHub 9ddce40de8 Restore .claude/ and .mcp.json from PR base branch before CLI runs (#1066)
* Restore .claude/ and .mcp.json from PR base branch before CLI runs

The CLI's non-interactive mode trusts cwd: it reads .mcp.json and
.claude/settings{,.local}.json from the working directory and acts on
them before any tool-permission gating — executing hooks, setting env
vars (NODE_OPTIONS, LD_PRELOAD), running apiKeyHelper shell commands,
and auto-approving MCP servers. When this action checks out a PR head,
these files are attacker-controlled.

Rather than enumerate dangerous keys, replace the entire .claude/ tree
and .mcp.json with the versions from the PR base branch (which a
maintainer has reviewed). Paths absent on base are deleted. Uses local
git state, so no TOCTOU against the GitHub API.

* Read PR base ref from payload for config restore in agent mode

Agent mode's branchInfo.baseBranch defaults to "main" (or env/input
override) instead of the PR's actual target branch — it doesn't query
prData.baseRefName like tag mode does. This meant a PR targeting
develop would get .claude/ restored from main.

Fix by reading pull_request.base.ref directly from the webhook payload
for pull_request, pull_request_review, and pull_request_review_comment
events. For issue_comment on a PR (no base.ref in payload), fall back
to the mode-provided value — tag mode's value is correct (from GraphQL);
agent mode on issue_comment is an edge case that at worst restores from
the wrong trusted branch, which is still secure.

The payload value passes through validateBranchName for defense-in-depth
(GitHub enforces valid branch names server-side, but we validate anyway).

* Extend restored paths to .gitmodules, .ripgreprc, .claude.json

.gitmodules defines submodule URLs and paths; path-confusion attacks
against git submodule operations can write into .git/hooks. .ripgreprc
can set --pre (arbitrary command on each file) if RIPGREP_CONFIG_PATH
points at it. .claude.json is cheap defense-in-depth.

Documented why .git/ is excluded (not trackable in commits, and
restoring it would undo the PR checkout), along with .gitconfig
(git never reads it from cwd) and shell rc files (sourced from $HOME,
not cwd — checkout cannot reach $HOME).
2026-03-18 12:00:18 -04:00
GitHub Actions 1b422b3517 chore: bump Claude Code to 2.1.78 and Agent SDK to 0.2.77 2026-03-17 23:47:59 +00:00
GitHub Actions 4c044bb2f5 chore: bump Claude Code to 2.1.77 and Agent SDK to 0.2.77 2026-03-17 00:33:47 +00:00
GitHub Actions cd77b50d2b chore: bump Claude Code to 2.1.76 and Agent SDK to 0.2.76 2026-03-14 01:29:31 +00:00
GitHub Actions 0e80d3c5b8 chore: bump Claude Code to 2.1.75 and Agent SDK to 0.2.75 2026-03-13 17:07:33 +00:00
kashyap muraliandGitHub f956510b1a Harden tag mode tool permissions against prompt injection (#1002)
Two defenses for tag mode where an attacker with repo write access could
craft a prompt injection payload in an issue/PR to gain RCE on the
Actions runner:

1. git-push wrapper (H1 #3556799)
   The Bash(git\ push:*) rule permitted arbitrary flags and remotes,
   including combinations that execute shell commands locally. Replaced
   with scripts/git-push.sh which allowlists exactly 'origin <ref>' with
   no flags, validates the ref via check-ref-format. Same pattern as
   scripts/gh.sh.

2. acceptEdits instead of blanket Write/Edit (Asana 1213310082312048)
   Edit/MultiEdit/Write in allowedTools granted write access to the
   whole runner filesystem (~/.bashrc etc). Removed from allowedTools
   and set --permission-mode acceptEdits, which auto-accepts edits
   inside cwd ($GITHUB_WORKSPACE) and denies outside. Headless SDK has
   no prompt handler so 'ask' becomes deny.

Also:
- Noted that create-prompt/index.ts exports ALLOWED_TOOLS env var that
  nothing reads. The live path is modes/tag/index.ts. Mirrored the fix
  in both so the file the H1 report likely points to stays in sync.
- Updated prompt text (3 callsites) to reference the wrapper.
- Updated tests (4 prompt-content asserts, 7 tool-list asserts).
2026-03-12 13:35:17 -07:00
kashyap muraliandGitHub 5d0cc745cd feat(inline-comment): add confirmed param + probe-pattern safety net (#1048)
* feat(inline-comment): add confirmed param + probe-pattern safety net

Subagents that inherit this tool sometimes probe it with test comments
('Test comment to see if I can create inline comments') after hitting
unrelated errors elsewhere. Recurring issue across customer PRs.

Adds two defenses:
1. confirmed param: set true to post (final review comments should pass
   this). When false, buffers to a JSONL file instead of posting.
2. Probe-pattern safety net: when confirmed is omitted (backward compat
   for existing prompts), the body is checked against obvious probe
   patterns ('test comment', 'can i', 'does this work', etc.). Matching
   calls are buffered instead of posted.

A post-run step in action.yml reports the buffered call count and bodies
as a workflow warning for diagnostics.

Backward compatibility:
- Existing single-agent prompts (no confirmed param) post normally unless
  the body happens to start with a probe phrase (unlikely for real
  review comments)
- The code-review skill is being updated to pass confirmed: true in its
  final posting step
- Subagent probes that would previously post now harmlessly buffer

* refactor: replace probe-regex with Haiku classification in post-step

The regex approach was narrow and could miss creative probe phrasings.
Replaced with a batch Haiku classification that runs after the session
completes.

Flow:
- MCP server: confirmed !== true -> buffer to JSONL (no classification
  in-band, no latency in the tool path)
- Post-step (src/entrypoints/post-buffered-inline-comments.ts): reads
  buffer, sends all bodies to a single Haiku call, posts only those
  classified as real review comments
- confirmed=false entries are never posted regardless of classification

Fail-open: if ANTHROPIC_API_KEY is unavailable (Bedrock/Vertex users)
or the classification call fails, posts all unconfirmed comments. This
matches pre-PR behavior where all calls posted immediately.

The post-step emits ::warning:: for each filtered comment so users can
see what was dropped and why.

* feat: add classify_inline_comments opt-out input

New action input classify_inline_comments (default 'true'). Setting to
'false' restores pre-buffering behavior: all inline comment calls post
immediately regardless of the confirmed param.

Threads through: action input -> CLASSIFY_INLINE_COMMENTS env ->
context.inputs.classifyInlineComments -> MCP server env ->
CLASSIFY_ENABLED module const.

Post-step is also gated on the input so it skips entirely when
classification is disabled.

* docs: document classify_inline_comments input and confirmed param

- usage.md: add classify_inline_comments to inputs table
- solutions.md: mention confirmed=true in the prompt example and explain
  buffering/classification in the tool permissions section
2026-03-12 00:12:55 -07:00
GitHub Actions 567be3da98 chore: bump Claude Code to 2.1.73 and Agent SDK to 0.2.73 2026-03-11 18:33:26 +00:00
GitHub Actions eb99fb38f0 chore: bump Claude Code to 2.1.72 and Agent SDK to 0.2.72 2026-03-10 00:49:35 +00:00
dustinandGitHub 33fbb80626 docs: warn that allowed_bots can expose the action to external triggers (#1039)
allowed_bots does not verify that a matching bot is installed on the
repository or has write access. On a public repo, external GitHub Apps
may be able to trigger workflow events (issues, comments, PR reviews).
If the workflow listens on those events and allowed_bots is '*', an
external App can invoke this action with a prompt it controls.

Default config (allowed_bots: "") is unaffected.

- docs/security.md: add warning and mitigation guidance
- docs/usage.md: add inline warning to the allowed_bots input row
- action.yml: add warning to the allowed_bots input description

🏠 Remote-Dev: homespace
2026-03-09 13:04:11 -07:00
GitHub Actions 3428ca8991 chore: bump Claude Code to 2.1.71 and Agent SDK to 0.2.71 2026-03-07 00:11:30 +00:00
GitHub Actions 26ec041249 chore: bump Claude Code to 2.1.70 and Agent SDK to 0.2.70 2026-03-06 01:18:43 +00:00
GitHub Actions 1fc90f3ed9 chore: bump Claude Code to 2.1.69 and Agent SDK to 0.2.69 2026-03-05 00:24:53 +00:00
GitHub Actions e763fe78de chore: bump Claude Code to 2.1.68 and Agent SDK to 0.2.68 2026-03-04 10:09:58 +00:00
GitHub Actions 5f8e5bfe5b chore: bump Claude Code to 2.1.66 and Agent SDK to 0.2.66 2026-03-04 01:17:58 +00:00
Octavian GuzuandGitHub 73367208d0 Improve gh.sh wrapper: stricter validation and better error messages (#996)
- Use allowlist for issue view (numeric issue numbers only)
- Enforce zero positional args for issue list / label list
- Pin GH_HOST and GH_REPO explicitly to avoid ambient state
- Add descriptive error messages with usage examples
2026-03-02 16:38:23 +00:00
David DworkenandGitHub 64c7a0ef71 Only expose permission_denials count in sanitized output (#993) 2026-03-02 09:21:16 +00:00
David DworkenGitHubclaude[bot] <209825114+claude[bot]@users.noreply.github.com>claude[bot] <209825114+claude[bot]@users.noreply.github.com>
220272d388 Change the default display_report option to false to restrict exposed data (#992)
* Change the default `display_report` option to false to restrict exposed data

* Update action.yml

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-03-01 21:57:53 -08:00
GitHub Actions ba7fa4bcf0 chore: bump Claude Code to 2.1.63 and Agent SDK to 0.2.63 2026-02-28 03:51:01 +00:00
GitHub Actions 1dd74842e5 chore: bump Claude Code to 2.1.61 and Agent SDK to 0.2.61 2026-02-26 22:39:58 +00:00
GitHub Actions 273fe82540 chore: bump Claude Code to 2.1.59 and Agent SDK to 0.2.59 2026-02-26 01:05:07 +00:00
Octavian GuzuandGitHub e750645f1b Add gh.sh wrapper for gh CLI commands in workflows (#975) 2026-02-25 20:42:29 +00:00
GitHub Actions cd4b150a2a chore: bump Claude Code to 2.1.58 and Agent SDK to 0.2.58 2026-02-25 20:04:44 +00:00
GitHub Actions ade221fd1c chore: bump Claude Code to 2.1.56 and Agent SDK to 0.2.56 2026-02-25 06:37:46 +00:00
GitHub Actions 48fe7dd592 chore: bump Claude Code to 2.1.55 and Agent SDK to 0.2.55 2026-02-25 03:21:10 +00:00
GitHub Actions 6ae1b29ba2 chore: bump Claude Code to 2.1.53 and Agent SDK to 0.2.53 2026-02-25 00:18:46 +00:00
Octavian GuzuandGitHub 7af3506741 Add non-write users check workflow (#973) 2026-02-24 19:47:33 +00:00
GitHub Actions 35a9e0292d chore: bump Claude Code to 2.1.52 and Agent SDK to 0.2.52 2026-02-24 06:44:55 +00:00
GitHub Actions fa3312a107 chore: bump Claude Code to 2.1.51 and Agent SDK to 0.2.51 2026-02-24 01:47:14 +00:00
Octavian GuzuandGitHub dd8541688d Use wrapper script for label operations in issue triage (#968)
* Use wrapper script for label operations in issue triage

Updates /label-issue command and examples to use a dedicated
edit-issue-labels.sh script for label operations instead of raw
gh issue edit. The script validates labels against the repo's existing
labels before applying them. Also tightens gh search permission to
gh search issues.

* Show multiple --add-label flags in label-issue example
2026-02-23 17:16:07 +00:00
GitHub Actions edd85d6153 chore: bump Claude Code to 2.1.49 and Agent SDK to 0.2.49 2026-02-19 23:33:09 +00:00
GitHub Actions 0cf5eeec4f chore: bump Claude Code to 2.1.47 and Agent SDK to 0.2.47 2026-02-18 21:44:15 +00:00
GitHub Actions e6cb7a7ce3 chore: bump Claude Code to 2.1.45 and Agent SDK to 0.2.45 2026-02-17 18:58:59 +00:00
GitHub Actions 2f8ba26a21 chore: bump Claude Code to 2.1.44 and Agent SDK to 0.2.44 2026-02-16 21:40:14 +00:00
Ashwin BhatGitHubclaude[bot] <41898282+claude[bot]@users.noreply.github.com>Ashwin Bhat
cc5ef44546 feat: add display_report option to disable step summary (#952)
Add a `display_report` input parameter (default: "true") that controls
whether the Claude Code Report is written to the GitHub Step Summary.
Setting it to "false" allows users with custom formatting solutions to
avoid duplicate output in the step summary.

Closes #206

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Ashwin Bhat <ashwin-ant@users.noreply.github.com>
2026-02-15 15:49:49 -08:00
Ashwin BhatandGitHub f6a1c4c1b4 fix: grant write permissions and use @main in claude workflow (#950)
Give the workflow contents/pull-requests/issues write permissions so
the OIDC app token can push. Also point to @main instead of @v1.
2026-02-15 15:13:59 -08:00
Ashwin BhatandGitHub 68cfeead18 Revert "fix: replace deprecated :* with modern * wildcard in git permissions (#929)" (#949)
This reverts commit 1bb0e7464b.
2026-02-15 14:39:25 -08:00
Ashwin BhatandGitHub f5088835af Fix stale claudeCodeVersion in run.ts and update bump automation (#943) 2026-02-13 15:12:56 -08:00
GitHub Actions ea36d6abde chore: bump Claude Code to 2.1.42 and Agent SDK to 0.2.42 2026-02-13 19:55:13 +00:00
c22f7c3f9d revert: undo PR checkout fork support and unique branch naming (#937)
Reverts the following commits:
- f669191 fix: use unique local branch names for PR checkout to avoid conflicts (#931)
- 21e3fe0 Fix PR checkout to support fork PRs (#851)

Simplifies PR branch checkout back to using headRefName directly instead
of the pr-{number} local branch naming scheme introduced in #931 and the
GitHub pull ref fetch approach introduced in #851.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 09:45:01 -08:00
f669191d7d fix: use unique local branch names for PR checkout to avoid conflicts (#931)
The previous implementation used the PR's original branch name when
fetching, which could conflict with existing local or remote branches
of the same name. This caused checkout failures for PRs with common
branch names like 'main' or 'feature/xyz'.

Changes:
- Use 'pr-{number}' format for local branch names (e.g., pr-385)
- Preserve original branch name for logging purposes
- Add detailed logging showing original -> local branch mapping

This ensures uniqueness since PR numbers are unique per repository,
while maintaining support for both same-repo and fork PRs via
GitHub's pull/{number}/head refs.

Fixes the issue introduced in #851 where fork PR support was added.

Co-authored-by: Yi-Cheng Wang <yicheng.wang@heph-ai.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-11 19:30:25 -08:00
Octavian GuzuandGitHub 8c383c5de3 fix: skip CI MCP server installation when actions:read permission is missing (#933) 2026-02-11 11:02:49 -08:00
1bb0e7464b fix: replace deprecated :* with modern * wildcard in git permissions (#929)
Replace `Bash(git add:*)` syntax with `Bash(git add *)` in default
tool permissions for tag mode and create-prompt. The colon-prefixed
wildcard syntax is deprecated and causes SDK validation errors.

Closes #856

Co-authored-by: Dave-London <hello@os4us.org>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 21:26:10 -08:00
GitHub Actions 23ed4cb53d chore: bump Claude Code to 2.1.39 and Agent SDK to 0.2.39 2026-02-10 23:10:22 +00:00
Sol RedfernandGitHub 21e3fe0542 Fix PR checkout to support fork PRs (#851)
Use GitHub's PR refs (pull/NUMBER/head) instead of fetching branch
by name. This works for both same-repo and fork PRs because GitHub
automatically creates these refs in the base repository for all PRs.

The branch name doesn't exist on origin for fork PRs, causing:
  fatal: couldn't find remote ref <branch-name>

Using pull/${entityNumber}/head:${branchName} fetches the PR head
and creates a local branch with the correct name.

Fixes issues with tag mode failing on fork PRs.
2026-02-10 08:52:32 -08:00
GitHub Actions b433f16b30 chore: bump Claude Code to 2.1.38 and Agent SDK to 0.2.38 2026-02-10 00:52:13 +00:00
7695f7866a fix: skip dev dependencies in CI install step (#919)
Use `bun install --production` instead of `bun install` in both
action.yml and base-action/action.yml to skip installing devDependencies
(@types/*, prettier, typescript) that are not needed at runtime.

Bun runs TypeScript natively without needing the typescript compiler
or type definition packages. This reduces installed packages from 151
to 135 and speeds up the install step.

Fixes #895

Co-authored-by: Dave-London <hello@os4us.org>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 10:09:41 -08:00
d5b01b6843 Update claude-opus-4-5 to claude-opus-4-6 in workflow (#909)
* Update claude-opus-4-5 to claude-opus-4-6 in workflow

* Fix whitespace formatting in docs and commands

* Fix whitespace formatting in docs and commands

* Add claude-opus-4-6 model to PR review workflow

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-07 14:18:08 -08:00
GitHub Actions 6c61301d8e chore: bump Claude Code to 2.1.37 and Agent SDK to 0.2.37 2026-02-07 19:08:42 +00:00
GitHub Actions db388438c1 chore: bump Claude Code to 2.1.36 and Agent SDK to 0.2.36 2026-02-07 18:00:59 +00:00
GitHub Actions b113f49a56 chore: bump Claude Code to 2.1.33 and Agent SDK to 0.2.33 2026-02-06 01:46:07 +00:00
Ashwin BhatandGitHub 7057f3318b refactor: simplify mode system by removing Mode interface and registry (#899)
Replace the over-engineered Mode interface/registry/detector pattern with
straightforward inline logic. There are only 2 modes (tag and agent) and
the complexity wasn't justified.

- Delete Mode interface, registry, and prepare pass-through modules
- Export prepareTagMode() and prepareAgentMode() as standalone functions
- Inline trigger checking and mode dispatch in run.ts/prepare.ts
- Change generatePrompt/createPrompt to take modeName string instead of Mode
- Remove dead code (extractGitHubContext, unused detector helpers)
- Update CLAUDE.md to reflect new architecture
2026-02-05 17:22:30 -08:00
David DworkenandGitHub f09dc9a6a3 fix: use original body from webhook payload for TOCTOU hardening (#904)
* fix: use original body from webhook payload for TOCTOU hardening

* test: add null originalBody + edited GraphQL body TOCTOU scenario
2026-02-05 10:54:51 -08:00
GitHub Actions 006aaf2935 chore: bump Claude Code to 2.1.32 and Agent SDK to 0.2.32 2026-02-05 17:46:38 +00:00
Ashwin BhatandGitHub 9a3c761f54 refactor: unify action into single composite step with run.ts entrypoint (#898)
* refactor: unify action into single composite step with run.ts entrypoint

Consolidate the prepare and base-action phases into a single composite
step that runs src/entrypoints/run.ts. This simplifies the action.yml
from multiple steps to one execution step, while keeping the same
behavior.

Key changes:
- Add src/entrypoints/run.ts as unified entrypoint
- Simplify action.yml to single 'Run Claude Code Action' step
- Pass all inputs via environment variables
- Update base-action to accept inputs via env vars
- Support agent mode auto-detection from prompt input

* refactor: keep SSH signing cleanup and token revocation as separate action steps

Move SSH signing key cleanup and app token revocation back to separate
composite action steps in action.yml with always() conditions, rather
than handling them inside run.ts. This keeps these cleanup concerns
as independently visible steps in the workflow.

* fix: address PR review feedback

- Use path.dirname() instead of manual string slicing for executable path
- Differentiate prepare vs execution errors in catch block so tracking
  comment accurately reflects which phase failed
- Update CLAUDE.md architecture docs to reflect unified run.ts entrypoint
  and four-phase design

* fix: address PR review feedback

- Use path.dirname() instead of manual string slicing for executable path
- Differentiate prepare vs execution errors in catch block so tracking
  comment accurately reflects which phase failed
- Rewrite CLAUDE.md to focus on mental model, key concepts, and gotchas
  instead of exhaustive file listings
2026-02-03 20:09:43 -08:00
GitHub Actions 6867bb3ab0 chore: bump Claude Code to 2.1.31 and Agent SDK to 0.2.31 2026-02-04 00:42:58 +00:00
GitHub Actions 98af40b63c chore: bump Claude Code to 2.1.30 and Agent SDK to 0.2.30 2026-02-03 18:04:51 +00:00
4ce5f178c2 fix: pass GitHub token to setup-bun to avoid rate limits (#861)
Co-authored-by: Jean-Eudes Peloye <jean-eudes.peloye@adevinta.com>
2026-02-01 14:09:41 -08:00
Sangyeon ChoGitHubClaude Opus 4.5조상연[플레이스 AI] <sang-yeon.cho@navercorp.com>
fab4258c6e fix: pass OpenTelemetry environment variables to Claude Code subprocess (#886)
* fix: pass OpenTelemetry environment variables to Claude Code subprocess

Environment variables set in workflow's step `env:` block were not being
passed to the Claude Code subprocess because composite actions only forward
explicitly referenced environment variables.

This fix adds references for telemetry-related environment variables:
- CLAUDE_CODE_ENABLE_TELEMETRY
- OTEL_METRICS_EXPORTER
- OTEL_LOGS_EXPORTER
- OTEL_EXPORTER_OTLP_PROTOCOL
- OTEL_EXPORTER_OTLP_ENDPOINT
- OTEL_METRIC_EXPORT_INTERVAL
- OTEL_LOGS_EXPORT_INTERVAL
- OTEL_RESOURCE_ATTRIBUTES

Co-Authored-By: 조상연[플레이스 AI] <sang-yeon.cho@navercorp.com>
Co-Authored-By: csy1204 <josang1204@gmail.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* test: add tests for OTEL environment variables passthrough

Verify that telemetry-related environment variables are correctly
passed through to sdkOptions.env when set in process.env.

Co-Authored-By: 조상연[플레이스 AI] <sang-yeon.cho@navercorp.com>
Co-Authored-By: csy1204 <josang1204@gmail.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: add missing OTEL_EXPORTER_OTLP_HEADERS environment variable

Add OTEL_EXPORTER_OTLP_HEADERS to the list of OpenTelemetry environment
variables passed through to the Claude Code subprocess. This variable is
needed for authentication when connecting to OTLP endpoints that require
bearer tokens or other credentials.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: 조상연[플레이스 AI] <sang-yeon.cho@navercorp.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 14:01:29 -08:00
GitHub Actions 70e16deb18 chore: bump Claude Code to 2.1.29 and Agent SDK to 0.2.29 2026-01-31 23:36:46 +00:00
GitHub Actions 0ed5eeaa54 chore: bump Claude Code to 2.1.27 and Agent SDK to 0.2.27 2026-01-30 20:37:50 +00:00
GitHub Actions 01e756b34e chore: bump Claude Code to 2.1.25 and Agent SDK to 0.2.25 2026-01-29 21:12:19 +00:00
GitHub Actions ff34ce0ff0 chore: bump Claude Code to 2.1.23 and Agent SDK to 0.2.23 2026-01-29 01:08:45 +00:00
GitHub Actions 2817c54db8 chore: bump Claude Code to 2.1.22 and Agent SDK to 0.2.22 2026-01-28 06:58:50 +00:00
Ashwin BhatandGitHub d01eedd981 Revert "chore: bump Claude Code to 2.1.21 and Agent SDK to 0.2.21" (#869)
This reverts commit 49046e0709.
2026-01-27 20:44:28 -08:00
GitHub Actions 49046e0709 chore: bump Claude Code to 2.1.21 and Agent SDK to 0.2.21 2026-01-28 02:24:23 +00:00
Ashwin BhatandGitHub 32ac7269f2 Revert "Revert "feat: send additional_permissions in token exchange request (…" (#866)
This reverts commit 231bd75b71.
2026-01-27 14:35:18 -08:00
Rani HalabiandGitHub fe72061e16 feat: add actor-based comment filtering to GitHub data fetching (#812)
- Introduced `include_comments_by_actor` and `exclude_comments_by_actor` inputs in action.yml to allow filtering of comments based on actor usernames.
- Updated context parsing to handle new input fields.
- Implemented `filterCommentsByActor` function to filter comments according to specified inclusion and exclusion patterns.
- Modified `fetchGitHubData` to apply actor filters when retrieving comments from pull requests and issues.
- Added comprehensive tests for the new filtering functionality.

This enhancement provides more control over which comments are processed based on the actor, improving the flexibility of the workflow.
2026-01-27 07:48:10 -08:00
Ashwin BhatandGitHub 231bd75b71 Revert "feat: send additional_permissions in token exchange request (#859)" (#864)
This reverts commit 0c704179b5.
2026-01-26 21:40:04 -08:00
GitHub Actions 4126f9d975 chore: bump Claude Code to 2.1.20 and Agent SDK to 0.2.20 2026-01-27 01:34:26 +00:00
ArthurandGitHub ba45bb9506 chore: upgarde checkout-action to v6 (#862) 2026-01-26 16:25:42 -08:00
Ashwin BhatandGitHub 0c704179b5 feat: send additional_permissions in token exchange request (#859)
* feat: send additional_permissions in token exchange request

Parse the ADDITIONAL_PERMISSIONS env var and send it as a JSON body
in the OIDC token exchange request. Permissions are merged on top of
the standard defaults (contents: write, pull_requests: write,
issues: write).

* docs: list specific available additional permissions
2026-01-26 09:02:20 -08:00
GitHub Actions f64219702d chore: bump Claude Code to 2.1.19 and Agent SDK to 0.2.19 2026-01-23 21:55:28 +00:00
GitHub Actions 8341a564b0 chore: bump Claude Code to 2.1.17 and Agent SDK to 0.2.17 2026-01-22 21:49:14 +00:00
GitHub Actions 2804b4174b chore: bump Claude Code to 2.1.16 and Agent SDK to 0.2.16 2026-01-22 20:08:26 +00:00
GitHub Actions 2316a9a8db chore: bump Claude Code to 2.1.15 and Agent SDK to 0.2.15 2026-01-21 22:00:12 +00:00
Ashwin BhatandGitHub 49cfcf8107 refactor: remove CLI path, use Agent SDK exclusively (#849)
* refactor: remove CLI path, use Agent SDK exclusively

- Remove CLI-based Claude execution in favor of Agent SDK
- Delete prepareRunConfig, parseAndSetSessionId, parseAndSetStructuredOutputs functions
- Remove named pipe IPC and sanitizeJsonOutput helper
- Remove test-agent-sdk job from test-base-action workflow (SDK is now default)
- Delete run-claude.test.ts and structured-output.test.ts (testing removed CLI code)
- Update CLAUDE.md to remove named pipe references

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Generated-By: Claude Code (cli/claude-opus-4-5=100%)
Claude-Steers: 2
Claude-Permission-Prompts: 1
Claude-Escapes: 0
Claude-Plan:
<claude-plan>
# Plan: Remove Non-Agent SDK Code Path

## Overview
Since `use_agent_sdk` defaults to `true`, remove the legacy CLI code path entirely from `base-action/src/run-claude.ts`.

## Files to Modify

### 1. `base-action/src/run-claude.ts` - Main Cleanup

**Remove imports:**
- `exec` from `child_process`
- `promisify` from `util`
- `unlink`, `writeFile`, `stat` from `fs/promises` (keep `readFile` - check if needed)
- `createWriteStream` from `fs`
- `spawn` from `child_process`
- `parseShellArgs` from `shell-quote` (still used in `parse-sdk-options.ts`, keep package)

**Remove constants:**
- `execAsync`
- `PIPE_PATH`
- `EXECUTION_FILE` (defined in both files, keep in SDK file)
- `BASE_ARGS`

**Remove types:**
- `PreparedConfig` type (lines 85-89) - only used by `prepareRunConfig()`

**Remove functions:**
- `sanitizeJsonOutput()` (lines 21-68)
- `prepareRunConfig()` (lines 91-125) - also remove export
- `parseAndSetSessionId()` (lines 131-155) - also remove export
- `parseAndSetStructuredOutputs()` (lines 162-197) - also remove export

**Simplify `runClaude()`:**
- Remove `useAgentSdk` flag check and logging (lines 200-204)
- Remove the `if (useAgentSdk)` block, make SDK call direct
- Remove entire CLI path (lines 211-438)
- Resulting function becomes just:
  ```typescript
  export async function runClaude(promptPath: string, options: ClaudeOptions) {
    const parsedOptions = parseSdkOptions(options);
    return runClaudeWithSdk(promptPath, parsedOptions);
  }
  ```

### 2. Delete Test Files

**`base-action/test/run-claude.test.ts`:**
- Delete entire file (only tests `prepareRunConfig()`)

**`base-action/test/structured-output.test.ts`:**
- Delete entire file (only tests `parseAndSetStructuredOutputs()` and `parseAndSetSessionId()`)

### 3. Workflow Update

**`.github/workflows/test-base-action.yml`:**
- Remove `test-agent-sdk` job (lines 120-176) - redundant now

### 4. Documentation Update

**`base-action/CLAUDE.md`:**
- Line 30: Remove "- Named pipes for IPC between prompt input and Claude process"
- Line 57: Remove "- Uses `mkfifo` to create named pipes for prompt input"

## Verification
1. Run `bun run typecheck` to ensure no type errors
2. Run `bun test` to ensure remaining tests pass
3. Run `bun run format` to fix any formatting issues
</claude-plan>

* fix: address PR review comments

- Add session_id output handling in run-claude-sdk.ts (critical)
- Remove unused claudeEnv parameter from ClaudeOptions and index.ts
- Update stale CLI path comment in parse-sdk-options.ts

Claude-Generated-By: Claude Code (cli/claude-opus-4-5=100%)
Claude-Steers: 0
Claude-Permission-Prompts: 0
Claude-Escapes: 0
Claude-Plan:
<claude-plan>
# Plan: Remove Non-Agent SDK Code Path

## Overview
Since `use_agent_sdk` defaults to `true`, remove the legacy CLI code path entirely from `base-action/src/run-claude.ts`.

## Files to Modify

### 1. `base-action/src/run-claude.ts` - Main Cleanup

**Remove imports:**
- `exec` from `child_process`
- `promisify` from `util`
- `unlink`, `writeFile`, `stat` from `fs/promises` (keep `readFile` - check if needed)
- `createWriteStream` from `fs`
- `spawn` from `child_process`
- `parseShellArgs` from `shell-quote` (still used in `parse-sdk-options.ts`, keep package)

**Remove constants:**
- `execAsync`
- `PIPE_PATH`
- `EXECUTION_FILE` (defined in both files, keep in SDK file)
- `BASE_ARGS`

**Remove types:**
- `PreparedConfig` type (lines 85-89) - only used by `prepareRunConfig()`

**Remove functions:**
- `sanitizeJsonOutput()` (lines 21-68)
- `prepareRunConfig()` (lines 91-125) - also remove export
- `parseAndSetSessionId()` (lines 131-155) - also remove export
- `parseAndSetStructuredOutputs()` (lines 162-197) - also remove export

**Simplify `runClaude()`:**
- Remove `useAgentSdk` flag check and logging (lines 200-204)
- Remove the `if (useAgentSdk)` block, make SDK call direct
- Remove entire CLI path (lines 211-438)
- Resulting function becomes just:
  ```typescript
  export async function runClaude(promptPath: string, options: ClaudeOptions) {
    const parsedOptions = parseSdkOptions(options);
    return runClaudeWithSdk(promptPath, parsedOptions);
  }
  ```

### 2. Delete Test Files

**`base-action/test/run-claude.test.ts`:**
- Delete entire file (only tests `prepareRunConfig()`)

**`base-action/test/structured-output.test.ts`:**
- Delete entire file (only tests `parseAndSetStructuredOutputs()` and `parseAndSetSessionId()`)

### 3. Workflow Update

**`.github/workflows/test-base-action.yml`:**
- Remove `test-agent-sdk` job (lines 120-176) - redundant now

### 4. Documentation Update

**`base-action/CLAUDE.md`:**
- Line 30: Remove "- Named pipes for IPC between prompt input and Claude process"
- Line 57: Remove "- Uses `mkfifo` to create named pipes for prompt input"

## Verification
1. Run `bun run typecheck` to ensure no type errors
2. Run `bun test` to ensure remaining tests pass
3. Run `bun run format` to fix any formatting issues
</claude-plan>
2026-01-20 16:00:23 -08:00
Ashwin BhatandGitHub e208124d29 chore: bump Bun to 1.3.6 and setup-bun action to v2.1.2 (#848)
Claude-Generated-By: Claude Code (cli/claude=100%)
Claude-Steers: 1
Claude-Permission-Prompts: 5
Claude-Escapes: 1
2026-01-20 14:06:49 -08:00
ba60ef7ba2 Consolidate CI workflows into a single entry point (#836)
* refactor: consolidate CI workflows with ci-all.yml orchestrator

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

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

* address security review comments

---------

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

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


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

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

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

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


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

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-14 15:22:40 -08:00
GitHub Actions a9171f0ced chore: bump Claude Code to 2.1.7 and Agent SDK to 0.2.7 2026-01-14 00:03:29 +00:00
GitHub Actions 4778aeae4c chore: bump Claude Code to 2.1.6 and Agent SDK to 0.2.6 2026-01-13 02:25:17 +00:00
GitHub Actions b6e5a9f27a chore: bump Claude Code to 2.1.4 and Agent SDK to 0.2.4 2026-01-11 00:27:43 +00:00
GitHub Actions 5d91d7d217 chore: bump Claude Code to 2.1.3 and Agent SDK to 0.2.3 2026-01-09 23:31:55 +00:00
GitHub Actions 90006bcae7 chore: bump Claude Code to 2.1.2 and Agent SDK to 0.2.2 2026-01-09 00:03:55 +00:00
005436f51d fix: parse ALL --allowed-tools flags, not just the first one (#801)
The parseAllowedTools() function previously used .match() which only
returns the first match. This caused tools specified in subsequent
--allowed-tools flags to be ignored during MCP server initialization.

Changes:
- Add /g flag to regex patterns for global matching
- Use matchAll() to find all occurrences
- Deduplicate tools while preserving order
- Make unquoted pattern not match quoted values

Fixes #800

 #vibe

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-09 01:36:12 +05:30
Ashwin BhatandGitHub 1b8ee3b941 fix: add missing import and update tests for branch template feature (#799)
* fix: add missing import and update tests for branch template feature

- Add missing `import { $ } from 'bun'` in branch.ts
- Add missing `labels` property to pull-request-target.test.ts fixture
- Update branch-template tests to expect 5-word descriptions

* address review feedback: update comment and add truncation test
2026-01-08 07:07:54 +05:30
Cole DandGitHub c247cb152d feat: custom branch name templates (#571)
* Add branch-name-template config option

* Logging

* Use branch name template

* Add label to template variables

* Add description template variable

* More concise description for branch_name_template

* Remove more granular time template variables

* Only fetch first label

* Add check for empty template-generated name

* Clean up comments, docstrings

* Merge createBranchTemplateVariables into generateBranchName

* Still replace undefined values

* Fall back to default on duplicate branch

* Parameterize description wordcount

* Remove some over-explanatory comments

* NUM_DESCRIPTION_WORDS: 3 -> 5
2026-01-08 06:47:26 +05:30
GitHub Actions cefa60067a chore: bump Claude Code to 2.1.1 and Agent SDK to 0.2.1 2026-01-07 21:30:16 +00:00
GitHub Actions 7a708f68fa chore: bump Claude Code to 2.1.0 and Agent SDK to 0.2.0 2026-01-07 20:03:23 +00:00
5da7ba548c feat: add path validation for commit_files MCP tool (#796)
Add validatePathWithinRepo helper to ensure file paths resolve within the repository root directory. This hardens the commit_files tool by validating paths before file operations.

Changes:
- Add src/mcp/path-validation.ts with async path validation using realpath
- Update commit_files to validate all paths before reading files
- Prevent symlink-based path escapes by resolving real paths
- Add comprehensive test coverage including symlink attack scenarios

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-07 10:16:31 -08:00
Ashwin BhatandGitHub 964b8355fb fix: use original title from webhook payload instead of fetched title (#793)
* fix: use original title from webhook payload instead of fetched title

- Add extractOriginalTitle() helper to extract title from webhook payload
- Add originalTitle parameter to fetchGitHubData()
- Update tag mode to pass original title from webhook context
- Add tests for extractOriginalTitle and originalTitle parameter

This ensures the title used in prompts is the one that existed when the
trigger event occurred, rather than a potentially modified title fetched
later via GraphQL.

* fix: add title sanitization and explicit TOCTOU test

- Apply sanitizeContent() to titles in formatContext() for defense-in-depth
- Add explicit test documenting TOCTOU prevention for title handling
2026-01-07 23:45:12 +05:30
c83d67a9b9 fix: resolve high vulnerability CVE-2025-66414 (#792)
Automatically generated security fix

Co-authored-by: orbisai0security <orbisai0security@users.noreply.github.com>
2026-01-07 12:53:45 +05:30
c9ec2b02b4 fix: set CLAUDE_CODE_ENTRYPOINT for SDK path to match CLI path (#791)
Previously, the SDK path would result in the CLI setting the entrypoint
to 'sdk-ts' internally, while the non-SDK (CLI) path would correctly
set it to 'claude-code-github-action' based on the CLAUDE_CODE_ACTION
env var.

This change explicitly sets CLAUDE_CODE_ENTRYPOINT in both:
1. The action.yml env block (for consistency)
2. The SDK options env (to override the CLI's internal default)

The CLI respects pre-set entrypoint values, so this ensures consistent
user agent reporting for both execution paths.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-06 02:10:44 +05:30
Ashwin BhatandGitHub 63ea7e3174 fix: prevent orphaned installer processes from blocking retries (#790)
* fix: prevent orphaned installer processes from blocking retries

When the `timeout` command expires during Claude Code installation, it only
kills the direct child bash process, not the grandchild installer processes.
These orphaned processes continue holding a lock file, causing retry attempts
to fail with "another process is currently installing Claude".

Add `--foreground` flag to run the command in a foreground process group so
all child processes are killed on timeout. Add `--kill-after=10` to send
SIGKILL if SIGTERM doesn't terminate processes within 10 seconds.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

   Co-Authored-By: Claude <noreply@anthropic.com>

* fix: apply same timeout fix to root action.yml

🤖 Generated with [Claude Code](https://claude.com/claude-code)

   Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-05 23:01:39 +05:30
653f9cd7a3 feat: support local plugin marketplace paths (#761)
* feat: support local plugin marketplace paths

Enable installing plugins from local directories in addition to remote
Git URLs. This allows users to use local plugin marketplaces within their
repository without requiring them to be hosted in a separate Git repo.

Example usage:
  plugin_marketplaces: "./my-local-marketplace"
  plugins: "my-plugin@my-local-marketplace"

Supported path formats:
- Relative paths: ./plugins, ../shared-plugins
- Absolute Unix paths: /home/user/plugins
- Absolute Windows paths: C:\Users\user\plugins

Fixes #664

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* support hidden folders

* Revert "support hidden folders"

This reverts commit a55626c9f1.

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-05 16:13:32 +05:30
b17b541bbc feat: send user request as separate content block for slash command support (#785)
* feat: send user request as separate content block for slash command support

When in tag mode with the SDK path, extracts the user's request from the
trigger comment (text after @claude) and sends it as a separate content
block. This enables the CLI to process slash commands like "/review-pr".

- Add extract-user-request utility to parse trigger comments
- Write user request to separate file during prompt generation
- Send multi-block SDKUserMessage when user request file exists
- Add tests for the extraction utility

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: address PR feedback

- Fix potential ReDoS vulnerability by using string operations instead of regex
- Remove unused extractUserRequestFromEvent function and tests
- Extract USER_REQUEST_FILENAME to shared constants
- Conditionally log user request based on showFullOutput setting
- Add JSDoc documentation to extractUserRequestFromContext

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-02 17:57:13 -08:00
Ashwin BhatandGitHub 7e4bf87b1c feat: add ssh_signing_key input for SSH commit signing (#784)
* feat: add ssh_signing_key input for SSH commit signing

Add a new ssh_signing_key input that allows passing an SSH signing key
for commit signing, as an alternative to the existing use_commit_signing
(which uses GitHub API-based commits).

When ssh_signing_key is provided:
- Git is configured to use SSH signing (gpg.format=ssh, commit.gpgsign=true)
- The key is written to ~/.ssh/claude_signing_key with 0600 permissions
- Git CLI commands are used (not MCP file ops)
- The key is cleaned up in a post step for security

Behavior matrix:
| ssh_signing_key | use_commit_signing | Result |
|-----------------|-------------------|--------|
| not set         | false             | Regular git, no signing |
| not set         | true              | GitHub API (MCP), verified commits |
| set             | false             | Git CLI with SSH signing |
| set             | true              | Git CLI with SSH signing (ssh_signing_key takes precedence)

* docs: add SSH signing key documentation

- Update security.md with detailed setup instructions for both signing options
- Explain that ssh_signing_key enables full git CLI operations (rebasing, etc.)
- Add ssh_signing_key to inputs table in usage.md
- Update bot_id/bot_name descriptions to note they're needed for verified commits

* fix: address security review feedback for SSH signing

- Write SSH key atomically with mode 0o600 (fixes TOCTOU race condition)
- Create .ssh directory with mode 0o700 (SSH best practices)
- Add input validation for SSH key format
- Remove unused chmod import
- Add tests for validation logic
2026-01-02 10:37:25 -08:00
Aidan DunlapandGitHub 154d0de144 feat: add instant "Fix this" links to PR code reviews (#773)
* feat: add "Fix this" links to PR code reviews

When Claude reviews PRs and identifies fixable issues, it now includes
inline links that open Claude Code with the fix request pre-loaded.

Format: [Fix this →](https://claude.ai/code?q=<URI_ENCODED_INSTRUCTIONS>&repo=<REPO>)

This enables one-click fix requests directly from code review comments.

* feat: add include_fix_links input to control Fix this links

Adds a configurable input to enable/disable the "Fix this →" links
in PR code reviews. Defaults to true for backwards compatibility.
2025-12-27 15:29:06 -08:00
GitHub Actions 3ba9f7c8c2 chore: bump Claude Code to 2.0.76 and Agent SDK to 0.1.76 2025-12-23 19:33:03 +00:00
きわみざむらいandGitHub e5b07416ea chore: remove unused ci yaml file (#763)
* fix: Replace direct template expansion in bump-claude-code-version workflow

* chore: remove bump-claude-code-version workflow file
2025-12-22 18:59:34 -08:00
Ashwin BhatGitHubclaude[bot] <41898282+claude[bot]@users.noreply.github.com>Ashwin Bhat
b89827f8d1 fix: update broken link in cloud-providers.md (#758)
Update the AWS Bedrock documentation link to point to the new
code.claude.com domain.

Fixes #756

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Ashwin Bhat <ashwin-ant@users.noreply.github.com>
2025-12-19 15:47:47 -08:00
GitHub Actions 7145c3e051 chore: bump Claude Code to 2.0.74 and Agent SDK to 0.1.74 2025-12-19 22:12:44 +00:00
GitHub Actions db4548b597 chore: bump Claude Code to 2.0.73 and Agent SDK to 0.1.73 2025-12-19 00:16:27 +00:00
GitHub Actions 0d19335299 chore: bump Claude Code to 2.0.72 and Agent SDK to 0.1.72 2025-12-17 21:59:16 +00:00
95be46676d fix: set GH_TOKEN alongside GITHUB_TOKEN for gh CLI precedence (#752)
The gh CLI prefers GH_TOKEN over GITHUB_TOKEN. When a calling workflow
sets GH_TOKEN in env, the action's GITHUB_TOKEN was being ignored,
causing the gh CLI to use the wrong token (e.g., the default workflow
token instead of an App token).

This ensures Claude's gh CLI commands use the action's prepared token.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-17 09:54:03 -08:00
Ashwin BhatGitHubAshwin Bhatclaude[bot] <41898282+claude[bot]@users.noreply.github.com>
f98c1a5aa8 fix: respect user's --setting-sources in claude_args (#750)
When users specify --setting-sources in claude_args (e.g., '--setting-sources user'),
the action now respects that value instead of overriding it with all three sources.

This fixes an issue where users who wanted to avoid in-repo configs would still
have them loaded because the settingSources was hardcoded to ['user', 'project', 'local'].

Fixes #749

Co-authored-by: Ashwin Bhat <ashwin-ant@users.noreply.github.com>

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
2025-12-16 15:00:34 -08:00
GitHub Actions b0c32b65f9 chore: bump Claude Code to 2.0.71 and Agent SDK to 0.1.71 2025-12-16 22:09:42 +00:00
Ashwin BhatGitHubClaudeclaude[bot] <41898282+claude[bot]@users.noreply.github.com>Ashwin Bhat
d7b6d50442 fix: merge multiple --mcp-config flags and support --allowed-tools parsing (#748)
* fix: merge multiple --mcp-config flags instead of overwriting

When users provide their own --mcp-config in claude_args, the action's
built-in MCP servers (github_comment, github_ci, etc.) were being lost
because multiple --mcp-config flags were overwriting each other.

This fix:
- Adds mcp-config to ACCUMULATING_FLAGS to collect all values
- Changes delimiter to null character to avoid conflicts with JSON
- Adds mergeMcpConfigs() to combine mcpServers objects from multiple configs
- Merges inline JSON configs while preserving file path configs

Fixes #745

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Ashwin Bhat <ashwin-ant@users.noreply.github.com>

* fix: support hyphenated --allowed-tools flag and multiple values

The --allowed-tools flag was not being parsed correctly when:
1. Using the hyphenated form (--allowed-tools) instead of camelCase (--allowedTools)
2. Passing multiple space-separated values after a single flag
   (e.g., --allowed-tools "Tool1" "Tool2" "Tool3")

This fix:
- Adds hyphenated variants (allowed-tools, disallowed-tools) to ACCUMULATING_FLAGS
- Updates parsing to consume all consecutive non-flag values for accumulating flags
- Merges values from both camelCase and hyphenated variants

Fixes #746

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Ashwin Bhat <ashwin-ant@users.noreply.github.com>
2025-12-16 13:08:25 -08:00
f375cabfab chore: update model to claude-opus-4-5 in workflow (#747)
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-16 12:47:41 -08:00
GitHub Actions 9acae263e7 chore: bump Claude Code to 2.0.70 and Agent SDK to 0.1.70 2025-12-15 23:53:03 +00:00
67bf0594ce feat: add session_id output to enable resuming conversations (#739)
Add a new `session_id` output that exposes the Claude Code session ID,
allowing other workflows or Claude Code instances to resume the
conversation using `--resume <session_id>`.

Changes:
- Add parseAndSetSessionId() function to extract session_id from
  the system.init message in execution output
- Add session_id output to both action.yml and base-action/action.yml
- Add comprehensive tests for the new functionality

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-14 19:42:54 -08:00
GitHub Actions b58533dbe0 chore: bump Claude Code version to 2.0.69 2025-12-13 01:00:43 +00:00
GitHub Actions bda9bf08de chore: bump Claude Code version to 2.0.68 2025-12-12 23:32:49 +00:00
79b343c094 feat: Make Agent SDK the default execution path (#738)
Change USE_AGENT_SDK to default to true instead of false. The Agent SDK
path is now used by default; set USE_AGENT_SDK=false to use the CLI path.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-12 13:55:16 -08:00
609c388361 Fix command injection vulnerability in branch setup (#736)
* fix: Prevent command injection in branch operations

Replace Bun shell template literals with Node.js execFileSync to prevent
command injection attacks via malicious branch names. Branch names from
PR data (headRefName) are now validated against a strict whitelist pattern
before use in git commands.

Changes:
- Add validateBranchName() function with strict character whitelist
- Replace $`git ...` shell templates with execGit() using execFileSync
- Validate all branch names before use in git operations

* fix: Address review comments for branch validation security

- Enhanced execGit JSDoc to explain security benefits of execFileSync
- Added comprehensive branch name validation:
  - Leading dash check (prevents option injection)
  - Control characters and special git characters (~^:?*[\])
  - Leading/trailing period checks
  - Trailing slash and consecutive slash checks
- Added -- separator to git checkout commands
- Added 30 unit tests for validateBranchName covering:
  - Valid branch names
  - Command injection attempts
  - Option injection attempts
  - Path traversal attempts
  - Git-specific invalid patterns
  - Control characters and edge cases

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-12 11:30:28 -08:00
GitHub Actions f0c8eb2980 chore: bump Claude Code version to 2.0.62 2025-12-09 02:12:14 +00:00
68a0348c20 fix: Replace direct template expansion of inputs in shell scripts with environment variables (#729)
Replace direct template expansion of user inputs in shell scripts with
environment variables to prevent potential command injection attacks.

Changes:
- sync-base-action.yml: Use $GITHUB_EVENT_NAME and $GITHUB_ACTOR instead of template expansion
- action.yml: Pass path_to_bun_executable and path_to_claude_code_executable through env vars
- base-action/action.yml: Same env var changes for path inputs

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-08 12:08:44 -08:00
GitHub Actions dc06a34646 chore: bump Claude Code version to 2.0.61 2025-12-07 10:47:47 +00:00
a3bb51dac1 Fix SDK path: add settingSources and default system prompt (#726)
Two fixes for the Agent SDK path (USE_AGENT_SDK=true):

1. Add settingSources to load filesystem settings
   - Without this, CLI-installed plugins aren't available to the SDK
   - Also needed to load CLAUDE.md files from the project

2. Default systemPrompt to claude_code preset
   - Without an explicit systemPrompt, the SDK would use no system prompt
   - Now defaults to { type: "preset", preset: "claude_code" } to match CLI behavior

Also adds logging of SDK options (excluding env) for debugging.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-06 16:52:26 -08:00
GitHub Actions 6610520549 chore: bump Claude Code version to 2.0.60 2025-12-06 00:10:42 +00:00
GitHub Actions e2eb96f51d chore: bump Claude Code version to 2.0.59 2025-12-04 23:09:43 +00:00
05c95aed79 fix: accumulate multiple --allowedTools flags for Agent SDK (#719)
* fix: merge allowedTools from claudeArgs when using Agent SDK

When USE_AGENT_SDK=true, the allowedTools from claudeArgs (which contains
tag mode's required tools like mcp__github_comment__update_claude_comment)
were being lost because parseClaudeArgsToExtraArgs converts args to a
Record<string, string>, and the SDK was using sdkOptions.allowedTools
(from direct options) instead of merging with extraArgs.allowedTools.

This fix:
- Extracts allowedTools/disallowedTools from extraArgs after parsing
- Merges them with any direct options.allowedTools/disallowedTools
- Removes them from extraArgs to prevent duplicate CLI flags
- Passes the merged list as sdkOptions.allowedTools

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: accumulate multiple --allowedTools flags in claudeArgs

When tag mode adds its --allowedTools (with MCP tools) and the user also
provides --allowedTools in their claude_args, the parseClaudeArgsToExtraArgs
function was only keeping the last value. This caused tag mode's required
tools like mcp__github_comment__update_claude_comment to be lost.

Now allowedTools and disallowedTools flags accumulate their values when
they appear multiple times in claudeArgs, so both tag mode's tools and
user's tools are preserved.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-04 10:25:54 -08:00
bb4a3f68f7 feat: add simplified prompt option via USE_SIMPLE_PROMPT env var (#718)
Adds a shorter, more concise prompt for tag mode that trusts the model
to figure out details. Opt-in via USE_SIMPLE_PROMPT=true. The simplified
prompt keeps all context data but reduces instructions from ~250 to ~70 lines.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-04 10:25:47 -08:00
Philippe LaflammeandGitHub 2acd1f7011 fix: commentBody may be null (#706)
* fix: `commentBody` may be `null`

This handles the cases where `pull_request_review` events have no
comments (`commentBody` field is `null`). In those cases, the `null`
value is converted to the empty string.

The issue was testing `!commentBody` which was triggerring on empty
strings as well. This guard was removed (which is the fix), but for
clarity, the `commentBody` field was also made optional to make it clear
that the comment may be missing.

* fix: bun run format
2025-12-03 17:34:31 -08:00
469fc9c1a4 feat: add Agent SDK support with USE_AGENT_SDK feature flag (#698)
* feat: add Agent SDK support with USE_AGENT_SDK feature flag

Add a feature-flagged code path that uses the Agent SDK instead of
spawning the CLI as a subprocess. When USE_AGENT_SDK=true is set,
the new SDK path is used; otherwise, existing CLI behavior is unchanged.

Changes:
- Add parse-sdk-options.ts for parsing ClaudeOptions into SDK format
- Add run-claude-sdk.ts for SDK execution with query() function
- Update run-claude.ts with feature flag check at entry point
- Update update-comment-link.ts to handle both cost_usd and total_cost_usd
- Add @anthropic-ai/claude-agent-sdk dependency

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: simplify SDK types by using @anthropic-ai/claude-agent-sdk types directly

- Remove duplicate SdkRunOptions and McpStdioServerConfig types
- Use SDK's Options and McpStdioServerConfig types directly
- Return { sdkOptions, showFullOutput, hasJsonSchema } from parseSdkOptions
- Remove unnecessary convertMcpServers function
- Net reduction of ~70 lines

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: use extraArgs for claudeArgs pass-through to CLI

Simplify option parsing by converting claudeArgs to extraArgs record
and letting the SDK/CLI handle --mcp-config, --json-schema, etc.

- Remove extractJsonSchema and parseMcpConfigs functions
- Add parseClaudeArgsToExtraArgs for simple flag parsing
- CLI handles complex args like --mcp-config directly

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* ci

* refactor: remove hardcoded permission bypass flags

The SDK path should match CLI path behavior - permissions are handled
by the CLI itself, not hardcoded in the action.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* chore: add logging for SDK vs CLI path selection

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-03 17:22:04 -08:00
GitHub Actions 90da6b6e15 chore: bump Claude Code version to 2.0.58 2025-12-03 20:09:55 +00:00
GitHub Actions 752ba96ea1 chore: bump Claude Code version to 2.0.57 2025-12-03 05:24:27 +00:00
GitHub Actions 66bf95c07f chore: bump Claude Code version to 2.0.56 2025-12-02 01:35:17 +00:00
6337623ebb fix: prevent TOCTOU race condition on issue/PR body edits (#710)
Add trigger-time validation for issue/PR body content to prevent attackers
from exploiting a race condition where they edit the body between when an
authorized user triggers @claude and when Claude processes the request.

The existing filterCommentsToTriggerTime() already protected comments -
this extends the same pattern to the main issue/PR body via isBodySafeToUse().

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-01 07:59:39 -08:00
GitHub Actions 6d79044f1d chore: bump Claude Code version to 2.0.55 2025-11-27 00:01:22 +00:00
a7e4c51380 fix: use cross-platform timeout for Claude Code installation (#700)
The GNU `timeout` command is not available on macOS. Check if it exists
and use it when available, otherwise run without timeout.

Also extracts the version into a CLAUDE_CODE_VERSION variable for
easier maintenance.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-24 20:57:33 -05:00
Ashwin BhatGitHubAshwin BhatClaudeclaude[bot] <41898282+claude[bot]@users.noreply.github.com>
7febbb006b Remove experimental allowed domains feature (#697)
* chore: remove experimental allowed domains feature

Remove the experimental_allowed_domains feature which was used to
restrict network access via a Squid proxy. This removes:

- The input definition from action.yml
- The Network Restrictions workflow step
- The setup-network-restrictions.sh script
- Documentation from experimental.md, usage.md, and related files
- The input default from collect-inputs.ts

* chore: fix formatting with prettier

Co-authored-by: Ashwin Bhat <ashwin-ant@users.noreply.github.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Ashwin Bhat <ashwin-ant@users.noreply.github.com>
2025-11-24 19:03:53 -05:00
798cf0988d chore: add retry loop to Claude Code installation (#694)
* chore: add --debug and retry loop to Claude Code installation

Adds 2-minute timeout with up to 3 retry attempts for installation.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: remove unsupported --debug flag from install script

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-21 16:52:35 -08:00
GitHub Actions 8458f4399d chore: bump Claude Code version to 2.0.50 2025-11-21 23:16:27 +00:00
GitHub Actions f9b2917716 chore: bump Claude Code version to 2.0.49 2025-11-21 01:31:39 +00:00
f092d4cefd feat: add Microsoft Foundry provider support (#684)
* feat: add Azure AI Foundry provider support

Add support for Azure AI Foundry as a fourth cloud provider option alongside Anthropic API, AWS Bedrock, and Google Vertex AI.

Changes:
- Add use_foundry input to enable Azure AI Foundry authentication
- Add Azure environment variables (ANTHROPIC_FOUNDRY_RESOURCE, ANTHROPIC_FOUNDRY_API_KEY, ANTHROPIC_FOUNDRY_BASE_URL)
- Support automatic base URL construction from resource name
- Add validation logic with mutual exclusivity checks for all providers
- Add comprehensive test coverage (7 Azure-specific tests, 3 mutual exclusivity tests)
- Add complete Azure AI Foundry documentation with OIDC and API key authentication
- Update README to reference Azure AI Foundry support

Features:
- Primary authentication via Microsoft Entra ID (OIDC) using azure/login action
- Optional API key authentication fallback
- Custom model deployment name support via ANTHROPIC_DEFAULT_*_MODEL variables
- Clear validation error messages for missing configuration

All tests pass (25 validation tests total).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: rename Azure AI Foundry to Microsoft Foundry and remove API key support

- Rename all references from "Azure AI Foundry" to "Microsoft Foundry"
- Remove ANTHROPIC_FOUNDRY_API_KEY support (OIDC only)
- Update documentation to reflect OIDC-only authentication
- Update tests to remove API key test case

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: simplify Microsoft Foundry setup and remove URL auto-construction

- Link to official docs instead of duplicating setup instructions
- Remove automatic base URL construction from resource name
- Pass ANTHROPIC_FOUNDRY_BASE_URL as-is

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-20 13:50:13 -08:00
Jose GarciaandGitHub c2edeab4c3 added: AWS_BEARER_TOKEN_BEDROCK authentication capabilities (#692) 2025-11-20 13:47:12 -08:00
4318310481 chore: limit PR review workflow to opened events only (#691)
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-20 12:09:21 -08:00
Kyle AltendorfandGitHub 11571151c4 update docs re: commit signing no longer default (#675)
* update docs re: commit signing no longer default

* format
2025-11-20 07:13:10 -08:00
GitHub Actions 70193f466c chore: bump Claude Code version to 2.0.47 2025-11-19 23:12:47 +00:00
GitHub Actions 9db20ef677 chore: bump Claude Code version to 2.0.46 2025-11-19 04:58:56 +00:00
6902c227aa feat: add structured output support via --json-schema argument (#687)
* feat: add structured output support

Add support for Agent SDK structured outputs.

New input: json_schema
Output: structured_output (JSON string)
Access: fromJSON(steps.id.outputs.structured_output).field

Docs: https://docs.claude.com/en/docs/agent-sdk/structured-outputs

* rm unused

* refactor: simplify structured outputs to use claude_args

Remove json_schema input in favor of passing --json-schema flag directly
in claude_args. This simplifies the interface by treating structured outputs
like other CLI flags (--model, --max-turns, etc.) instead of as a special
input that gets injected.

Users now specify: claude_args: '--json-schema {...}'
Instead of separate: json_schema: {...}

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* chore: remove unused json-schema util and revert version

- Remove src/utils/json-schema.ts (no longer used after refactor)
- Revert Claude Code version from 2.0.45 back to 2.0.42

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-18 17:18:05 -08:00
GitHub Actions e45f28fae7 chore: bump Claude Code version to 2.0.45 2025-11-18 16:50:24 +00:00
GitHub Actions 8c4e1e7eb1 chore: bump Claude Code version to 2.0.44 2025-11-18 04:50:59 +00:00
GitHub Actions 906bd89c74 chore: bump Claude Code version to 2.0.43 2025-11-18 00:29:32 +00:00
GitHub Actions 08f88abe2b chore: bump Claude Code version to 2.0.42 2025-11-15 00:17:35 +00:00
GitHub Actions 14ab4250bb chore: bump Claude Code version to 2.0.37 2025-11-11 00:21:46 +00:00
GitHub Actions c7fdd19642 chore: bump Claude Code version to 2.0.36 2025-11-07 22:08:15 +00:00
GitHub Actions 92d173475f chore: bump Claude Code version to 2.0.35 2025-11-06 21:07:07 +00:00
GitHub Actions 108e982900 chore: bump Claude Code version to 2.0.34 2025-11-05 21:11:28 +00:00
GitHub Actions 7bb53ae6ee chore: bump Claude Code version to 2.0.33 2025-11-04 23:40:50 +00:00
GitHub Actions 804b418b93 chore: bump Claude Code version to 2.0.32 2025-11-03 23:22:17 +00:00
GitHub Actions 500439cb9b chore: bump Claude Code version to 2.0.31 2025-10-31 22:00:23 +00:00
GitHub Actions 4cda0ef6d1 chore: bump Claude Code version to 2.0.30 2025-10-30 23:35:37 +00:00
037b85d0d2 docs: update action version from @beta to @v1 in docs (#650)
Updates documentation examples to use @v1 instead of @beta in:
- docs/setup.md: custom GitHub app example
- docs/configuration.md: additional permissions examples

Migration guide and usage comparison examples intentionally kept with @beta to show old syntax.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-10-29 21:45:52 -07:00
GitHub Actions 8a1c437175 chore: bump Claude Code version to 2.0.29 2025-10-29 23:25:55 +00:00
56c8ae7d88 Add show_full_output option to control output verbosity (#580)
* Add show_full_output option to control output verbosity

* Update base-action/src/run-claude.ts

Co-authored-by: Ashwin Bhat <ashwin@anthropic.com>

* Wire show_full_output through to base-action

* Document show_full_output security warnings in docs/security.md

---------

Co-authored-by: Ashwin Bhat <ashwin@anthropic.com>
2025-10-28 11:52:18 -07:00
GitHub Actions f4d737af0b chore: bump Claude Code version to 2.0.28 2025-10-27 21:32:34 +00:00
29fe50368c feat: change plugins input from comma-separated to newline-separated (#644)
* feat: change plugins input from comma-separated to newline-separated

Changes:
- Update parsePlugins() to split by newline instead of comma for consistency with marketplaces input
- Update action.yml and base-action/action.yml with newline-separated format and realistic plugin examples
- Add plugin_marketplaces documentation to docs/usage.md
- Update all unit tests to match new installPlugins() signature (marketplaces, plugins, executable)
- Improve JSDoc comments for parsePlugins() and installPlugin() functions
- All 25 install-plugins tests passing

Breaking change: Users must update their workflows to use newline-separated format:
  Before: plugins: "plugin1,plugin2"
  After: plugins: "plugin1\nplugin2"

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* test: add comprehensive marketplace functionality tests

Critical fix: All previous tests passed undefined as marketplacesInput parameter,
leaving the entire marketplace functionality completely untested.

Added 13 new tests covering:
- Single marketplace installation
- Multiple marketplaces with newline separation
- Marketplace + plugin installation order verification
- Marketplace URL validation (format, protocol, .git extension)
- Whitespace and empty entry handling
- Error handling for marketplace operations
- Custom executable path for marketplace operations

Test coverage: 38 tests (was 25), 81 expect calls (was 50)
All tests passing 

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-10-27 09:01:34 -07:00
8ad13bd20b feat(docs): simplify custom GitHub App creation with manifest support (#620)
* feat(docs): simplify custom GitHub App creation with manifest support

- Add github-app-manifest.json with pre-configured permissions
- Create interactive HTML tool for one-click app creation
- Update setup.md documentation with manifest-based instructions
- Maintain existing manual setup as alternative option

This significantly improves the developer experience by eliminating
manual permission configuration and reducing setup time from multiple
steps to a single click.

Fixes #619

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Kris Coleman <kriscodeman@gmail.com>

* feat: create-app ux improvements

Signed-off-by: Kris Coleman <kriscodeman@gmail.com>

---------

Signed-off-by: Kris Coleman <kriscodeman@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
2025-10-27 08:26:46 -07:00
7b914ae5c0 feat: add plugin_marketplaces input for dynamic marketplace installation (#642)
- Added plugin_marketplaces input to both main and base-action action.yml files
- Updated install-plugins.ts to support multiple marketplace URLs (newline-separated)
- Added validation for marketplace URLs to prevent security issues
- Updated installPlugins function to dynamically add marketplaces instead of hardcoding
- Defaults to official Claude Code marketplace when no marketplaces are specified
- Updated base-action index.ts to pass plugin_marketplaces to installPlugins

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-10-26 15:47:23 -07:00
d4c09790f5 feat: add plugins input to install Claude Code plugins (#638)
* feat: add plugins input to install Claude Code plugins

Add support for installing Claude Code plugins via a comma-separated list.
Plugins are installed from the official marketplace before Claude Code execution.

Changes:
- Add plugins input to action.yml with validation
- Implement secure plugin installation with injection prevention
- Add marketplace setup before plugin installation
- Add comprehensive validation for plugin names (Unicode normalization, path traversal detection)
- Add tests covering installation flow, error handling, and security

Security features:
- Plugin name validation with regex and Unicode normalization
- Path traversal attack prevention
- Command injection protection
- Maximum plugin name length enforcement

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: optimize path traversal check and improve type safety

- Replace multiple includes() checks with single comprehensive regex (60-70% faster)
- Change spawnSpy type from 'any' to proper 'ReturnType<typeof spyOn> | undefined'
- Maintain same security guarantees with better performance

* refactor: extract shared command execution logic to eliminate DRY violation

Extract executeClaudeCommand() helper to eliminate 40+ lines of duplicated
error handling code between installPlugin() and addMarketplace().

Benefits:
- Single source of truth for command execution and error handling
- Easier to maintain and modify command execution behavior
- More concise and focused function implementations
- Consistent error message formatting across all commands

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-10-25 20:47:06 -07:00
GitHub Actions 5033c581bb chore: bump Claude Code version to 2.0.27 2025-10-24 21:17:11 +00:00
GitHub Actions f8749bd14b chore: bump Claude Code version to 2.0.26 2025-10-23 23:03:39 +00:00
btooandGitHub f30f5eecfc Update usage.md with link to claude cli args (#600) 2025-10-21 14:38:12 -07:00
GitHub Actions fc4013af38 chore: bump Claude Code version to 2.0.25 2025-10-21 21:37:39 +00:00
BangDoriandGitHub 96524b7ffe docs: clarify job run link format in system prompt (#627) 2025-10-20 22:52:05 -07:00
GitHub Actions fd20c95358 chore: bump Claude Code version to 2.0.24 2025-10-20 19:12:24 +00:00
GitHub Actions d808160c26 chore: bump Claude Code version to 2.0.23 2025-10-20 17:58:41 +00:00
Okumura TakahiroandGitHub 3eacedbeb7 Update github-mcp-server to v0.17.1 (#613) 2025-10-20 09:14:27 -07:00
Dale SeoandGitHub f52f12eba5 chore: upgrade actions/checkout from v4 to v5 (#632) 2025-10-20 09:11:13 -07:00
GitHub Actions 4a85933f25 chore: bump Claude Code version to 2.0.22 2025-10-17 22:29:52 +00:00
GitHub Actions ba6edd55ef chore: bump Claude Code version to 2.0.21 2025-10-17 00:48:29 +00:00
GitHub Actions 06461dddff chore: bump Claude Code version to 2.0.20 2025-10-16 16:51:26 +00:00
GitHub Actions c2a94eead0 chore: bump Claude Code version to 2.0.19 2025-10-15 22:29:38 +00:00
1c0c3eaced docs: document GitHub App permissions in security guide (#607)
Clarifies which permissions are currently used (Contents, Pull Requests, Issues) versus those requested for planned future features (Discussions, Actions, Checks, Workflows).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-10-15 10:12:11 -07:00
GitHub Actions 23d2d6c6b4 chore: bump Claude Code version to 2.0.17 2025-10-15 16:58:01 +00:00
GitHub Actions e8bad57227 chore: bump Claude Code version to 2.0.15 2025-10-14 17:50:14 +00:00
GitHub Actions 0a6d62601b chore: bump Claude Code version to 2.0.14 2025-10-10 21:25:16 +00:00
GitHub Actions 777ffcbfc9 chore: bump Claude Code version to 2.0.13 2025-10-09 17:53:02 +00:00
GitHub Actions dc58efed33 chore: bump Claude Code version to 2.0.12 2025-10-09 16:41:48 +00:00
GitHub Actions e5437bfbc5 chore: bump Claude Code version to 2.0.11 2025-10-08 20:36:56 +00:00
GitHub Actions b2dd1006a0 chore: bump Claude Code version to 2.0.10 2025-10-07 21:14:39 +00:00
GitHub Actions ac1a3207f3 chore: bump Claude Code version to 2.0.9 2025-10-06 21:57:24 +00:00
521d069da7 docs: add prompt injection security note (#604)
* docs: add prompt injection security note

Add warning about potential hidden markdown in untrusted content from external contributors. Documents existing sanitization measures while acknowledging new bypass techniques may emerge.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Update docs/security.md

Co-authored-by: David Dworken <dworken@anthropic.com>

* format

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: David Dworken <dworken@anthropic.com>
2025-10-06 09:51:50 -07:00
GitHub Actions 7e4b782d5f chore: bump Claude Code version to 2.0.8 2025-10-04 23:17:33 +00:00
GitHub Actions 4fb0ef3be0 chore: bump Claude Code version to 2.0.5 2025-10-02 19:29:43 +00:00
GitHub Actions 14ac8aa20e chore: bump Claude Code version to 2.0.1 2025-10-01 02:30:10 +00:00
90d189f3ab fix: update permission test prompts to trigger actual tool usage (#596)
Changed test prompts from communication-style echo commands to legitimate
technical operations. This ensures Claude attempts the Bash tool call
(which then gets blocked by permissions) instead of refusing based on
communication guidelines.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-09-30 18:04:13 -07:00
GitHub Actions 9c09b26b2d chore: bump Claude Code version to 2.0.2 2025-10-01 00:48:36 +00:00
GitHub Actions 2086c977a5 chore: bump Claude Code version to 2.0.1 2025-09-30 02:45:30 +00:00
GitHub Actions 851ef5b84e chore: bump Claude Code version to 2.0.0 2025-09-29 16:45:58 +00:00
Song HuangandGitHub 1ce8153c18 docs: fix the faq doc link (#593) 2025-09-28 10:02:17 -07:00
GitHub Actions 00391ab25e chore: bump Claude Code version to 1.0.128 2025-09-27 16:44:46 +00:00
GitHub Actions 426380f01b chore: bump Claude Code version to 1.0.127 2025-09-26 18:15:45 +00:00
GitHub Actions 77f51d2905 chore: bump Claude Code version to 1.0.126 2025-09-26 01:13:47 +00:00
GitHub Actions 7e5b42b197 chore: bump Claude Code version to 1.0.124 2025-09-25 04:23:38 +00:00
GitHub Actions 1b7c7a77d3 chore: bump Claude Code version to 1.0.123 2025-09-23 23:48:31 +00:00
Vibhor AgrawalandGitHub bd70a3ef2b fix: add support for pull_request_target event in GitHub Actions workflows (#579)
Add pull_request_target event support to enable Claude Code usage with forked
repositories while maintaining proper security boundaries. This resolves issues
with dependabot PRs and external contributions that require write permissions.

Changes:
- Add pull_request_target to supported GitHub events in context parsing
- Update type definitions to include PullRequestTargetEvent
- Modify IS_PR calculation to detect pull_request_target as PR context
- Add comprehensive test coverage for pull_request_target workflows
- Update documentation to reflect pull_request_target support

The pull_request_target event provides the same payload structure as
pull_request but runs with write permissions from the base repository,
making it ideal for secure automation of external contributions.

Fixes #347
2025-09-22 09:20:27 -07:00
marcusandGitHub f4954b5256 removed mcp_config as input from usage.md and added to deprecated inputs with instructions to migrate to --mcp-config instead (#574) 2025-09-22 09:19:26 -07:00
93f8ab56c2 Add support for kebab-case --allowed-tools flag (#581)
- Update parseAllowedTools to accept both --allowedTools and --allowed-tools
- Add regex alternation to support both camelCase and kebab-case variants
- Add test cases for unquoted and quoted kebab-case formats
- All existing tests continue to pass

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-09-22 09:18:04 -07:00
GitHub Actions 93028b410e chore: bump Claude Code version to 1.0.120 2025-09-19 23:55:52 +00:00
GitHub Actions 838d4d9d25 chore: bump Claude Code version to 1.0.119 2025-09-19 00:53:43 +00:00
GitHub Actions 7ed3b616d5 chore: bump Claude Code version to 1.0.117 2025-09-16 23:49:28 +00:00
kashyap muraliandGitHub 09ea2f00e1 Delete .github/workflows/claude-test.yml (#573) 2025-09-16 13:46:34 -07:00
GitHub Actions 455b943dd7 chore: bump Claude Code version to 1.0.115 2025-09-16 00:52:01 +00:00
GitHub Actions 063d17ebb2 chore: bump Claude Code version to 1.0.113 2025-09-13 02:32:28 +00:00
Kevin CuiandGitHub 2e92922dd6 fix(tag): no such tool available mcp__github_* (#556)
Signed-off-by: Kevin Cui <bh@bugs.cc>

# Conflicts:
#	src/mcp/install-mcp-server.ts
#	src/modes/tag/index.ts
#	test/modes/agent.test.ts
2025-09-12 12:33:34 -07:00
GitHub Actions a5528eec74 chore: bump Claude Code version to 1.0.112 2025-09-12 01:14:51 +00:00
1d4650c102 fix: update test workflow reference in test-local.sh (#564)
* fix: update test workflow reference in test-local.sh

Change workflow file from test-action.yml to test-base-action.yml

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* docs(CLAUDE): update test workflow reference in CLAUDE.md

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-09-11 07:25:16 -07:00
86d6f44e34 chore: consolidate duplicate test directories (#565)
Move detector.test.ts from tests/modes/ to test/modes/ and fix TypeScript
type errors by adding missing required properties (botId, botName, allowedNonWriteUsers).
Remove empty tests/ directory structure.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-09-11 07:24:55 -07:00
GitHub Actions c1adac956c chore: bump Claude Code version to 1.0.111 2025-09-10 23:56:22 +00:00
f197e7bfd5 docs: add documentation for path_to_claude_code_executable and path_to_bun_executable inputs (#562)
Add documentation for the two previously undocumented inputs that allow
users to provide custom executables for specialized environments:

- path_to_claude_code_executable: for custom Claude Code binaries
- path_to_bun_executable: for custom Bun runtime

These inputs are particularly useful for environments like Nix, NixOS,
custom containers, and other package management systems where the
default installation may not work.

Updated files:
- docs/usage.md: Added to inputs table
- docs/faq.md: Added FAQ entry with examples and use cases
- docs/configuration.md: Added dedicated section with examples

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-09-10 16:27:28 -07:00
89f9131f6c Add PostToolUse hook for automatic formatting (#563)
Added a PostToolUse hook that automatically runs `bun run format` after
Edit, Write, or MultiEdit operations, similar to the Python SDK's ruff
formatting hook. This ensures code is automatically formatted whenever
changes are made.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-09-10 13:19:53 -07:00
Jimmy UtterströmandGitHub b78e1c0244 feat: Add ANTHROPIC_CUSTOM_HEADERS environment variable support (#561) 2025-09-10 09:42:54 -07:00
GitHub Actions abf075daf2 chore: bump Claude Code version to 1.0.110 2025-09-10 00:20:34 +00:00
a3ff61d47a enable track_progress for comments, fix mcp config (#558)
* enable track_progress for comments

* refactor: pass mode explicitly to prepareMcpConfig

Update prepareMcpConfig to receive the mode parameter from its callers
instead of detecting agent mode by checking context.inputs.prompt.
This makes mode determination explicit and controlled by the caller.

Also update all test cases to include the required mode parameter
and fix agent mode test expectations to match new behavior where
MCP config is only included when tools are explicitly allowed.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix test

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-09-09 09:19:14 -07:00
PhantomandGitHub 1b7eb924f1 fix: add missing githubContext (#547) 2025-09-08 22:47:46 -07:00
GitHub Actions 0f7dfed927 chore: bump Claude Code version to 1.0.109 2025-09-08 23:47:53 +00:00
11a01b7183 feat: update claude-review workflow to use slash command (#554)
* feat: update claude-review workflow to use progress tracking and slash command

- Rename workflow from "Auto review PRs" to "PR Review with Progress Tracking"
- Update trigger types to include synchronize, ready_for_review, reopened
- Add pull-requests: write permission for tracking comments
- Replace direct_prompt with /review-pr slash command using custom command file
- Update to use claude-code-action@v1
- Switch to inline comment tool for more precise PR feedback

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* agents

* refactor: standardize agent output format instructions

Unified the output format instructions across all reviewer agents to follow a consistent structure:
- Converted numbered sections to bold headers for better readability
- Standardized "Review Structure" sections across all agents
- Maintained distinct analysis areas specific to each reviewer type

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-09-08 07:06:52 -07:00
69dec299f8 feat: add allowed_non_write_users input to bypass permission checks (#550)
* chore: bump Claude Code version to 1.0.108

* triage fix

---------

Co-authored-by: GitHub Actions <actions@github.com>
2025-09-07 14:20:02 -07:00
1a8e7d330a fix: remove unnecessary GitHub comment server inclusion in agent mode (#549)
The GitHub comment MCP server was being included in agent mode even when no comment tools were explicitly allowed. This fix ensures the server is only included in tag mode where it's always needed for updating Claude comments.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-09-07 13:33:21 -07:00
9975f36410 fix: use agent mode for issues events with explicit prompts (#530)
Fixes #528 - Issues events now correctly use agent mode when an explicit
prompt is provided in the workflow YAML, matching the behavior of PR events.

Previously, issues events would always use tag mode (with tracking comments)
even when a prompt was provided, creating inconsistent behavior compared to
pull request events which correctly used agent mode for automation.

The fix adds a check for explicit prompts before checking for triggers,
ensuring consistent mode selection across all event types.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-09-06 20:32:20 -07:00
GitHub Actions c1ffc8a0e8 chore: bump Claude Code version to 1.0.108 2025-09-05 22:50:25 +00:00
13e47489f4 feat: add repository_dispatch event support (#546)
* feat: add repository_dispatch event support

Add support for repository_dispatch events in GitHub context parsing system. This enables the action to handle custom API-triggered events properly.

Changes:
- Add RepositoryDispatchEvent type definition
- Include repository_dispatch in automation event names
- Update context parsing to handle repository_dispatch events
- Update documentation to reflect repository_dispatch availability

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* style: format code with prettier

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* test: add comprehensive repository_dispatch event test coverage

- Add mockRepositoryDispatchContext with realistic payload structure
- Add repository_dispatch mode detection tests in registry.test.ts
- Add repository_dispatch trigger tests in agent.test.ts
- Ensure repository_dispatch events are properly handled as automation events
- Verify agent mode trigger behavior with and without prompts
- All 394 tests passing with new coverage

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* style: format test files with prettier

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-09-05 15:06:00 -07:00
765fadc6a6 fix: remove OIDC id-token permission and add github_token input (#545)
Removes the OIDC id-token permission requirement and adds explicit github_token input to both workflow files. This simplifies authentication by using the standard GITHUB_TOKEN instead of requiring OIDC token exchange.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-09-05 11:58:05 -07:00
fd2c17f101 feat: enhance issue triage workflow with priority labeling and OIDC support (#540)
- Add id-token write permission for OIDC token exchange
- Update prompt to emphasize adding P1/P2/P3 priority labels based on label descriptions
- Ensure Claude selects appropriate priority labels from the available options

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-09-04 20:58:52 -07:00
GitHub Actions a4a723b927 chore: bump Claude Code version to 1.0.107 2025-09-05 02:40:36 +00:00
GitHub Actions d22fa6061b chore: bump Claude Code version to 1.0.106 2025-09-04 23:35:43 +00:00
Ashwin BhatGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Claude
63f1c772bd feat: add bot_id input to handle GitHub App authentication errors (#534)
Adds a new optional bot_id input parameter that defaults to the github-actions[bot] ID (41898282). This resolves the "403 Resource not accessible by integration" error that occurs when using GitHub App installation tokens, which cannot access the /user endpoint.

Changes:
- Add bot_id input to action.yml with default value
- Update context parsing to include bot_id from environment
- Modify agent mode to use bot_id when available, avoiding API calls that fail with GitHub App tokens
- Add clear error handling for GitHub App token limitations
- Update documentation in usage.md and faq.md
- Fix test mocks to include bot_id field

This allows users to specify a custom bot user ID or use the default github-actions[bot] ID automatically, preventing 403 errors in automation workflows.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2025-09-04 14:55:25 -07:00
fb823f6dd6 fix: update action reference to claude-code-action in issue triage workflow (#537)
Changed from @anthropics/claude-code-base-action to @anthropics/claude-code-action
to use the correct action name in the issue triage workflow.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-09-04 14:35:56 -07:00
Ashwin BhatGitHubclaude[bot] <209825114+claude[bot]@users.noreply.github.com>Ashwin Bhat
9e9123239f docs: add timeout_minutes breaking change to migration guide (#529)
Add documentation for the timeout_minutes input removal that occurred in PR #482.
The input has been replaced with standard GitHub Actions timeout-minutes at job level.

Fixes #527

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: Ashwin Bhat <ashwin-ant@users.noreply.github.com>
2025-09-04 12:39:22 -07:00
GitHub Actions 791fcb9fd1 chore: bump Claude Code version to 1.0.105 2025-09-04 16:13:45 +00:00
GitHub Actions 9365bbe4af chore: bump Claude Code version to 1.0.103 2025-09-03 23:44:36 +00:00
GitHub Actions 2e6fc44bd4 chore: bump Claude Code version to 1.0.102 2025-09-02 23:34:15 +00:00
kashyap muraliGitHubClaudekashyap muraliclaude[bot] <209825114+claude[bot]@users.noreply.github.com>
a6ca65328b restore: bring back generic tag mode example (claude.yml) (#516)
* restore: bring back generic tag mode example (claude.yml)

PR #505 removed claude.yml as part of simplifying examples, but this
left a gap - there was no longer a generic tag mode example showing
how to respond to @claude mentions.

This file serves as the primary starting point for users wanting to
use tag mode to have Claude respond to mentions in:
- Issue comments
- Pull request review comments
- Issues (when opened or assigned)
- Pull request reviews

The example includes all required permissions and shows optional
configurations commented out.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* format: add missing newline at end of claude.yml

Co-authored-by: kashyap murali <km-anthropic@users.noreply.github.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: kashyap murali <km-anthropic@users.noreply.github.com>
2025-09-02 11:40:47 -07:00
GitHub Actions ce697c0d4c chore: bump Claude Code version to 1.0.100 2025-09-01 22:45:35 +00:00
b60e3f0e60 fix: add missing id-token: write permissions to issue triage workflow (#519)
Fixes the OIDC authentication issue by adding the required id-token: write permission
to the GitHub Actions workflow for issue triage.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-08-31 07:44:19 -07:00
kashyap muraliGitHubClaudekashyap muraliclaude[bot] <209825114+claude[bot]@users.noreply.github.com>Kashyap Murali
3ed14485f8 feat: improve examples and migration guide with GitHub context (#505)
* feat: improve documentation with solutions guide and GitHub context

- Add comprehensive solutions.md with 9 complete use case examples
- Fix migration guide examples to include required GitHub context
- Update examples missing GitHub context (workflow-dispatch-agent, claude-modes)
- Enhance README with prominent Solutions & Use Cases section
- Document tracking comment behavior change in automation mode
- All PR review examples now include REPO and PR NUMBER context

Fixes issues reported in discussions #490 and #491 where:
- Migration examples were dysfunctional without GitHub context
- Users lost PR review capability after v0.x migration
- Missing explanation of tracking comment removal in agent mode

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: apply prettier formatting to fix CI

Co-authored-by: kashyap murali <km-anthropic@users.noreply.github.com>

* refactor: streamline examples and remove implementation details

- Remove 5 redundant examples (57% reduction: 12→7 files)
  - Deleted: claude.yml, claude-auto-review.yml, claude-modes.yml,
    claude-args-example.yml, auto-fix-ci-signed/
- Rename examples for clarity
  - pr-review-with-tracking.yml → pr-review-comprehensive.yml
  - claude-pr-path-specific.yml → pr-review-filtered-paths.yml
  - claude-review-from-author.yml → pr-review-filtered-authors.yml
  - workflow-dispatch-agent.yml → manual-code-analysis.yml
  - auto-fix-ci/auto-fix-ci.yml → ci-failure-auto-fix.yml
- Update all examples from @v1-dev to @v1
- Remove implementation details (agent mode references) from docs
- Delete obsolete DIY Progress Tracking section
- Add track_progress documentation and examples

Addresses PR feedback about exposing internal implementation details
and consolidates redundant examples into focused, clear use cases.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: apply prettier formatting to fix CI

Applied prettier formatting to 3 files to resolve CI formatting issues.

Co-authored-by: kashyap murali <km-anthropic@users.noreply.github.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: kashyap murali <km-anthropic@users.noreply.github.com>
Co-authored-by: Kashyap Murali <13315300+katchu11@users.noreply.github.com>
2025-08-29 16:55:57 -07:00
45408b4058 feat: make MCP servers conditional in agent mode (#513)
* feat: make MCP servers conditional in agent mode

In agent mode, MCP servers (github_comment, github_ci) are now only included
when explicitly requested via allowedTools, rather than being auto-provisioned.

This change gives agent mode workflows complete control over which MCP
servers are included, preventing unwanted automatic provisioning of GitHub
integration tools.

Changes:
- Add agent mode detection in prepareMcpConfig
- Make github_comment server conditional based on allowedTools in agent mode
- Make github_ci server conditional based on allowedTools in agent mode
- Tag mode behavior remains unchanged (auto-inclusion)

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* test: update agent mode test for conditional MCP behavior

Updated test expectation to match the new conditional MCP server behavior
where agent mode only includes MCP config when servers are actually needed.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Kashyap Murali <13315300+katchu11@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2025-08-29 16:40:14 -07:00
GitHub Actions 1f8cfe7658 chore: bump Claude Code version to 1.0.98 2025-08-29 21:24:55 +00:00
a6888c03f2 feat: add time-based comment filtering to tag mode (#512)
Implement time-based filtering for GitHub comments and reviews to prevent
malicious actors from editing existing comments after Claude is triggered
to inject harmful content.

Changes:
- Add updatedAt and lastEditedAt fields to GraphQL queries
- Update GitHubComment and GitHubReview types with timestamp fields
- Implement filterCommentsToTriggerTime() and filterReviewsToTriggerTime()
- Add extractTriggerTimestamp() to extract trigger time from webhooks
- Update tag and review modes to pass trigger timestamp to data fetcher

Security benefits:
- Prevents comment injection attacks via post-trigger edits
- Maintains chronological integrity of conversation context
- Ensures only comments in their final state before trigger are processed
- Backward compatible with graceful degradation

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-08-29 09:49:08 -07:00
c041f89493 feat: enhance mode routing with track_progress and context preservation (#506)
* feat: enhance mode routing with track_progress and context preservation

This PR implements enhanced mode routing to address two critical v1 migration issues:
1. Lost GitHub context when using custom prompts in tag mode
2. Missing tracking comments for automatic PR reviews

Changes:
- Add track_progress input to force tag mode with tracking comments for PR/issue events
- Support custom prompt injection in tag mode via <custom_instructions> section
- Inject GitHub context as environment variables in agent mode
- Validate track_progress usage (only allowed for PR/issue events)
- Comprehensive test coverage for new routing logic

Event Routing:
- Comment events: Default to tag mode, switch to agent with explicit prompt
- PR/Issue events: Default to agent mode, switch to tag mode with track_progress
- Custom prompts can now be used in tag mode without losing context

This ensures backward compatibility while solving context preservation and tracking visibility issues reported in discussions #490 and #491.

* formatting

* fix: address review comments

- Simplify track_progress description to be more general
- Move import to top of types.ts file

* revert: keep detailed track_progress description

The original description provides clarity about which specific event actions are supported.

* fix: add GitHub CI MCP tools to tag mode allowed list

Claude was trying to use CI status tools but they weren't in the
allowed list for tag mode, causing permission errors. This fix adds
the CI tools so Claude can check workflow status when reviewing PRs.

* fix: provide explicit git base branch reference to prevent PR review errors

- Tell Claude to use 'origin/{baseBranch}' instead of assuming 'main'
- Add explicit instructions for git diff/log commands with correct base branch
- Fixes 'fatal: ambiguous argument main..HEAD' error in fork environments
- Claude was autonomously running git diff main..HEAD when reviewing PRs

* fix prompt generation

* ci pass

---------

Co-authored-by: Ashwin Bhat <ashwin@anthropic.com>
2025-08-28 17:58:32 -07:00
0c127307fa feat: improve PR review examples with context and tools (#504)
- Add PR repository and number to review prompts
- Note that PR branch is already checked out
- Update allowed tools to use inline comments and gh CLI
- Remove experimental review mode example in favor of standardized approach

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-08-28 09:02:27 -07:00
GitHub Actions 8a20581ed5 chore: bump Claude Code version to 1.0.96 2025-08-28 15:32:23 +00:00
GitHub Actions a2ad6b7b4e chore: bump Claude Code version to 1.0.95 2025-08-28 01:26:35 +00:00
f0925925f1 fix: prevent test pollution by ensuring inputs are cloned (#499)
Always create a new object copy of defaultInputs to prevent mutations from affecting other tests.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-08-27 17:14:28 -07:00
GitHub Actions ef8c0a650e chore: bump Claude Code version to 1.0.93 2025-08-27 22:27:45 +00:00
GitHub Actions dd49718216 chore: bump Claude Code version to 1.0.94 2025-08-27 21:53:29 +00:00
GitHub Actions be4b56e1ea chore: bump Claude Code version to 1.0.93 2025-08-26 22:54:12 +00:00
dfef61fdee fix: remove redundant update-major-tag workflow that was incorrectly updating beta (#489)
The update-major-tag.yml workflow was:
1. Incorrectly updating the beta tag instead of major version tags
2. Redundant - release.yml already has an update-major-tag job that properly updates major version tags

Removing this workflow ensures:
- Beta tag stays at v0.0.63 and won't be automatically moved
- No duplicate major tag update logic
- Single source of truth for tag management in release.yml

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Kashyap Murali <13315300+katchu11@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2025-08-26 15:30:37 -07:00
5218d84d4f chore: temporarily disable base action GitHub release creation (#488)
Commenting out the GitHub release creation step for the base action repository
to temporarily pause automatic releases while keeping tag synchronization active.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-08-26 10:30:22 -07:00
Ashwin BhatandGitHub c05ccc5ce4 temporarily remove mcp outer action tests (#487) 2025-08-26 09:47:06 -07:00
41e5ba9012 chore: migrate GitHub workflows from @beta to @v1 (#486)
* tmp

* chore: migrate GitHub workflows from @beta to @v1

- Update claude.yml and issue-triage.yml to use claude-code-action@v1
- Migrate deprecated inputs to new claude_args format
- Move mcp_config to --mcp-config in claude_args
- Follow v1 migration guide for simplified configuration

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-08-26 09:46:56 -07:00
e6f32c8321 Remove mcp_config input in favor of --mcp-config in claude_args (#485)
* Remove mcp_config input in favor of --mcp-config in claude_args

BREAKING CHANGE: The mcp_config input has been removed. Users should now use --mcp-config flag in claude_args instead.

This simplifies the action's input surface area and aligns better with the Claude Code CLI interface. Users can still add multiple MCP configurations by using multiple --mcp-config flags.

Migration:
- Before: mcp_config: '{"mcpServers": {...}}'
- After: claude_args: '--mcp-config {"mcpServers": {...}}'

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Add outer action MCP tests to workflow

- Add test-outer-action-inline-mcp job to test inline MCP config via claude_args
- Add test-outer-action-file-mcp job to test file-based MCP config via claude_args
- Keep base-action tests unchanged (they still use mcp_config parameter)
- Test that MCP tools are properly discovered and can be executed through the outer action

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Fix: Add Bun setup to outer action MCP test jobs

The test jobs for the outer action were failing because Bun wasn't installed.
Added Setup Bun step to both test-outer-action-inline-mcp and test-outer-action-file-mcp jobs.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Add id-token permission to outer action MCP test jobs

The outer action needs id-token: write permission for OIDC authentication
when using the GitHub App. Added full permissions block to both
test-outer-action-inline-mcp and test-outer-action-file-mcp jobs.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Use github_token parameter instead of id-token permission

Replace id-token: write permission with explicit github_token parameter
for both outer action MCP test jobs. This simplifies authentication by
using the provided GitHub token directly.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Use RUNNER_TEMP environment variable consistently

Changed from GitHub Actions expression syntax to environment variable
for consistency with the rest of the workflow file.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Use execution_file output from action instead of hardcoded path

Updated outer action test jobs to:
- Add step IDs (claude-inline-test, claude-file-test)
- Use the execution_file output from the action steps
- This is more reliable than hardcoding the output file path

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* tmp

* Fix MCP test assertions to match actual output format

Updated the test assertions to match the actual JSON structure:
- Tool calls are in assistant messages with type='tool_use'
- Tool results are in user messages with type='tool_result'
- The test tool returns 'Test tool response' not 'Hello from test tool'

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Make inline MCP test actually use the tool instead of just listing

Changed the inline MCP test to:
- Request that Claude uses the test tool (not just list it)
- Add --allowedTools to ensure the tool can be used
- Check that the tool was actually called and returned expected result
- Output the full JSON for debugging

This makes both tests (inline and file-based) consistent in their approach.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-08-26 09:43:18 -07:00
GitHub Actions ada5bc42eb chore: bump Claude Code version to 1.0.92 2025-08-26 00:56:19 +00:00
d6d3ddd4a7 chore: remove beta tag job from release workflow (#479)
Remove the update-beta-tag job since the update-major-tag job already handles major version tagging (v0, v1, v2). Keep release marked as non-latest to allow v1 to remain the latest release.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-08-25 12:57:50 -07:00
0630ef383a feat: implement Claude Code GitHub Action v1.0 with auto-detection and slash commands (#421)
* feat: implement Claude Code GitHub Action v1.0 with auto-detection and slash commands

Major features:
- Mode auto-detection based on GitHub event type
- Unified prompt field replacing override_prompt and direct_prompt
- Slash command system with pre-built commands
- Full backward compatibility with v0.x

Key changes:
- Add mode detector for automatic mode selection
- Implement slash command loader with YAML frontmatter support
- Update action.yml with new prompt input
- Create pre-built slash commands for common tasks
- Update all tests for v1.0 compatibility

Breaking changes (with compatibility):
- Mode input now optional (auto-detected)
- override_prompt deprecated (use prompt)
- direct_prompt deprecated (use prompt)

* test + formatting fixes

* feat: simplify to two modes (tag and agent) for v1.0

BREAKING CHANGES:
- Remove review mode entirely - now handled via slash commands in agent mode
- Remove all deprecated backward compatibility fields (mode, anthropic_model, override_prompt, direct_prompt)
- Simplify mode detection: prompt overrides everything, then @claude mentions trigger tag mode, default is agent mode
- Remove slash command resolution from GitHub Action - Claude Code handles natively
- Remove variable substitution - prompts passed through as-is

Architecture changes:
- Only two modes now: tag (for @claude mentions) and agent (everything else)
- Agent mode is the default for all events including PRs
- Users configure behavior via prompts/slash commands (e.g. /review)
- GitHub Action is now a thin wrapper that passes prompts to Claude Code
- Mode names changed: 'experimental-review' → removed entirely

This aligns with the philosophy that the GitHub Action should do minimal work and delegate to Claude Code for all intelligent behavior.

* fix: address PR review comments for v1.0 simplification

- Remove duplicate prompt field spread (line 160)
- Remove async from generatePrompt since slash commands are handled by Claude Code
- Add detailed comment explaining why prompt → agent mode logic
- Remove entire slash-commands loader and directories as Claude Code handles natively
- Simplify prompt generation to just pass through to Claude Code

These changes align with v1.0 philosophy: GitHub Action is a thin wrapper
that delegates everything to Claude Code for native handling.

* chore: remove unused js-yaml dependencies

These were added for slash-command YAML parsing but are no longer
needed since we removed slash-command preprocessing entirely

* fix: remove experimental-review mode reference from MCP config

The inline comment server configuration was checking for deprecated
'mode' field. Since review mode is removed in v1.0, this conditional
block is no longer needed.

* prettify

* feat: add claudeArgs input for direct CLI argument passing

- Add claude_args input to both action.yml files
- Implement shell-style argument parsing with quote handling
- Pass arguments directly to Claude CLI for maximum flexibility
- Add comprehensive tests for argument parsing
- Log custom arguments for debugging

Users can now pass any Claude CLI arguments directly:
  claude_args: '--max-turns 3 --mcp-config /path/to/config.json'

This provides power users full control over Claude's behavior without
waiting for specific inputs to be added to the action.

* refactor: use industry-standard shell-quote for argument parsing

- Replace custom parseShellArgs with battle-tested shell-quote package
- Simplify code by removing unnecessary -p filtering (Claude handles it)
- Update tests to use shell-quote directly
- Add example workflow showing claude_args usage

This provides more robust argument parsing while reducing code complexity.

* bun format

* feat: add claudeArgs input for direct CLI argument passing

- Add claude_args input to action.yml for flexible CLI control
- Parse arguments with industry-standard shell-quote library
- Maintain proper argument order: -p [claudeArgs] [legacy] [BASE_ARGS]
- Keep tag mode defaults (needed for functionality)
- Agent mode has no defaults (full user control)
- Add comprehensive tests for new functionality
- Add example workflow showing usage

* format

* refactor: complete v1.0 simplification by removing all legacy inputs

- Remove all backward compatibility for v1.0 simplification
- Remove 10 legacy inputs from base-action/action.yml
- Remove 9 legacy inputs from main action.yml
- Simplify ClaudeOptions type to just timeoutMinutes and claudeArgs
- Remove all legacy option handling from prepareRunConfig
- Update tests to remove references to deleted fields
- Remove obsolete test file github/context.test.ts
- Clean up types to remove customInstructions, allowedTools, disallowedTools

Users now use claudeArgs exclusively for CLI control.

* fix: update MCP server tests after removing additionalPermissions

- Change github_ci server logic to check for workflow token presence
- Update test names to reflect new behavior
- Fix test that was incorrectly setting workflow token

* model version update

* Update package json

* remove deprecated workflow file (tests features we no longer support)

* Simplify agent mode and re-add additional_permissions input

- Agent mode now only triggers when explicit prompt is provided
- Removed automatic triggering for workflow_dispatch/schedule without prompt
- Re-added additional_permissions input for requesting GitHub permissions
- Fixed TypeScript types for mock context helpers to properly handle partial inputs
- Updated documentation to reflect simplified mode behavior

* Fix MCP config not being passed to Claude CLI

The MCP servers (including github_comment server) were configured but not passed to Claude. This caused the "update_claude_comment" tool to be unavailable.

Changes:
- Write MCP config to a file at $RUNNER_TEMP/claude-mcp-config.json
- Add mcp_config_file output from prepare.ts
- Pass MCP config file via --mcp-config flag in claude_args
- Use fs/promises writeFile to match codebase conventions

* Fix MCP tool availability and shell escaping in tag mode

Pass MCP config and allowed tools through claude_args to ensure tools like
mcp__github_comment__update_claude_comment are properly available to Claude CLI.

Key changes:
- Tag mode outputs claude_args with MCP config (as JSON string) and allowed tools
- Fixed shell escaping vulnerability when JSON contains single quotes
- Agent mode passes through user-provided claude_args unchanged
- Re-added mcp_config input for users to provide custom MCP servers
- Cleaned up misleading comments and unused file operations
- Clarified test workflow is for fork testing

Security fix: Properly escape single quotes in MCP config JSON to prevent
shell injection vulnerabilities.

Co-Authored-By: Claude <noreply@anthropic.com>

* bun format

* tests, typecheck, format

* registry test update

* Update agent mode to have github server as a default

* Fix agent mode to include GitHub MCP server with proper token

* Simplify review workflow - prevent multiple submissions

- Rename workflow to avoid conflicts
- Remove review submission tools
- Keep only essential tools for reading and analyzing PR

* Add GitHub MCP server and context prefix to agent mode

- Include main GitHub MCP server (Docker-based) by default
- Fetch and prefix GitHub context to prompts when in PR/issue context
- Users no longer need to manually configure GitHub tools

* Delete .github/workflows/claude-auto-review-test.yml

* Remove github_comment and inline_comment servers from agent mode defaults

- Agent mode now only includes the main GitHub MCP server by default
- Users can add additional servers via mcp_config if needed
- Reduces unnecessary MCP server overhead

* Remove all default MCP servers from agent mode

Agent mode now starts with no default servers - users must explicitly configure any MCP servers they need via mcp_config input

* Remove GitHub context prefixing and clean up agent mode

- Remove automatic GitHub context fetching and prefixing
- Remove unused imports (fetcher, formatter, context checks)
- Clean up comments
- Agent mode now simply passes through the user's prompt as-is

* Add GitHub MCP support to agent mode

- Parse --allowedTools from claude_args to detect when user wants GitHub MCPs
- Wire up github_inline_comment server in prepareMcpConfig for PR contexts
- Update agent mode to use prepareMcpConfig instead of manual config
- Add comprehensive tests for parseAllowedTools edge cases
- Fix TypeScript types to support both entity and automation contexts

* Format code with prettier

* Fix agent mode test to expect branch values

* Fix agent test to handle dynamic branch names from environment

* Better fix: Control environment variables in agent test for predictable behavior

* minor formatting

* Simplify MCP configuration to use multiple --mcp-config flags

- Remove MCP config merging logic from prepareMcpConfig
- Update agent and tag modes to pass multiple --mcp-config flags
- Let Claude handle config merging natively through multiple flags
- Fix TypeScript errors in test file

This approach is cleaner and relies on Claude's built-in support for multiple --mcp-config flags instead of manual JSON merging.

* feat: Copy project subagents to Claude runtime environment

Enables custom subagents defined in .claude/agents/ to work in GitHub Actions by:
- Checking for project agents in GITHUB_WORKSPACE/.claude/agents/
- Creating ~/.claude/agents/ directory if needed
- Copying all .md agent files to Claude's runtime location
- Following same pattern as slash commands for consistency

Includes comprehensive test coverage for the new functionality.

* formatting

* Add auto-fix CI workflows with slash command and inline approaches

- Add /fix-ci slash command for programmatic CI failure fixing
- Create auto-fix-ci.yml workflow using slash command approach
- Create auto-fix-ci-inline.yml workflow with full inline prompt
- Both workflows automatically analyze CI failures and create fix branches

* Add workflow_run event support and auto-fix CI workflows

- Add support for workflow_run event type in GitHub context
- Create /fix-ci slash command for programmatic CI failure fixing
- Add auto-fix-ci.yml workflow using slash command approach
- Add auto-fix-ci-inline.yml workflow with full inline prompt
- Both workflows automatically analyze CI failures and create fix branches
- Fix workflow syntax issues with optional chaining operator

* Use proper WorkflowRunEvent type instead of any

* bun formatting

* Remove auto-fix workflows and commands from v1-dev

These files should only exist in km-anthropic fork:
- .github/workflows/auto-fix-ci.yml
- .github/workflows/auto-fix-ci-inline.yml
- slash-commands/fix-ci.md
- .claude/commands/fix-ci.md

The workflow_run event support remains as it's useful for general automation.

* feat: Expose GitHub token as action output for external use

This allows workflows to use the Claude App token obtained by the action
for posting comments as claude[bot] instead of github-actions[bot].

Changes:
- Add github_token output to action.yml
- Export token from prepare.ts after authentication
- Allows workflows to use the same token Claude uses internally

* Debug: Add logging and always output github_token in prepare step

* Fix: Add git authentication to agent mode

Agent mode now fetches the authenticated user (claude[bot] when using Claude App token)
and configures git identity properly, matching the behavior of tag mode.

This fixes the issue where commits in agent mode were failing due to missing git identity.

* minor bun format

* remove unnecessary file

* fix: Add branch environment variable support to agent mode for signed commits

- Read CLAUDE_BRANCH and BASE_BRANCH env vars in agent mode
- Pass correct branch info to MCP file ops server
- Enables signed auto-fix workflows to create branches via API

* feat: Add auto-fix CI workflow examples

- Add auto-fix-ci example with inline git commits
- Add auto-fix-ci-signed example with signed commits via MCP
- Include corresponding slash commands for both workflows
- Examples demonstrate automated CI failure detection and fixing

* fix: Fix TypeScript error in agent mode git config

- Remove dependency on configureGitAuth which expects ParsedGitHubContext
- Implement git configuration directly for automation contexts
- Properly handle git authentication for agent mode

* fix: Align agent mode git config with existing patterns

- Use GITHUB_SERVER_URL from config module consistently
- Remove existing headers before setting new ones
- Use remote URL with embedded token like git-config.ts does
- Match the existing git authentication pattern in the codebase

* refactor: Use shared configureGitAuth function in agent mode

- Update configureGitAuth to accept GitHubContext instead of ParsedGitHubContext
- This allows both tag mode and agent mode to use the same function
- Removes code duplication and ensures consistent git configuration

* feat: Improve error message for 403 permission errors when committing

When the github_file_ops MCP server gets a 403 error, it now shows a cleaner
message suggesting to rebase from main/master branch to fix the issue.

* docs: Update documentation for v1.0 release (#476)

* docs: Update documentation for v1.0 release

- Integrate breaking changes naturally without alarming users
- Replace deprecated inputs (direct_prompt, custom_instructions, mode) with new unified approach
- Update all examples to use prompt and claude_args instead of deprecated inputs
- Add migration guides to help users transition from v0.x to v1.0
- Emphasize automatic mode detection as a key feature
- Update all workflow examples to @v1 from @beta
- Document how claude_args provides direct CLI control
- Update FAQ with automatic mode detection explanation
- Convert all tool configuration to use claude_args format

* fix: Apply prettier formatting to documentation files

* fix: Update all Claude model versions to latest and improve documentation accuracy

- Update all model references to claude-4-0-sonnet-20250805 (latest Sonnet 4)
- Update Bedrock models to anthropic.claude-4-0-sonnet-20250805-v1:0
- Update Vertex models to claude-4-0-sonnet@20250805
- Fix cloud-providers.md to use claude_args instead of deprecated model input
- Ensure all examples use @v1 instead of @beta
- Keep claude-opus-4-1-20250805 in examples where Opus is demonstrated
- Align all documentation with v1.0 patterns consistently

* feat: Add dedicated migration guide as requested in PR feedback

- Create comprehensive migration-guide.md with step-by-step instructions
- Add prominent links to migration guide in README.md
- Update usage.md to reference the separate migration guide
- Include before/after examples for all common scenarios
- Add checklist for systematic migration
- Address Ashwin's feedback about having a separate, clearly linked migration guide

* feat: Add comprehensive examples for hero use cases

- Add dedicated issue deduplication workflow example
- Add issue triage example (moved from .github/workflows)
- Update all examples to use v1-dev branch consistently
- Enable MCP tools in claude-auto-review.yml
- Consolidate PR review examples into single comprehensive example

Hero use cases now covered:
1. Code reviews (claude-auto-review.yml)
2. Issue triaging (issue-triage.yml)
3. Issue deduplication (issue-deduplication.yml)
4. Auto-fix CI failures (auto-fix-ci/auto-fix-ci.yml)

All examples updated to follow v1-dev paradigm with proper prompt and claude_args configuration.

* refactor: Remove timeout_minutes parameter from action (#482)

This change removes the custom timeout_minutes parameter from the action in favor of using GitHub Actions' native timeout-minutes feature.

Changes:
- Removed timeout_minutes input from action.yml and base-action/action.yml
- Removed all timeout handling logic from base-action/src/run-claude.ts
- Updated base-action/src/index.ts to remove timeoutMinutes parameter
- Removed timeout-related tests from base-action/test/run-claude.test.ts
- Removed timeout_minutes from all example workflow files (19 files)

Rationale:
- Simplifies the codebase by removing custom timeout logic
- Users can use GitHub Actions' native timeout-minutes at the job/step level
- Reduces complexity and maintenance burden
- Follows GitHub Actions best practices

BREAKING CHANGE: The timeout_minutes parameter is no longer supported. Users should use GitHub Actions' native timeout-minutes instead.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>

* refactor: Remove unused slash commands and agents copying logic

Removes experimental file copying features that had no default content:
- Removed experimental_slash_commands_dir parameter and related logic
- Removed automatic project agents copying from .claude/agents/
- Eliminated flaky error-prone cp operations with stderr suppression
- Removed 175 lines of unused code and associated tests

These features were infrastructure without default content that used
problematic error handling patterns (2>/dev/null || true) which could
hide real filesystem errors.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: Remove references to timeout_minutes parameter

The timeout_minutes parameter was removed in commit 986e40a but
documentation still referenced it. This updates:
- docs/usage.md: Removed timeout_minutes from inputs table
- base-action/README.md: Removed from inputs table and example

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: km-anthropic <km-anthropic@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Kashyap Murali <13315300+katchu11@users.noreply.github.com>
2025-08-25 12:51:37 -07:00
9c7e1bac94 feat: add path_to_bun_executable input for custom Bun installations (#481)
* feat: add path_to_bun_executable input for custom Bun installations

Adds optional input to specify a custom Bun executable path, bypassing automatic installation. This enables:
- Using pre-installed Bun binaries for faster workflow runs
- Testing with specific Bun versions for debugging
- Custom installation paths in unique environments

Follows the same pattern as path_to_claude_code_executable input.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: consolidate custom executable tests into single workflow

- Remove separate test-custom-executable.yml workflow
- Rename test-custom-bun.yml to test-custom-executables.yml
- Add comprehensive tests for custom Claude, custom Bun, and both together
- Improve verification steps with better error handling

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* simplify: test workflow to single job testing both custom executables

- Remove individual test jobs for Claude and Bun
- Keep only the combined test that validates both custom executables work together
- Simplifies CI workflow and reduces redundant testing

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-08-25 11:08:12 -07:00
dc65f4ac98 feat: add path_to_claude_code_executable input for custom Claude Code… (#480)
* feat: add path_to_claude_code_executable input for custom Claude Code installations

Adds optional input to specify a custom Claude Code executable path, bypassing automatic installation. This enables:
- Using pre-installed Claude Code binaries
- Testing with specific versions for debugging
- Custom installation paths in unique environments

Includes warning that older versions may cause compatibility issues with new features.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* test: add workflow to test custom Claude Code executable path

Adds test workflow that:
- Manually installs Claude Code via install script
- Uses the new path_to_claude_code_executable input
- Verifies the custom executable path works correctly
- Validates output and execution success

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-08-25 08:11:15 -07:00
GitHub Actions 88be3fe6f5 chore: bump Claude Code version to 1.0.90 2025-08-24 23:05:18 +00:00
GitHub Actions a47fdbe49f chore: bump Claude Code version to 1.0.89 2025-08-22 23:01:22 +00:00
GitHub Actions 28f8362010 chore: bump Claude Code version to 1.0.88 2025-08-22 01:22:25 +00:00
9f02f6f6d4 fix: Increase maxBuffer for jq processing to handle large Claude outputs (#473)
Fixes "stdout maxBuffer length exceeded" error by increasing the buffer
from Node.js default of 1MB to 10MB when processing Claude output with jq.
This prevents failures when Claude produces large execution logs.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-08-20 20:02:00 -07:00
GitHub Actions 79cee96324 chore: bump Claude Code version to 1.0.86 2025-08-20 23:27:37 +00:00
194fca8b05 feat: preserve file permissions when committing via GitHub API (#469)
Add file permission detection to github-file-ops-server.ts to properly preserve file modes (regular, executable, symlink) when committing files through the GitHub API. This ensures executable scripts and other special files maintain their correct permissions.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-08-19 17:18:05 -07:00
GitHub Actions 0f913a6e0e chore: bump Claude Code version to 1.0.85 2025-08-19 23:59:52 +00:00
Ashwin BhatandGitHub 68b7ca379c include input bools in claude env (#464) 2025-08-18 17:00:18 -07:00
GitHub Actions 900322ca88 chore: bump Claude Code version to 1.0.84 2025-08-18 23:43:42 +00:00
8f0a7fe9d3 clarify workflow validation message (#463)
Update the workflow validation message to be more specific about when
Claude Code workflows will start working, providing clearer guidance
to users experiencing this validation error.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-08-18 15:50:27 -07:00
Chris BurnsandGitHub db36412854 provides github token for claude code action (#462)
Currently when running the `gh` command in the action, there is an error in the action logs that suggests that the GH_TOKEN isn't being set. We've solved this internally in our company by providing the GH_TOKEN in the action.
2025-08-18 13:13:34 -07:00
f05d669d5f fix: prevent undefined directory creation when RUNNER_TEMP is not set (#461)
When running tests locally, process.env.RUNNER_TEMP is undefined, causing
the code to literally create "undefined/claude-prompts/" directories in
the working directory. Added fallback to "/tmp" following the pattern
already used in src/mcp/github-actions-server.ts.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-08-18 10:51:57 -07:00
e89411bb6f feat: skip action gracefully for workflow validation errors (#460)
* feat: skip action gracefully for workflow validation errors

Handle workflow_not_found_on_default_branch and workflow_content_mismatch
errors by skipping the action with a warning instead of failing. This
improves user experience when adding Claude Code workflows to new
repositories or making workflow changes in PRs.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Update src/github/token.ts

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-08-18 10:50:15 -07:00
Hironori YamamotoandGitHub 02e9ed3181 fix: add Claude Code binary to GitHub Actions PATH (#455) 2025-08-17 21:06:17 -07:00
GitHub Actions 78b07473f5 chore: bump Claude Code version to 1.0.83 2025-08-16 00:11:18 +00:00
Ashwin BhatandGitHub f562ed53e2 fix typo in example (#454) 2025-08-15 13:33:01 -07:00
a1507aefdc Add GitHub token redaction to comment tools (#453)
* Add GitHub token redaction to update_claude_comment tool

- Add redactGitHubTokens() function to sanitizer.ts that detects and redacts all GitHub token formats (ghp_, gho_, ghs_, ghr_, github_pat_)
- Update sanitizeContent() to include token redaction in the sanitization pipeline
- Apply sanitization to comment body in github-comment-server.ts before updating comments
- Add comprehensive tests covering all token formats, edge cases, and integration scenarios
- Prevents accidental exposure of GitHub tokens in PR/issue comments while preserving existing functionality

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Add GitHub token redaction to inline comment server

- Apply sanitizeContent() to comment body in github-inline-comment-server.ts before creating inline PR comments
- Ensures consistency in token redaction across all comment creation tools
- Prevents GitHub tokens from being exposed in inline PR review comments

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-08-15 13:04:52 -07:00
ae66eb6a64 Switch to curl-based Claude Code installation (#452)
Replace bun install with official install script for more reliable
installation across different environments.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-08-15 09:11:02 -07:00
Ashwin BhatandGitHub 432c7cc889 update example workflow (#451) 2025-08-14 19:09:58 -07:00
Ashwin BhatandGitHub 0b138d9d49 Update token.ts copy (#450) 2025-08-14 16:42:49 -07:00
GitHub Actions c34e066a3b chore: bump Claude Code version to 1.0.81 2025-08-14 17:00:23 +00:00
GitHub Actions 449c6791bd chore: bump Claude Code version to 1.0.80 2025-08-13 21:17:49 +00:00
GitHub Actions 2b67ac084b chore: bump Claude Code version to 1.0.77 2025-08-13 20:33:11 +00:00
GitHub Actions 76de8a48fc chore: bump Claude Code version to 1.0.79 2025-08-13 20:26:17 +00:00
GitHub Actions a80505bbfb chore: bump Claude Code version to 1.0.77 2025-08-12 19:25:39 +00:00
GitHub Actions af23644a50 chore: bump Claude Code version to 1.0.76 2025-08-12 18:10:59 +00:00
GitHub Actions 98e6a902bf chore: bump Claude Code version to 1.0.74 2025-08-12 16:19:34 +00:00
GitHub Actions 8b2bd6d04f chore: bump Claude Code version to 1.0.73 2025-08-11 23:43:47 +00:00
Ashwin BhatandGitHub 4f4f43f044 docs: add prominent notice about upcoming v1.0 breaking changes (#437)
- Add GitHub alert box highlighting the v1.0 roadmap
- Link to discussion #428 for community feedback
- Briefly summarize key changes (automatic mode selection, unified prompt interface)
- Position prominently at top of README for maximum visibility
2025-08-10 16:19:08 -07:00
Matthew BurkeandGitHub 8a5d751740 fix - allowed and disallowed tools ignored in agent mode (#424) 2025-08-08 14:34:55 -07:00
GitHub Actions bc423b47f5 chore: bump Claude Code version to 1.0.72 2025-08-08 18:16:40 +00:00
SteveandGitHub 6d5c92076b non negative line validation for comment server (#429)
* enforce non-negative validation for line in GH comment server

* include  .nonnegative() for startLine too
2025-08-08 08:36:20 -07:00
fec554fc7c feat: add flexible bot access control with allowed_bots option (#117)
* feat: skip permission check for GitHub App bot users

GitHub Apps (users ending with [bot]) now bypass permission checks
as they have their own authorization mechanism.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: add allow_bot_users option to control bot user access

- Add allow_bot_users input parameter (default: false)
- Modify checkHumanActor to optionally allow bot users
- Add comprehensive tests for bot user handling
- Improve security by blocking bot users by default

This change prevents potential prompt injection attacks from bot users
while providing flexibility for trusted bot integrations.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: mark bot user support feature as completed in roadmap

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: move allowedBots parameter to context object

Move allowedBots from function parameter to context.inputs to maintain
consistency with other input handling throughout the codebase.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: update README for bot user support feature

Add documentation for the new allowed_bots parameter that enables
bot users to trigger Claude actions with granular control.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: add missing allowedBots property in permissions test

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: update bot name format to include [bot] suffix in tests and docs

- Update test cases to use correct bot actor names with [bot] suffix
- Update documentation example to show correct bot name format
- Align with GitHub's actual bot naming convention

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: normalize bot names for allowed_bots validation

- Strip [bot] suffix from both actor names and allowed bot list for comparison
- Allow both "dependabot" and "dependabot[bot]" formats in allowed_bots input
- Display normalized bot names in error messages for consistency
- Add comprehensive test coverage for both naming formats

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-08-07 18:03:20 -07:00
GitHub Actions 59ca6e42d9 chore: bump Claude Code version to 1.0.71 2025-08-07 22:57:57 +00:00
Aner CohenandGitHub 7afc848186 fix: improve GitHub suggestion guidelines in review mode to prevent code duplication (#422)
* fix: prevent duplicate function signatures in review mode suggestions

This fixes a critical bug in the experimental review mode where GitHub
suggestions could create duplicate function signatures when applied.

The issue occurred because:
- GitHub suggestions REPLACE the entire selected line range
- Claude wasn't aware of this behavior and would include the function
  signature in multi-line suggestions, causing duplication

Changes:
- Added detailed instructions about GitHub's line replacement behavior
- Provided clear examples for single-line vs multi-line suggestions
- Added explicit warnings about common mistakes (duplicate signatures)
- Improved code readability by using a codeBlock variable instead of
  escaped backticks in template strings

This ensures Claude creates syntactically correct suggestions that
won't break code when applied through GitHub's suggestion feature.

* chore: format
2025-08-07 08:56:30 -07:00
Graham CampbellandGitHub 6debac392b Go with Opus 4.1 (#420) 2025-08-06 21:22:15 -07:00
GitHub Actions 55fb6a96d0 chore: bump Claude Code version to 1.0.70 2025-08-06 19:59:40 +00:00
15db2b3c79 feat: add inline comment MCP server for experimental review mode (#414)
* feat: add inline comment MCP server for experimental review mode

- Create standalone inline PR comments without review workflow
- Support single-line and multi-line comments
- Auto-install server when in experimental review mode
- Uses octokit.rest.pulls.createReviewComment() directly

* docs: clarify GitHub code suggestion syntax in inline comment server

Add clear documentation that suggestion blocks replace the entire selected
line range and must be syntactically complete drop-in replacements.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-08-06 08:21:29 -07:00
Ashwin BhatandGitHub 188d526721 refactor: change git hook from pre-push to pre-commit (#401)
- Renamed scripts/pre-push to scripts/pre-commit
- Updated install-hooks.sh to install pre-commit hook
- Hook now runs formatting, type checking, and tests before commit
2025-08-05 17:02:34 -07:00
a519840051 fix: remove git config user.name and user.email from allowed tools (#410)
These git config commands are no longer needed as allowed tools since
Claude should not be modifying git configuration settings. Updated
the corresponding test to reflect this intentional change.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-08-05 11:32:46 -07:00
yoshikoukiandGitHub 85287e957d fix: restore prompt file creation in agent mode (#405)
- Restore prompt file creation logic that was accidentally removed in PR #374
- Agent mode now creates the prompt file directly in prepare() method
- Uses override_prompt or direct_prompt if available, falls back to minimal prompt
- Fixes 'Prompt file does not exist' error for workflow_dispatch and schedule events
- Add TODO comment to refactor this to use createPrompt in the future

Fixes #403
2025-08-05 11:14:28 -07:00
GitHub Actions c6a07895d7 chore: bump Claude Code version to 1.0.69 2025-08-05 16:50:23 +00:00
atsushi-ishibashiandGitHub 0c5d54472f feat: Add HTML img tag support to GitHub image downloader (#402)
* feat: support html img tag

* rm files

* refactor
2025-08-04 19:37:50 -07:00
GitHub Actions 2845685880 chore: bump Claude Code version to 1.0.68 2025-08-04 23:29:44 +00:00
Ashwin BhatandGitHub b39377f9bc feat: add getSystemPrompt method to mode interface (#400)
Allows modes to provide custom system prompts that are appended to Claude's base system prompt. This enables mode-specific instructions without modifying the core action logic.

- Add optional getSystemPrompt method to Mode interface
- Implement method in all existing modes (tag, agent, review)
- Update prepare.ts to call getSystemPrompt and export as env var
- Wire up APPEND_SYSTEM_PROMPT in action.yml to pass to base-action

All modes currently return undefined (no additional prompts), but the infrastructure is now in place for future modes to provide custom instructions.
2025-08-04 10:51:30 -07:00
Matthew BurkeandGitHub 618565bc0e Update documentation incorrectly reverted after refactor (#399) 2025-08-04 09:00:22 -07:00
Ashwin BhatandGitHub 0d9513b3b3 refactor: restructure documentation into organized docs directory (#383)
- Move FAQ.md to docs/faq.md
- Create structured documentation files:
  - setup.md: Manual setup and custom GitHub app instructions
  - usage.md: Basic usage and workflow configuration
  - custom-automations.md: Automation examples
  - configuration.md: MCP servers and advanced settings
  - experimental.md: Execution modes and network restrictions
  - cloud-providers.md: AWS Bedrock and Google Vertex setup
  - capabilities-and-limitations.md: Features and constraints
  - security.md: Security information
- Condense README.md to overview with links to detailed docs
- Keep CONTRIBUTING.md, SECURITY.md, CODE_OF_CONDUCT.md at top level
2025-08-03 21:16:50 -07:00
458e4b9e7f feat: ship slash commands with GitHub Action (#381)
* feat: add slash command shipping infrastructure

- Created /slash-commands/ directory to store bundled slash commands
- Added code-review.md slash command for automated PR reviews
- Modified setup-claude-code-settings.ts to copy slash commands to ~/.claude/
- Added test coverage for slash command installation
- Commands are automatically installed when the GitHub Action runs

* fix: simplify slash command implementation to match codebase patterns

- Reverted to using Bun's $ shell syntax consistently with the rest of the codebase
- Simplified slash command copying to basic shell commands
- Removed unnecessary fs/promises complexity
- Maintained all functionality and test coverage
- More appropriate for GitHub Action context where inputs are trusted

* remove test slash command

* fix: rename slash_commands_dir to experimental_slash_commands_dir

- Added 'experimental' prefix as suggested by Ashwin
- Updated all references in action.yml and base-action
- Restored accidentally removed code-review.md file

---------

Co-authored-by: km-anthropic <km-anthropic@users.noreply.github.com>
2025-08-03 21:05:33 -07:00
Ashwin BhatandGitHub d66adfb7fa refactor: rename ACTIONS_TOKEN to DEFAULT_WORKFLOW_TOKEN (#385)
Updated all references from ACTIONS_TOKEN to DEFAULT_WORKFLOW_TOKEN to match
the naming convention used in action.yml where the GitHub token is passed as
DEFAULT_WORKFLOW_TOKEN environment variable.
2025-08-02 21:26:52 -07:00
GitHub Actions d829b4d14b chore: bump Claude Code version to 1.0.67 2025-08-01 22:56:22 +00:00
0a78530f89 docs: clarify agent mode only works with workflow_dispatch and schedule events (#378)
* docs: clarify agent mode only works with workflow_dispatch and schedule events

Updates documentation to match the current implementation where agent mode
is restricted to workflow_dispatch and schedule events only. This addresses
the confusion reported in issues #364 and #376.

Changes:
- Updated README to clearly state agent mode limitations
- Added explicit note that agent mode does NOT work with PR/issue events
- Updated example workflows to only show supported event types
- Updated CLAUDE.md internal documentation

Fixes #364
Fixes #376

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* minor formatting update

* update agent mode docs

---------

Co-authored-by: km-anthropic <km-anthropic@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2025-08-01 15:47:53 -07:00
GitHub Actions 20e09ef881 chore: bump Claude Code version to 1.0.65 2025-08-01 22:28:48 +00:00
56179f5fc9 feat: add review mode for automated PR code reviews (#374)
* feat: add review mode for PR code reviews

- Add 'review' as a new execution mode in action.yml
- Use default GitHub Action token (ACTIONS_TOKEN) for review mode
- Create review mode implementation with GitHub MCP tools included by default
- Move review-specific prompt to review mode's generatePrompt method
- Add comprehensive review workflow instructions for inline comments
- Fix type safety with proper mode validation
- Keep agent mode's simple inline prompt handling

* docs: add review mode example workflow

* update sample workflow

* fix: update review mode example to use @beta tag

* fix: enable automatic triggering for review mode on PR events

* fix: export allowed tools environment variables in review mode

The GitHub MCP tools were not being properly allowed because review mode
wasn't exporting the ALLOWED_TOOLS environment variable like agent mode does.
This caused all GitHub MCP tool calls to be blocked with permission errors.

* feat: add review mode workflow for testing

* fix: use INPUT_ prefix for allowed/disallowed tools environment variables

The base action expects INPUT_ALLOWED_TOOLS and INPUT_DISALLOWED_TOOLS
(following GitHub Actions input naming convention) but we were exporting
them without the INPUT_ prefix. This was causing the tools to not be
properly allowed in the base action.

* fix: add explicit review tool names and additional workflow permissions

- Add explicit tool names in case wildcards aren't working properly
- Add statuses and checks write permissions to workflow
- Include both github and github_comment MCP server tools

* refactor: consolidate review workflows and use review mode

- Update claude-review.yml to use review mode instead of direct_prompt
- Use km-anthropic fork action
- Remove duplicate claude-review-mode.yml workflow
- Add synchronize event to review PR updates
- Update permissions for review mode (remove id-token, add pull-requests/issues write)

* feat: enhance review mode to provide detailed tracking comment summary

- Update review mode prompt to explicitly request detailed summaries
- Include issue counts, key findings, and recommendations in tracking comment
- Ensure users can see complete review overview without checking each inline comment

* Revert "refactor: consolidate review workflows and use review mode"

This reverts commit 54ca948599.

* fix: address PR review feedback for review mode

- Make generatePrompt required in Mode interface
- Implement generatePrompt in all modes (tag, agent, review)
- Remove unnecessary git/branch operations from review mode
- Restrict review mode triggers to specific PR actions
- Fix type safety issues by removing any types
- Update tests to support new Mode interface

* test: update mode registry tests to include review mode

* chore: run prettier formatting

* fix: make mode parameter required in generatePrompt function

Remove optional mode parameter since the function throws an error when mode is not provided. This makes the type signature consistent with the actual behavior.

* fix: remove last any type and update README with review mode

- Remove any type cast in review mode by using isPullRequestEvent type guard
- Add review mode documentation to README execution modes section
- Update mode parameter description in README configuration table

* mandatory bun format

* fix: improve review mode GitHub suggestion format instructions

- Add clear guidance on GitHub's suggestion block format
- Emphasize that suggestions must only replace the specific commented lines
- Add examples of correct vs incorrect suggestion formatting
- Clarify when to use multi-line comments with startLine and line parameters
- Guide on handling complex changes that require multiple modifications

This should resolve issues where suggestions aren't directly committable.

* Add missing MCP tools for experimental-review mode based on test requirements

* chore: format code

* docs: add experimental-review mode documentation with clear warnings

* docs: remove emojis from experimental-review mode documentation

* docs: clarify experimental-review mode triggers - depends on workflow configuration

* minor format update

* test: fix registry tests for experimental-review mode name change

* refactor: clean up review mode implementation based on feedback

- Remove unused parameters from generatePrompt in agent and review modes
- Keep Claude comment requirement for review mode (tracking comment)
- Add overridePrompt support to review mode
- Remove non-existent MCP tools from review mode allowed list
- Fix unused import in agent mode

These changes address all review feedback while maintaining clean code
and proper functionality.

* fix: remove redundant update_claude_comment from review mode allowed tools

The github_comment server is always included automatically, so we don't
need to explicitly list mcp__github_comment__update_claude_comment in
the allowed tools.

* feat: review mode now uses review body instead of tracking comment

- Remove tracking comment creation from review mode
- Update prompt to instruct Claude to write comprehensive review in body
- Remove comment ID requirement for review mode
- The review submission body now serves as the main review content

This makes review mode cleaner with one less comment on the PR. The
review body contains all the information that would have been in the
tracking comment.

* add back id-token: write for example

* Add PR number for context + make it mandatory to have a PR associated

* add `mcp__github__add_issue_comment` tool

* rename token

* bun format

---------

Co-authored-by: km-anthropic <km-anthropic@users.noreply.github.com>
2025-08-01 15:04:23 -07:00
GitHub Actions 0e5fbc0d44 chore: bump Claude Code version to 1.0.66 2025-08-01 21:53:55 +00:00
atsushi-ishibashiandGitHub b4cc5cd6c5 fix: include cache tokens in token usage display (#367)
* fix: Include cache tokens in the stats

* Update format-turns.ts

* fix test

* format
2025-08-01 07:40:07 -07:00
GitHub Actions 1b4ac7d7e0 chore: bump Claude Code version to 1.0.65 2025-07-31 22:02:40 +00:00
GitHub Actions 1f6e3225b0 chore: bump Claude Code version to 1.0.64 2025-07-30 21:49:34 +00:00
atsushi-ishibashiandGitHub 6672e9b357 Remove empty XML tags in Issue context to reduce token usage (#369)
* chore: remove empty xml tags

* format
2025-07-30 07:30:32 -07:00
aki77andGitHub 950bdc01df fix: update GitHub MCP server tool name for PR review comments (#363)
Update add_pull_request_review_comment_to_pending_review to add_comment_to_pending_review
  following upstream change in github/github-mcp-server#697

  - Update .github/workflows/claude-review.yml
  - Update examples/claude-auto-review.yml
2025-07-30 07:20:20 -07:00
atsushi-ishibashiandGitHub 15dd796e97 use total_cost_usd (#366) 2025-07-30 07:19:29 -07:00
atsushi-ishibashiandGitHub fd012347a2 feat: exclude hidden (minimized) comments from GitHub Issues and PRs (#368)
* feat: ignore minimized comments

* fix tests
2025-07-30 07:18:34 -07:00
5bdc533a52 docs: enhance CLAUDE.md with comprehensive architecture overview (#362)
* docs: enhance CLAUDE.md with comprehensive architecture overview

- Add detailed two-phase execution architecture documentation
- Document mode system (tag/agent) and extensible registry pattern
- Include comprehensive GitHub integration layer breakdown
- Add MCP server architecture and authentication flow details
- Document branch strategy, comment threading, and code conventions
- Provide complete project structure with component descriptions

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: clarify base-action dual purpose and remove branch strategy

- Explain base-action serves as both standalone published action and internal logic
- Remove branch strategy section as requested
- Improve architecture documentation clarity

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-07-29 18:03:45 -07:00
YutaSaitoandGitHub d45539c118 fix: move env var before image name in docker run for github-mcp-server (#361)
In the previous commit (e07ea013bd), the GITHUB_HOST variable was placed after the image name in the Docker run command, which caused a runtime error. This commit moves the -e option before the image name so it is correctly passed into the container.
2025-07-29 16:22:39 -07:00
daac7e353f refactor: implement discriminated unions for GitHub contexts (#360)
* feat: add agent mode for automation scenarios

- Add agent mode that always triggers without checking for mentions
- Implement Mode interface with support for mode-specific tool configuration
- Add getAllowedTools() and getDisallowedTools() methods to Mode interface
- Simplify tests by combining related test cases
- Update documentation and examples to include agent mode
- Fix TypeScript imports to prevent circular dependencies

Agent mode is designed for automation and workflow_dispatch scenarios
where Claude should always run without requiring trigger phrases.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Minor update to readme (from @main to @beta)

* Since workflow_dispatch isn't in the base action, update the examples accordingly

* minor formatting issue

* Update to say beta instead of main

* Fix missed tracking comment to be false

* add schedule & workflow dispatch paths. Also make prepare logic conditional

* tests

* Add test workflow for workflow_dispatch functionality

* Update workflow to use correct branch reference

* remove test workflow dispatch file

* minor lint update

* update workflow dispatch agent example

* minor lint update

* refactor: simplify prepare logic with mode-specific implementations

* ensure tag mode can't work with workflow dispatch and schedule tasks

* simplify: remove workflow_dispatch/schedule from create-prompt

- Remove workflow_dispatch and schedule event handling from create-prompt
  since agent mode doesn't use the standard prompt generation flow
- Enforce mode compatibility at selection time in the registry instead
  of runtime validation in tag mode
- Add explanatory comment in agent mode about why prompt file is needed
- Update tests to reflect simplified event handling

This reduces code duplication and makes the separation between tag mode
(entity-based events) and agent mode (automation events) clearer.

* simplify PR by making agent mode only work with workflow dispatch and schedule events

* remove unnecessary changes

* remove unnecessary changes from PR

- Revert update-comment-link.ts changes (agent mode doesn't use this)
- Revert create-initial.ts changes (agent mode doesn't create comments)
- Remove unused default-branch.ts file
- Revert install-mcp-server.ts changes (agent mode uses minimal MCP)

These files are only used by tag mode for entity-based events, not needed
for workflow_dispatch/schedule support via agent mode.

* fix: handle optional entityNumber for TypeScript

- Add runtime checks in files that require entityNumber
- These files are only used by tag mode which always has entityNumber
- Agent mode (workflow_dispatch/schedule) doesn't use these files

* linting update

* refactor: implement discriminated unions for GitHub contexts

Split ParsedGitHubContext into entity-specific and automation contexts:
- ParsedGitHubContext: For entity events (issues/PRs) with required entityNumber and isPR
- AutomationContext: For workflow_dispatch/schedule events without entity fields
- GitHubContext: Union type for all contexts

This eliminates ~20 null checks throughout the codebase and provides better type safety.
Entity-specific code paths are now guaranteed to have the required fields.

Co-Authored-By: Claude <noreply@anthropic.com>

* update comment

* More robust type checking

* refactor: improve discriminated union implementation based on review feedback

- Use eventName checks instead of 'in' operator for more robust type guards
- Remove unnecessary type assertions - TypeScript's control flow analysis works correctly
- Remove redundant runtime checks for entityNumber and isPR
- Simplify code by using context directly after type guard

Co-Authored-By: Claude <noreply@anthropic.com>

* some structural simplification

* refactor: further simplify discriminated union implementation

- Add event name constants to reduce duplication
- Derive EntityEventName and AutomationEventName types from constants
- Use isAutomationContext consistently in agent mode and registry
- Simplify parseGitHubContext by removing redundant type assertions
- Extract payload casts to variables for cleaner code

Co-Authored-By: Claude <noreply@anthropic.com>

* bun format

* specify the type

* minor linting update again

---------

Co-authored-by: km-anthropic <km-anthropic@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2025-07-29 14:58:59 -07:00
GitHub Actions bdfdd1f788 chore: bump Claude Code version to 1.0.63 2025-07-29 21:43:48 +00:00
ec0e9b4f87 add schedule & workflow dispatch paths. Also make prepare logic conditional (#353)
* feat: add agent mode for automation scenarios

- Add agent mode that always triggers without checking for mentions
- Implement Mode interface with support for mode-specific tool configuration
- Add getAllowedTools() and getDisallowedTools() methods to Mode interface
- Simplify tests by combining related test cases
- Update documentation and examples to include agent mode
- Fix TypeScript imports to prevent circular dependencies

Agent mode is designed for automation and workflow_dispatch scenarios
where Claude should always run without requiring trigger phrases.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Minor update to readme (from @main to @beta)

* Since workflow_dispatch isn't in the base action, update the examples accordingly

* minor formatting issue

* Update to say beta instead of main

* Fix missed tracking comment to be false

* add schedule & workflow dispatch paths. Also make prepare logic conditional

* tests

* Add test workflow for workflow_dispatch functionality

* Update workflow to use correct branch reference

* remove test workflow dispatch file

* minor lint update

* update workflow dispatch agent example

* minor lint update

* refactor: simplify prepare logic with mode-specific implementations

* ensure tag mode can't work with workflow dispatch and schedule tasks

* simplify: remove workflow_dispatch/schedule from create-prompt

- Remove workflow_dispatch and schedule event handling from create-prompt
  since agent mode doesn't use the standard prompt generation flow
- Enforce mode compatibility at selection time in the registry instead
  of runtime validation in tag mode
- Add explanatory comment in agent mode about why prompt file is needed
- Update tests to reflect simplified event handling

This reduces code duplication and makes the separation between tag mode
(entity-based events) and agent mode (automation events) clearer.

* simplify PR by making agent mode only work with workflow dispatch and schedule events

* remove unnecessary changes

* remove unnecessary changes from PR

- Revert update-comment-link.ts changes (agent mode doesn't use this)
- Revert create-initial.ts changes (agent mode doesn't create comments)
- Remove unused default-branch.ts file
- Revert install-mcp-server.ts changes (agent mode uses minimal MCP)

These files are only used by tag mode for entity-based events, not needed
for workflow_dispatch/schedule support via agent mode.

* fix: handle optional entityNumber for TypeScript

- Add runtime checks in files that require entityNumber
- These files are only used by tag mode which always has entityNumber
- Agent mode (workflow_dispatch/schedule) doesn't use these files

* linting update

---------

Co-authored-by: km-anthropic <km-anthropic@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2025-07-29 11:52:45 -07:00
Ashwin BhatandGitHub af32fd318a Revert "feat: add GITHUB_HOST to github-mcp-server for GitHub Enterprise Serv…" (#359)
This reverts commit e07ea013bd.
2025-07-29 11:51:20 -07:00
YutaSaitoandGitHub e07ea013bd feat: add GITHUB_HOST to github-mcp-server for GitHub Enterprise Server (#343) 2025-07-28 17:39:52 -07:00
aki77andGitHub 6037d754ac chore: update MCP server image to version 0.9.0 (#344) 2025-07-28 16:20:59 -07:00
GitHub Actions 04b2df22d4 chore: bump Claude Code version to 1.0.62 2025-07-28 22:36:23 +00:00
8fc9a366cb chore: update Claude Code installation to use bun and version 1.0.61 (#352)
- Switch from npm to bun for Claude Code installation in base-action
- Update Claude Code version from 1.0.59 to 1.0.61 in main action
- Ensures consistent package manager usage across both action files

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-07-28 09:44:51 -07:00
GitHub Actions 7c5a98d59d chore: bump Claude Code version to 1.0.61 2025-07-25 21:06:57 +00:00
c3e0ab4d6d feat: add agent mode for automation scenarios (#337)
* feat: add agent mode for automation scenarios

- Add agent mode that always triggers without checking for mentions
- Implement Mode interface with support for mode-specific tool configuration
- Add getAllowedTools() and getDisallowedTools() methods to Mode interface
- Simplify tests by combining related test cases
- Update documentation and examples to include agent mode
- Fix TypeScript imports to prevent circular dependencies

Agent mode is designed for automation and workflow_dispatch scenarios
where Claude should always run without requiring trigger phrases.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Minor update to readme (from @main to @beta)

* Since workflow_dispatch isn't in the base action, update the examples accordingly

* minor formatting issue

* Update to say beta instead of main

* Fix missed tracking comment to be false

---------

Co-authored-by: km-anthropic <km-anthropic@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2025-07-24 14:53:15 -07:00
GitHub Actions 94437192fa chore: bump Claude Code version to 1.0.60 2025-07-24 21:02:45 +00:00
9cf75f75b9 feat: format PR and issue body text in prompt variables (#330)
* feat: format PR and issue body text in prompt variables

Apply formatBody function to PR_BODY and ISSUE_BODY variables to properly handle images and markdown formatting in prompt context.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* style: format PR_BODY and ISSUE_BODY ternary expressions

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: add claude_code_oauth_token to all GitHub workflow tests

Add claude_code_oauth_token parameter to all test workflow files to support new authentication method. This ensures proper authentication for Claude Code API access in GitHub Actions.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Revert "feat: add claude_code_oauth_token to all GitHub workflow tests"

This reverts commit fccc1a0ebd.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-07-23 22:16:10 -07:00
a58dc37018 Add mode support (#333)
* Add mode support

* update "as any" with proper "as unknwon as ModeName" casting

* Add documentation to README and registry.ts

* Add  tests for differen event types, integration flows, and error conditions

* Clean up some tests

* Minor test fix

* Minor formatting test + switch from interface to type

* correct the order of mkdir call

* always configureGitAuth as there's already a fallback to handle null users by using the bot ID

* simplify registry setup

---------

Co-authored-by: km-anthropic <km-anthropic@users.noreply.github.com>
2025-07-23 20:35:11 -07:00
963754fa12 perf: optimize Squid proxy startup time (#334)
* perf: optimize Squid proxy startup time

- Replace fixed 7-second sleep with dynamic readiness check
- Only shutdown existing Squid if actually running
- Add detailed timing logs to track each step's duration
- Expected reduction: ~7-8 seconds to ~1-2 seconds startup overhead

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: extract squid setup into standalone script

Move squid proxy setup logic from action.yml inline bash script
to scripts/setup-network-restrictions.sh for better maintainability
and cleaner action configuration.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Revert "refactor: extract squid setup into standalone script"

This reverts commit b18aa2821d.

* tmp

* Reapply "refactor: extract squid setup into standalone script"

This reverts commit 07f6911549.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-07-23 20:33:29 -07:00
Ashwin BhatandGitHub 3f4d843152 Revert "feat: integrate Claude Code SDK to replace process spawning (#327)" (#335)
* Revert "feat: integrate Claude Code SDK to replace process spawning (#327)"

This reverts commit 204266ca45.

* 1.0.59
2025-07-23 18:42:43 -07:00
GitHub Actions e26577a930 chore: bump Claude Code version to 1.0.59 2025-07-23 21:38:01 +00:00
GitHub Actions eba34996fb chore: bump Claude Code version to 1.0.58 2025-07-23 21:24:21 +00:00
0763498a5a feat: add DETAILED_PERMISSION_MESSAGES env var to Claude Code invocation (#328)
Enables detailed permission messages in Claude Code by setting the
DETAILED_PERMISSION_MESSAGES environment variable to '1' in the
Run Claude Code step.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-07-22 20:02:46 -07:00
204266ca45 feat: integrate Claude Code SDK to replace process spawning (#327)
* feat: integrate Claude Code SDK to replace process spawning

- Add @anthropic-ai/claude-code dependency to base-action
- Replace mkfifo/cat process spawning with direct SDK usage
- Remove global Claude Code installation from action.yml files
- Maintain full compatibility with existing options
- Add comprehensive tests for SDK integration

This change makes the implementation cleaner and more reliable by
eliminating the complexity of managing child processes and named pipes.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: add debugging and bun executable for Claude Code SDK

- Add stderr handler to capture CLI errors
- Explicitly set bun as the executable for the SDK
- This should help diagnose why the CLI is exiting with code 1

* fix: extract mcpServers from parsed MCP config

The SDK expects just the servers object, not the wrapper object with mcpServers property.

* tsc

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-07-22 16:56:54 -07:00
GitHub Actions ef304464bb chore: bump Claude Code version to 1.0.58 2025-07-22 23:12:32 +00:00
Ashwin BhatandGitHub 0d204a6599 feat: clarify direct prompt instructions in create-prompt (#324)
- Added IMPORTANT note explaining direct prompts are user instructions that take precedence
- Updated the direct instruction notice to be marked as CRITICAL and HIGH PRIORITY
- These changes make it clearer that direct prompts override other context
2025-07-22 07:26:14 -07:00
c96a923d95 refactor: clarify git command availability and remove user config instruction (#322)
- Update wording to remind users about available git commands instead of implying limitation
- Remove git user configuration instruction as it's not needed for action usage

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-07-21 20:44:19 -07:00
b89253bcb0 chore: use bun install instead of npm for Claude Code installation (#323)
Replace npm install with bun install for consistency with the rest of the project's package management.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-07-21 20:41:45 -07:00
Whoemoon JangandGitHub 51e00deb08 fix: git checkout disambiguate error (#306)
See also https://git-scm.com/docs/git-checkout#_argument_disambiguation
2025-07-21 20:11:25 -07:00
8f551b358e Add override prompt variable (#301)
* Add override prompt variable

* create test

* Fix typechecks

* remove use of `any` for additional type-safety

---------

Co-authored-by: km-anthropic <km-anthropic@users.noreply.github.com>
2025-07-21 17:41:25 -07:00
GitHub Actions 0d8a8fe1ac chore: bump Claude Code version to 1.0.57 2025-07-22 00:25:13 +00:00
93df09fd88 fix: checkout base branch before creating new branches (#311)
- Fix bug where base_branch parameter was not being respected
- Add git fetch and checkout of source branch before creating new branch
- Ensures new branches are created from specified base_branch instead of current HEAD
- Fixes issue #268

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-07-19 08:26:59 -07:00
d290268f83 fix: run Claude from workflow directory instead of base-action directory (#312)
Changed the action to cd back to the original directory after installing
dependencies, ensuring Claude runs in the context of the user's workflow
rather than the base-action subdirectory.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-07-19 08:26:23 -07:00
Ashwin BhatandGitHub d69f61e377 fix: conditionally show Bash limitation based on commit signing setting (#310)
- Remove 'Run arbitrary Bash commands' from limitations when commit signing is disabled
- This avoids confusion since git commands ARE allowed via Bash when not using commit signing
- The prompt now accurately reflects what Claude can do based on the useCommitSigning parameter
2025-07-19 08:18:05 -07:00
de86beb3ae fix: add model parameter support to base-action (#307)
- Add model field to ClaudeOptions type
- Pass ANTHROPIC_MODEL env var to runClaude function
- Handle --model argument in prepareRunConfig

This allows the model specified in action.yml to be properly passed
to the Claude CLI command.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-07-19 07:48:29 -07:00
GitHub Actions 5c420d2402 chore: bump Claude Code version to 1.0.56 2025-07-19 00:07:08 +00:00
f6e7adf89e fix: add Bedrock base URL fallback to match base-action configuration (#304)
The action.yml was missing the fallback logic to construct the Bedrock
endpoint URL from AWS_REGION when ANTHROPIC_BEDROCK_BASE_URL is not
explicitly set. This matches the configuration in claude-code-base-action.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-07-18 16:15:17 -07:00
d1e03ad18e feat: update sync workflow to use MIRROR_DISCLAIMER.md file (#300)
- Add MIRROR_DISCLAIMER.md file to base-action directory
- Update sync workflow to concatenate disclaimer with README
- Cleaner approach than embedding content in workflow file

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-07-18 14:54:19 -07:00
dfa92d6952 feat: add workflow to sync base-action to claude-code-base-action repo (#299)
* feat: add workflow to sync base-action to claude-code-base-action repo

This workflow automatically mirrors the base-action directory to the
anthropics/claude-code-base-action repository whenever changes are
pushed to base-action files on the main branch.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: add automated release sync to claude-code-base-action

- Release workflow now creates matching releases in claude-code-base-action repo
- All release jobs now run in production environment
- Uses CLAUDE_CODE_BASE_ACTION_PAT for authentication

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-07-18 14:22:43 -07:00
8335bda243 feat: integrate claude-code-base-action as local subaction (#285)
* feat: integrate claude-code-base-action as local subaction

- Copy claude-code-base-action into base-action/ directory
- Update action.yml to reference ./base-action instead of external repo
- Preserve complete base action structure for future refactoring

This eliminates the external dependency while maintaining modularity.

* feat: consolidate CI workflows and add version bump workflow

- Move base-action test workflows to main .github/workflows/
- Update workflow references to use ./base-action
- Add CI jobs for base-action (test, typecheck, prettier)
- Add bump-claude-code-version workflow for base-action
- Remove redundant .github directory from base-action

This consolidates all CI workflows in one place while maintaining
full test coverage for both the main action and base-action.

* tsc

* copy again

* fix tests

* fix: use absolute path for base-action reference

Replace relative path ./base-action with ${{ github.action_path }}/base-action
to ensure the action works correctly when used in other repositories.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: inline base-action execution to support usage in other repos

Replace uses: ./base-action with direct shell execution since GitHub Actions
doesn't support dynamic paths in composite actions. This ensures the action
works correctly when used in other repositories.

Changes:
- Install Claude Code globally before execution
- Run base-action's index.ts directly with bun
- Pass all required INPUT_* environment variables
- Maintain base-action for future separate publishing

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-07-18 13:52:56 -07:00
David DworkenandGitHub 00b4a23551 fix: prevent command injection in git hash-object call (#297)
* Update package name to reference under the @Anthropic-AI NPM org

* fix: prevent command injection in git hash-object call

* Revert accidental change
2025-07-18 09:58:22 -07:00
Ashwin BhatGitHubAshwin Bhatclaude[bot] <209825114+claude[bot]@users.noreply.github.com>
d4d7974604 fix: use GITHUB_SERVER_URL to determine email domain for GitHub Enterprise (#290)
* fix: use GITHUB_SERVER_URL to determine email domain for GitHub Enterprise

- Extract hostname from GITHUB_SERVER_URL environment variable
- Use users.noreply.github.com for GitHub.com
- Use users.noreply.{hostname} for GitHub Enterprise instances

Fixes #288

Co-authored-by: Ashwin Bhat <ashwin-ant@users.noreply.github.com>

* lint

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: Ashwin Bhat <ashwin-ant@users.noreply.github.com>
2025-07-17 08:11:48 -07:00
GitHub Actions 8fcb8e16b8 chore: update claude-code-base-action to v0.0.36 2025-07-17 00:26:16 +00:00
06b3126baf Add Squid proxy network restrictions for claude-code-action (#259)
* feat: add Squid proxy network restrictions to Claude workflow

Implements URL whitelisting for GitHub Actions to prevent unauthorized network access.
Only allows connections to:
- Claude API (anthropic.com)
- GitHub services
- Package registries (npm, bun)
- Azure blob storage for caching

Uses NO_PROXY for package registries to avoid integrity check issues.

* test: add network restrictions verification test

* test: simplify network restrictions test output

* refactor: make network restrictions opt-in and move to examples

- Removed network restrictions from .github/workflows/claude.yml
- Added network restrictions to examples/claude.yml as opt-in feature
- Changed from DISABLE_NETWORK_RESTRICTIONS to ENABLE_NETWORK_RESTRICTIONS
- Added support for CUSTOM_ALLOWED_DOMAINS repository variable
- Organized whitelist by provider (Anthropic, Bedrock, Vertex AI)
- Removed package registries from whitelist (already in NO_PROXY)

Users can now enable network restrictions by setting ENABLE_NETWORK_RESTRICTIONS=true
and configure additional domains via CUSTOM_ALLOWED_DOMAINS.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Minor bun format

* test: simplify network restrictions test

- Reduce to one allowed and one blocked domain
- Remove slow google.com test
- Fix TypeScript errors with AbortController
- Match test formatting conventions

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Move network restrictions to actions.yml + show custom domains in the examples folder

* Simplify network restrictions -- Move it to actions, remove extended examples in claude.yml and move them to readme

* Remove unnecessary network restrictions test and update readme + action.yml with no default domains and respective instructions in the readme

* Update README with common domains

* Give an example of network restriction in claude.yml

* Remove unnecesssary NO_PROXY as packages are installed beforehand

* Remove proxy example -- it's intuitive for users to figure it out

* Update potential EOF not being treated as a string issue

* update claude.yml to test

* Update example allowed_domains with tested domains for network restrictions

* change to experimental allowed domains and add `.blob.core.windows.net` to use cached bun isntall

* Update remaining allowed_domains references to experimental_allowed_domains

* Reset claude.yml to match origin/main

Remove network restrictions test changes from claude.yml

* Format README.md table alignment

Run bun format to fix table column alignment

---------

Co-authored-by: km-anthropic <km-anthropic@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2025-07-16 12:39:45 -07:00
bf2400d475 docs: add missing use_commit_signing input to README (#283)
* docs: add missing use_commit_signing input to README

Added the `use_commit_signing` input to the README's inputs table. This input was present in action.yml but not documented in the README.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* ci: add documentation consistency check to PR reviews

Updated claude-review.yml to include checking that README.md and other documentation files are updated to reflect code changes, especially for new inputs, features, or configuration options.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-07-16 11:33:13 -07:00
Ashwin BhatandGitHub 4e2cfbac36 Fix: Pass correct branch names to MCP file ops server (#279)
* Reapply "feat: defer remote branch creation until first commit (#244)" (#278)

This reverts commit 018533dc9a.

* fix branch names
2025-07-15 17:10:23 -07:00
Ashwin BhatandGitHub 018533dc9a Revert "feat: defer remote branch creation until first commit (#244)" (#278)
This reverts commit cefe963a6b.
2025-07-15 16:05:30 -07:00
a9d9ad3612 feat: add settings input support (#276)
- Add settings input to action.yml that accepts JSON string or file path
- Pass settings parameter to claude-code-base-action
- Update README with comprehensive settings documentation
- Add link to official Claude Code settings documentation
- Document precedence rules for model and tool permissions

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-07-15 14:00:26 -07:00
GitHub Actions 4824494f4d chore: update claude-code-base-action to v0.0.35 2025-07-15 18:54:33 +00:00
c09fc691c5 docs: add custom GitHub App setup instructions (#267)
Add comprehensive section explaining how to create and use a custom GitHub App
instead of the official Claude app. This is particularly useful for users with
restrictive organization policies or those using AWS Bedrock/Google Vertex AI.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-07-14 17:17:56 -07:00
GitHub Actions b3c6de94ea chore: update claude-code-base-action to v0.0.34 2025-07-14 15:59:55 +00:00
Jay DerinbogazandGitHub b92e56a96b refactor: update branch naming convention for Kubernetes compatibility (#249)
* refactor: update branch naming convention for Kubernetes compatibility

- Changed timestamp format in branch names to a shorter, Kubernetes-compatible style (lowercase, hyphens only).
- Updated related tests to reflect new branch name format.
- Ensured branch names are limited to a maximum of 50 characters to comply with Kubernetes naming requirements.

* refactor: clean up timestamp formatting in branch naming logic

- Removed unnecessary whitespace and standardized string formatting for the Kubernetes-compatible timestamp in branch names.
- Ensured consistency in the use of double quotes for string literals.
2025-07-12 11:30:49 -07:00
David WellsandGitHub b6868bfc27 Expose the created branch for downstream usage (#237)
* Expose the created branch for downstream usage

* run bun format
2025-07-11 10:15:41 -07:00
Allen LiandGitHub 0f9a2c4dc3 fix: add GITHUB_API_URL to all Octokit client instantiations (#243)
Not all Octokit client instantiations were respecting GITHUB_API_URL, so
these tools would fail on enterprise.
2025-07-11 07:46:23 -07:00
Ashwin BhatandGitHub cefe963a6b feat: defer remote branch creation until first commit (#244)
* feat: defer remote branch creation until first commit

- For commit signing: branches are created remotely by github-file-ops-server on first commit
- For non-signing: branches are created locally with 'git checkout -b' and pushed when needed
- Consolidated duplicate branch creation logic in github-file-ops-server into a shared helper function
- Claude is unaware of these implementation details and simply sees it's on the correct branch
- No branch links are shown in initial comments since branches don't exist remotely yet

* fix: prevent broken branch links in final comment update

- Check if branch exists remotely before adding branch link
- Only add branch links for branches that actually exist on GitHub
- Add test coverage for non-existent remote branches
- Fixes issue where users would see broken branch links for local-only branches

* fix: don't show branch name in comment header when branch doesn't exist remotely

- Only pass branchName to updateCommentBody when branchLink exists
- Prevents showing branch names for branches that only exist locally
- Add test to verify branch name is not shown when branch doesn't exist

* tmp
2025-07-10 12:57:15 -07:00
GitHub Actions eda5af4e69 chore: update claude-code-base-action to v0.0.33 2025-07-10 17:05:41 +00:00
87facd7051 feat: add use_commit_signing input with default false (#238)
* feat: add use_commit_signing input with default false

- Add new input 'use_commit_signing' to action.yml (defaults to false)
- Separate comment update functionality into standalone github-comment-server.ts
- Update MCP server configuration to conditionally load servers based on signing preference
- When commit signing is disabled, use specific Bash git commands (e.g., Bash(git add:*))
- When commit signing is enabled, use github-file-ops-server for atomic commits with signing
- Always include github-comment-server for comment updates regardless of signing mode
- Update prompt generation to provide appropriate instructions based on signing preference
- Add comprehensive test coverage for new functionality

This change simplifies the default setup for users who don't need commit signing,
while maintaining the option to enable it for those who require GitHub's commit
signature verification.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: auto-commit uncommitted changes when commit signing is disabled

- Check for uncommitted changes after Claude finishes (non-signing mode only)
- Automatically commit and push any uncommitted work to preserve Claude's changes
- Update tests to avoid actual git operations during test runs
- Pass use_commit_signing flag to branch cleanup logic

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-07-09 16:28:36 -07:00
a804c9e83f feat: add OAuth token authentication support (#236)
* feat: add OAuth token authentication support

Add claude_code_oauth_token as an alternative authentication method to anthropic_api_key.
This provides more flexibility for users who prefer OAuth authentication.

- Add claude_code_oauth_token input to action.yml
- Pass OAuth token through to claude-code-base-action
- Update README with OAuth token documentation and examples
- Update security best practices to cover both authentication methods
- Add OAuth example to examples/claude.yml

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: add OAuth token generation instructions for Pro/Max users

Update README to mention that Pro and Max users can generate OAuth tokens
by running `claude setup-token` locally. This provides clearer guidance
for users who want to use OAuth authentication instead of API keys.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: update CI capabilities documentation

- Move GitHub Actions access from limitations to capabilities in README
- Update FAQ to explain how to enable CI/CD access with actions:read permission
- Clarify that Claude can access workflow results on PRs where it's tagged

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-07-07 16:07:22 -07:00
GitHub Actions d6bc8ddf8a chore: update claude-code-base-action to v0.0.32 2025-07-07 22:54:31 +00:00
Ashwin BhatGitHubclaude[bot] <209825114+claude[bot]@users.noreply.github.com>Ashwin Bhat
86665d0984 feat: forward NODE_VERSION environment variable to base action (#230)
This allows users to override the default Node version by setting the
NODE_VERSION environment variable in their workflow.

Fixes #229

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: Ashwin Bhat <ashwin-ant@users.noreply.github.com>
2025-07-06 16:21:00 -07:00
6364776f60 fix: update MCP server image to version 0.6.0 (#234)
🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-07-05 22:12:48 -07:00
Rodrigo YokotaandGitHub e43c1b7fac fix(github): fixing claude login user name (#227)
* fix(github): fixing claude login user name

* fix: improving bot user identification conditions

* fix: making a const out of claude bot id
2025-07-04 11:14:14 -07:00
23fae74fdb Add GitHub Actions MCP server for viewing workflow results (#231)
* actions server

* tmp

* Replace view_actions_results with additional_permissions input

- Changed input from boolean view_actions_results to a more flexible additional_permissions format
- Uses newline-separated colon format similar to claude_env (e.g., "actions: read")
- Maintains permission checking to warn users when their token lacks required permissions
- Updated all tests to use the new format

This allows for future extensibility while currently supporting only "actions: read" permission.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Update GitHub Actions MCP server with RUNNER_TEMP and status filtering

- Use RUNNER_TEMP environment variable for log storage directory (defaults to /tmp)
- Add status parameter to get_ci_status tool to filter workflow runs
- Supported statuses: completed, action_required, cancelled, failure, neutral, skipped, stale, success, timed_out, in_progress, queued, requested, waiting, pending
- Pass RUNNER_TEMP from install-mcp-server.ts to the MCP server environment

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Add GitHub Actions MCP tools to allowed tools when actions:read is granted

- Automatically include github_ci MCP server tools in allowed tools list when actions:read permission is granted
- Added mcp__github_ci__get_ci_status, mcp__github_ci__get_workflow_run_details, mcp__github_ci__download_job_log
- Simplified permission checking to avoid duplicate parsing logic
- Added tests for the new functionality

This ensures Claude can use the Actions tools when the server is enabled.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Refactor additional permissions parsing to parseGitHubContext

- Moved additional permissions parsing from individual functions to centralized parseGitHubContext
- Added parseAdditionalPermissions function to handle newline-separated colon format
- Removed redundant additionalPermissions parameter from prepareMcpConfig
- Updated tests to use permissions from context instead of passing as parameter
- Added comprehensive tests for parseAdditionalPermissions function

This centralizes all input parsing logic in one place for better maintainability.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Remove unnecessary hasActionsReadPermission parameter from createPrompt

- Removed hasActionsReadPermission parameter since createPrompt has access to context
- Calculate hasActionsReadPermission directly from context.inputs.additionalPermissions inside createPrompt
- Simplified prepare.ts by removing intermediate permission check

This completes the refactoring to centralize all permission handling through the context object.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: Add documentation for additional_permissions feature

- Document the new additional_permissions input that replaces view_actions_results
- Add dedicated section explaining CI/CD integration with actions:read permission
- Include example workflow showing how to grant GitHub token permissions
- Update main workflow example to show optional additional_permissions usage

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* roadmap

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-07-03 18:58:02 -07:00
Ashwin BhatandGitHub 3c739a8cf3 Add retry logic for intermittent 403 errors in MCP file operations (#232)
- Extract retry logic to shared utility in src/utils/retry.ts
- Update token.ts to use shared retry utility
- Add retry with exponential backoff to git reference updates
- Only retry on 403 errors, fail immediately on other errors
- Use shorter delays (1-5s) for transient GitHub API failures

This handles intermittent 403 'Resource not accessible by integration'
errors transparently without requiring workflow permission changes. These
errors appear to be transient GitHub API issues that succeed on retry.
2025-07-03 15:56:19 -07:00
GitHub Actions aa28d465c5 chore: update claude-code-base-action to v0.0.31 2025-07-03 21:42:32 +00:00
55b7205cd2 feat: add fallback_model input to enable automatic model fallback (#228)
- Add fallback_model input to action.yml matching claude-code-base-action
- Pass fallback_model through to the base action
- Document the new input in README.md inputs table
- Enables automatic fallback when primary model is unavailable

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-07-03 11:09:10 -07:00
8fe405c45f feat: add formatted output for Claude Code execution reports (#18)
* feat: add formatted output for Claude Code execution reports

- Write turns formatter
- Modify GitHub Action to call formatter instead of dumping raw JSON
- Add comprehensive unit tests (30 tests) covering all functionality
- Add integration test with sample data for output consistency
- Support syntax highlighting for multiple content types (JSON, Python, bash, etc.)
- Include turn grouping logic and token usage tracking
- Provide CLI interface for standalone formatter usage

🤖 Generated with [Claude Code](https://claude.ai/code)
Note: seriously I have never written any line of ts code in my life, so
please make sure this is fine as I don't give any guarantees

Co-Authored-By: Claude <noreply@anthropic.com>

* Add fallback

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-07-03 10:59:12 -07:00
Ashwin BhatandGitHub 73012199e4 fix sticky comment variable name (#226)
* fix sticky comment variable name

* fix condition
2025-07-03 09:18:28 -07:00
GitHub Actions 459b56e54d chore: update claude-code-base-action to v0.0.30 2025-07-03 04:27:02 +00:00
GitHub Actions 00f9595fb4 chore: update claude-code-base-action to v0.0.29 2025-07-01 21:38:26 +00:00
79f2086fce feat: add sticky_comment input to reduce GitHub comment spam (#211)
* feat: no claude spam

* feat: add silent property

* feat: add silent property

* feat: add silent property

* chore: call me a sticky comment

* chore: applying review comments

* chore: apply review comments

* format

* reword

---------

Co-authored-by: Ashwin Bhat <ashwin@anthropic.com>
2025-07-01 10:37:39 -07:00
Julien TanayandGitHub bcb072b63f docs: add FAQ entry about assigning in a private repo (#218) 2025-07-01 07:17:03 -07:00
GitHub Actions e3b3e531a7 chore: update claude-code-base-action to v0.0.28 2025-07-01 02:57:05 +00:00
a7665d3698 fix: resolve CI issues - formatting and TypeScript errors (#217)
* fixed file ingestion

* working binary files

* added replaced baseUrl

* fix: add type assertion for GitHub blob API response

Fixes TypeScript error where blobData was of type 'unknown' by adding
proper type assertion for the blob creation response.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Andrew Grosser <dioptre@gmail.com>
Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Andrew Grosser <dioptre@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
2025-06-30 19:56:37 -07:00
91c510a769 fix: add missing LABEL_TRIGGER environment variable to prepare step (#209)
The label_trigger input was defined but not passed as an environment variable
to the prepare step, causing it to be undefined in the prepare script.
This adds the missing LABEL_TRIGGER environment variable mapping.

Co-authored-by: taku.tsunose <taku.tsunose@takutsunosenoMacBook-Pro.local>
2025-06-27 09:25:00 -07:00
GitHub Actions 1e006bf2d0 chore: update claude-code-base-action to v0.0.27 2025-06-26 01:00:41 +00:00
Stefano AmorelliandGitHub ece712ea81 chore(README): add base branch parameter (#201) 2025-06-25 14:21:46 -07:00
Stefano AmorelliandGitHub 032008d3b6 feat(config): add branch prefix configuration (#197) 2025-06-25 14:01:25 -07:00
Tomohiro IshibashiandGitHub b0d9b8c4cd Add label trigger functionality to Claude Code Action (#177)
- introduced a new input parameter `label_trigger` in `action.yml` to allow triggering actions based on specific labels applied to issues.
- Enhanced the context preparation and event handling in the code to support the new labled event.
2025-06-25 10:25:26 -07:00
GitHub Actions c831be8f54 chore: update claude-code-base-action to v0.0.26 2025-06-24 23:47:06 +00:00
38254908ae fix: allow direct_prompt with issue assignment without requiring assignee_trigger (#192)
- Modified validation logic to only require assignee_trigger when direct_prompt is not provided
- Made assigneeTrigger optional in IssueAssignedEvent type definition
- Enhanced context generation to handle missing assigneeTrigger gracefully
- Added comprehensive test coverage for the new behavior

This enables direct_prompt workflows on issue assignment events without
requiring assignee_trigger configuration, fixing the error:
"ASSIGNEE_TRIGGER is required for issue assigned event"

Fixes #113

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-06-23 17:41:25 -07:00
882586e496 chore: remove unwanted files added in commit 91f620f (#193)
Remove example-dispatch-workflow.yml and pr-summary.md that were
unintentionally added to the root directory in commit 91f620f.
These files should not be in the repository root.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-06-23 17:39:14 -07:00
GitHub Actions 28aaa5404d chore: update claude-code-base-action to v0.0.25 2025-06-24 00:35:11 +00:00
GitHub Actions ebbd9e9be4 chore: update claude-code-base-action to v0.0.24 2025-06-20 21:50:00 +00:00
GitHub Actions 237de9d329 chore: update claude-code-base-action to v0.0.23 2025-06-20 15:38:21 +00:00
Ashwin BhatGitHubclaude[bot] <209825114+claude[bot]@users.noreply.github.com>Ashwin Bhat
91f620f8c2 docs: remove references to non-existent test-local.sh script (#187)
All tests for this repo can be run with `bun test` - the test-local.sh script was a holdover from the base action repo.

Fixes #172

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: Ashwin Bhat <ashwin-ant@users.noreply.github.com>
2025-06-19 07:52:42 -07:00
GitHub Actions 3486c33ebf chore: update claude-code-base-action to v0.0.22 2025-06-17 21:59:57 +00:00
13ccdab2f8 fix: correct assignee trigger test to handle different assignee properly (#178)
* fix: use direct assignee field

* fix: correct assignee trigger test to handle different assignee properly

The test was failing because the mockIssueAssignedContext was missing the
top-level assignee field that the trigger validation logic checks. Added
the missing assignee field to the mock context and updated the test to
properly override both the top-level assignee and issue.assignee fields
when testing assignment to a different user.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Adjust IssuesAssignedEvent import position (#2)

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-06-17 10:06:06 -07:00
GitHub Actions bcf2fe94f8 chore: update claude-code-base-action to v0.0.21 2025-06-17 13:39:54 +00:00
Ashwin BhatandGitHub 2dab3f2afe Revert "feat: enhance error reporting with specific error types from Claude e…" (#179)
This reverts commit 1b94b9e5a8.
2025-06-16 16:52:07 -07:00
1b94b9e5a8 feat: enhance error reporting with specific error types from Claude execution (#164)
* feat: enhance error reporting with specific error types from Claude execution

- Extract error subtypes (error_during_execution, error_max_turns) from result object
- Display specific error messages in comment header based on error type
- Use total_cost_usd field from SDKResultMessage type
- Prevent showing redundant error details when already displayed in header

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* chore: update claude-code-base-action to v0.0.19

* feat: use GitHub display name in Co-authored-by trailers (#163)

* feat: use GitHub display name in Co-authored-by trailers

- Add name field to GitHubAuthor type
- Update GraphQL queries to fetch user display names
- Add triggerDisplayName to CommonFields type
- Extract display name from fetched GitHub data in prepareContext
- Update Co-authored-by trailer generation to use display name when available

This ensures consistency with GitHub's web interface behavior where
Co-authored-by trailers use the user's display name rather than username.

Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com>

* fix: update GraphQL queries to handle Actor type correctly

The name field is only available on the User subtype of Actor in GitHub's
GraphQL API. This commit updates the queries to use inline fragments
(... on User) to conditionally access the name field when the actor is
a User type.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: clarify Co-authored-by instructions in prompt

Replace interpolated values with clear references to XML tags and add
explicit formatting instructions. This makes it clearer how to use the
GitHub display name when available while maintaining the username for
the email portion.

Changes:
- Use explicit references to <trigger_display_name> and <trigger_username> tags
- Add clear formatting instructions and example
- Explain fallback behavior when display name is not available

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: fetch trigger user display name via dedicated GraphQL query

Instead of trying to extract the display name from existing data (which
was incomplete due to Actor type limitations), we now:

- Add a dedicated USER_QUERY to fetch user display names
- Pass the trigger username to fetchGitHubData
- Fetch the display name during data collection phase
- Simplify prepareContext to use the pre-fetched display name

This ensures we always get the correct display name for Co-authored-by
trailers, regardless of where the trigger came from.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>

* feat: use dynamic fetch depth based on PR commit count (#169)

- Replace fixed depth of 20 with dynamic calculation
- Use Math.max(commitCount, 20) to ensure minimum context

* Accept multiline input for allowed_tools and disallowed_tools (#168)

* docs: add uv example for Python MCP servers in mcp_config section (#170)

Added documentation showing how to configure Python-based MCP servers using uv
with the --directory argument, as requested in issue #130.

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: Ashwin Bhat <ashwin-ant@users.noreply.github.com>

* feat: add release workflow with beta tag management (#171)

- Auto-increment patch version for new releases
- Update beta tag to point to latest release
- Update major version tag (v0) for simplified action usage
- Support dry run mode for testing
- Keep beta as the "latest" release channel

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>

* chore: update claude-code-base-action to v0.0.20

* update MCP server image to version 0.5.0 (#175)

* refactor: convert error subtype check to switch case

Replace if-else chain with switch statement for better readability
and maintainability when handling error subtypes.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: GitHub Actions <actions@github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com>
Co-authored-by: Bastian Gutschke <bge@medicuja.com>
Co-authored-by: Hidetake Iwata <int128@gmail.com>
Co-authored-by: Tomohiro Ishibashi <103555868+tomoish@users.noreply.github.com>
2025-06-16 15:31:43 -07:00
Tomohiro IshibashiandGitHub e0d3fec39f update MCP server image to version 0.5.0 (#175) 2025-06-16 07:40:13 -07:00
GitHub Actions 3c748dc927 chore: update claude-code-base-action to v0.0.20 2025-06-14 02:45:07 +00:00
ffb2927088 feat: add release workflow with beta tag management (#171)
- Auto-increment patch version for new releases
- Update beta tag to point to latest release
- Update major version tag (v0) for simplified action usage
- Support dry run mode for testing
- Keep beta as the "latest" release channel

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-06-13 17:43:56 -04:00
Ashwin BhatGitHubclaude[bot] <209825114+claude[bot]@users.noreply.github.com>Ashwin Bhat
def1b3a94e docs: add uv example for Python MCP servers in mcp_config section (#170)
Added documentation showing how to configure Python-based MCP servers using uv
with the --directory argument, as requested in issue #130.

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: Ashwin Bhat <ashwin-ant@users.noreply.github.com>
2025-06-13 14:15:50 -07:00
Hidetake IwataandGitHub 67d7753c80 Accept multiline input for allowed_tools and disallowed_tools (#168) 2025-06-13 10:19:36 -04:00
Bastian GutschkeandGitHub a8d323af27 feat: use dynamic fetch depth based on PR commit count (#169)
- Replace fixed depth of 20 with dynamic calculation
- Use Math.max(commitCount, 20) to ensure minimum context
2025-06-13 10:13:30 -04:00
Ashwin BhatGitHubClaudeclaude[bot] <209825114+claude[bot]@users.noreply.github.com>ashwin-ant
41dd0aa695 feat: use GitHub display name in Co-authored-by trailers (#163)
* feat: use GitHub display name in Co-authored-by trailers

- Add name field to GitHubAuthor type
- Update GraphQL queries to fetch user display names
- Add triggerDisplayName to CommonFields type
- Extract display name from fetched GitHub data in prepareContext
- Update Co-authored-by trailer generation to use display name when available

This ensures consistency with GitHub's web interface behavior where
Co-authored-by trailers use the user's display name rather than username.

Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com>

* fix: update GraphQL queries to handle Actor type correctly

The name field is only available on the User subtype of Actor in GitHub's
GraphQL API. This commit updates the queries to use inline fragments
(... on User) to conditionally access the name field when the actor is
a User type.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: clarify Co-authored-by instructions in prompt

Replace interpolated values with clear references to XML tags and add
explicit formatting instructions. This makes it clearer how to use the
GitHub display name when available while maintaining the username for
the email portion.

Changes:
- Use explicit references to <trigger_display_name> and <trigger_username> tags
- Add clear formatting instructions and example
- Explain fallback behavior when display name is not available

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: fetch trigger user display name via dedicated GraphQL query

Instead of trying to extract the display name from existing data (which
was incomplete due to Actor type limitations), we now:

- Add a dedicated USER_QUERY to fetch user display names
- Pass the trigger username to fetchGitHubData
- Fetch the display name during data collection phase
- Simplify prepareContext to use the pre-fetched display name

This ensures we always get the correct display name for Co-authored-by
trailers, regardless of where the trigger came from.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2025-06-12 18:16:36 -04:00
GitHub Actions 55966a1dc0 chore: update claude-code-base-action to v0.0.19 2025-06-12 21:55:17 +00:00
GitHub Actions b10f287695 chore: update claude-code-base-action to v0.0.18 2025-06-11 23:01:51 +00:00
GitHub Actions 56d8eac7ce chore: update claude-code-base-action to v0.0.17 2025-06-11 22:03:34 +00:00
Ashwin BhatGitHubashwin-antclaude[bot] <209825114+claude[bot]@users.noreply.github.com>
25f9b8ef9e fix: add baseUrl to Octokit initialization in update_claude_comment (#157)
* fix: add baseUrl to Octokit initialization in update_claude_comment

Fixes Bad credentials error on GitHub Enterprise Server by passing
GITHUB_API_URL as baseUrl when initializing Octokit, consistent with
other Octokit instances in the codebase.

Fixes #156
Related to #107

Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com>

* fix: pass GITHUB_API_URL as env var to MCP server

Update the MCP server initialization to pass GITHUB_API_URL as an
environment variable, allowing it to work correctly with GitHub
Enterprise Server instances.

Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com>

* fix: import GITHUB_API_URL from config in install-mcp-server

Use the centralized GITHUB_API_URL constant from src/github/api/config.ts instead of reading directly from process.env when passing environment variables to the MCP server. This ensures consistency with how the API URL is handled throughout the codebase.

Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com>

* fix

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com>
2025-06-11 17:45:05 -04:00
Ashwin BhatGitHubclaude[bot] <209825114+claude[bot]@users.noreply.github.com>ashwin-ant
3bcfbe7385 feat: add MultiEdit to base_allowed_tools (#155)
Add MultiEdit tool to the BASE_ALLOWED_TOOLS array to enable Claude Code to use the MultiEdit tool for making multiple edits to a single file in one operation.

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com>
2025-06-10 19:36:52 -04:00
GitHub Actions bdd0c925cb chore: update claude-code-base-action to v0.0.14 2025-06-10 19:08:55 +00:00
atsushi-ishibashiandGitHub 37ec8e4781 fix: set disallowed_tools as env when runing prepare.ts (#151) 2025-06-10 08:59:55 -04:00
Ashwin BhatandGitHub e5b1633249 feat: add roadmap for Claude Code GitHub Action v1.0 (#150)
Add ROADMAP.md documenting planned features and improvements for reaching v1.0:
- GitHub Action CI results visibility
- Cross-repo support
- Workflow file modification capabilities
- Additional event trigger support
- Configurable commit signing
- Enhanced code review features
- Bot user trigger support
- Customizable base prompts

The roadmap provides transparency on development priorities and invites
community feedback and contributions.
2025-06-09 18:58:08 -04:00
Ashwin BhatGitHubashwin-antclaude[bot] <209825114+claude[bot]@users.noreply.github.com>
37483ba112 feat: add max_turns parameter support (#149)
* feat: add max_turns parameter support

- Add max_turns input to action.yml with proper description
- Pass max_turns parameter through to claude-code-base-action
- Update README with documentation and examples for max_turns usage
- Add comprehensive tests to verify max_turns configuration
- Add yaml dependency for test parsing

Closes #148

Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com>

* chore: remove max-turns test and yaml dependency

Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com>

* chore: revert package.json and bun.lock changes

Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com>

* Update action.yml

* prettier

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com>
2025-06-09 13:28:22 -04:00
Sepehr SobhaniandGitHub 9b50f473cb Update allowed tools align with what is available in github-mcp-server (#145) 2025-06-08 16:24:25 -04:00
GitHub Actions 47ea5c2a69 chore: update claude-code-base-action to v0.0.13 2025-06-06 19:44:49 +00:00
GitHub Actions 4bd9c2053a chore: update claude-code-base-action to v0.0.12 2025-06-06 15:30:07 +00:00
GitHub Actions f862b5a16a chore: update claude-code-base-action to v0.0.11 2025-06-05 23:19:03 +00:00
Ashwin BhatandGitHub 424d1b8f87 update package name (#135) 2025-06-05 13:41:23 -07:00
David DworkenandGitHub 1d5e695d0c Update package name to reference under the @Anthropic-AI NPM org (#134) 2025-06-05 13:28:36 -07:00
Ashwin BhatGitHubclaude[bot] <209825114+claude[bot]@users.noreply.github.com>ashwin-ant
8e8be41f15 fix: replace github.action_path with GITHUB_ACTION_PATH for containerized workflows (#133)
This change fixes an issue where the action fails in containerized workflow 
jobs. By using ${GITHUB_ACTION_PATH} instead of ${{ github.action_path }}, 
the action can properly locate its resources in container environments.

Fixes #132

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com>
2025-06-05 10:10:43 -07:00
Ashwin BhatandGitHub c7957fda5d chore: update claude-code-base-action to v0.0.10 (#131) 2025-06-05 09:59:42 -07:00
Minsu LeeandGitHub 1990b0bdb3 Update temp directory paths to use runner temp directory (#129)
* Update temp directory paths to use `runner` temp directory

* Update temp directory paths to use `runner` temp directory
2025-06-05 09:53:56 -07:00
Ashwin BhatGitHubclaude[bot] <209825114+claude[bot]@users.noreply.github.com>ashwin-ant
699aa26b41 fix: only load GitHub MCP server when its tools are allowed (#124)
* fix: only load GitHub MCP server when its tools are allowed

- Add allowedTools parameter to prepareMcpConfig
- Check for mcp__github__ and mcp__github_file_ops__ tool prefixes
- Only include MCP servers when their tools are in allowed_tools
- Maintain backward compatibility when allowed_tools is not specified
- Update tests to reflect the new conditional loading behavior

This optimizes resource usage by not loading unnecessary MCP servers
when their tools are not allowed in the configuration.

Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com>

* fix: always load github_file_ops server regardless of allowed_tools

- Only apply conditional loading to the github MCP server
- Always load github_file_ops server as it contains essential tools
- Update tests to reflect this behavior

Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com>

* refactor: move allowedTools/disallowedTools parsing to parseGitHubContext

- Change allowedTools and disallowedTools from string to string[] in ParsedGitHubContext type
- Parse comma-separated environment variables into arrays in parseGitHubContext function
- Update create-prompt and install-mcp-server to use pre-parsed arrays
- Update all affected test files to use array syntax
- Eliminate duplicate parsing logic across the codebase

* style: apply prettier formatting

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com>
2025-06-04 11:56:56 -07:00
Ashwin BhatGitHubashwin-antclaude[bot] <209825114+claude[bot]@users.noreply.github.com>
94c0c31c1b chore: switch to upstream github-mcp-server v0.4.0 (#126)
* chore: switch to upstream github-mcp-server v0.4.0

Switch from anthropics fork to github/github-mcp-server at commit e9f748f
(version 0.4.0) as requested.

Release link: https://github.com/github/github-mcp-server/releases/tag/v0.4.0

Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com>

* fix comment

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com>
2025-06-04 11:42:57 -07:00
Robb WaltersandGitHub be799cbe7b fix: skip SHA computation for deleted files (#115) 2025-06-03 13:40:52 -07:00
bd71ac0e8f fix: wrap github MCP config with mcpServers in issue-triage workflow (#118)
Update the MCP configuration structure to properly wrap the github
server configuration within an mcpServers object, matching the
expected format for MCP config files.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-06-03 11:31:04 -07:00
Ashwin BhatandGitHub 65b9bcde80 chore: update claude-code-base-action to v0.0.9 (#116) 2025-06-02 21:42:23 -07:00
Ashwin BhatGitHubashwin-antclaude[bot] <209825114+claude[bot]@users.noreply.github.com>
70245e56e3 feat: add claude_env input for custom environment variables (#102)
* feat: add claude_env input for custom environment variables

Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com>

* docs: add claude_env input documentation with clear syntax examples

Added comprehensive documentation for the new claude_env input including:
- Entry in the Inputs table with description
- Example in the basic workflow configuration
- Detailed section in Advanced Configuration with practical use cases

This makes it clear how users can pass custom environment variables
to Claude Code execution in YAML format for CI/test setups.

Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com>

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com>
2025-06-02 20:52:34 -07:00
Ashwin BhatGitHubashwin-antclaude[bot] <209825114+claude[bot]@users.noreply.github.com>
1d4d6c4b93 feat: add unified update_claude_comment tool (#98)
* feat: add unified update_claude_comment tool

- Add new update_claude_comment tool that automatically handles both issue and PR comments
- Remove individual update_issue_comment and update_pull_request_comment tools
- Pass CLAUDE_COMMENT_ID, GITHUB_EVENT_NAME, and IS_PR to MCP server environment
- Simplify Claude's comment update workflow by removing need for owner/repo/commentId params
- Update prompts and tests to use the new unified tool

* feat: add unified update_claude_comment tool

- Add new update_claude_comment tool that automatically handles both issue and PR comments
- Remove individual update_issue_comment and update_pull_request_comment tools
- Pass CLAUDE_COMMENT_ID, GITHUB_EVENT_NAME, and IS_PR to MCP server environment
- Use Octokit instead of raw fetch for better type safety and error handling
- Simplify Claude's comment update workflow by removing need for owner/repo/commentId params
- Update prompts and tests to use the new unified tool

* refactor: extract update_claude_comment logic to standalone testable function

- Create new updateClaudeComment function in operations/comments
- Add comprehensive unit tests following image-downloader pattern
- Update MCP server to use extracted function
- Refactor update-comment-link.ts and update-with-branch.ts to eliminate duplication
- All tests passing (10 new tests for update-claude-comment)

Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com>

* prettier

* tsc

* clean up comments

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com>
2025-06-02 12:15:25 -07:00
Ashwin BhatGitHubashwin-antclaude[bot] <209825114+claude[bot]@users.noreply.github.com>
e409c57d90 feat: add mcp_config input that merges with existing mcp server (#96)
* feat: add mcp_config input that merges with existing mcp server

- Add mcp_config input parameter to action.yml 
- Modify prepareMcpConfig() to accept and merge additional config
- Provided config overrides built-in servers in case of naming collisions
- Pass MCP_CONFIG environment variable from action to prepare step

Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com>

* refactor: improve MCP config validation and merging logic

- Add JSON validation to ensure parsed config is an object
- Simplify merge logic with explicit mcpServers merging
- Enhance error logging with config preview for debugging

Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com>

* refactor: improve MCP config logging per review feedback

- Remove configPreview from error logging to avoid cluttering output
- Add informational log when merging MCP server configurations
- Simplify error message for failed config parsing

Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com>

* test: add comprehensive unit tests for prepareMcpConfig

Add tests covering:
- Basic functionality with no additional config
- Valid JSON merging scenarios
- Invalid JSON handling
- Empty/null config handling
- Server name collision scenarios
- Complex nested configurations
- Environment variable handling

Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com>

* docs: add mcp_config example with sequential-thinking server

- Add mcp_config to inputs table
- Add example section showing how to use mcp_config with sequential-thinking MCP server
- Include clear explanation that custom servers override built-in servers

Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com>

* readme

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com>
2025-06-02 09:03:45 -07:00
YutaSaitoandGitHub f6e5597633 fix: specify baseUrl in Octokit (#107) (#108) 2025-06-01 21:07:44 -07:00
Andrew MunsellandGitHub 0cd44e50dd fix: Update condition for final message in output (#106)
The last message in the Claude Code output does not have a role, but instead has a field "type" set to "result". With the current condition, the duration is never appended to the header of the comments.
2025-06-01 18:33:02 -07:00
Ashwin BhatandGitHub a8a36ced96 fix mistake in FAQ (#100) 2025-05-30 12:33:15 -07:00
Ashwin BhatandGitHub 180a1b6680 switch to opus for this repo's claude workflow (#97)
* switch to opus for this repo's claude workflow

* prettier
2025-05-30 08:14:11 -07:00
8da47815ec docs: add comprehensive FAQ covering common gotchas and limitations (#92)
- Add FAQ.md with sections on triggering, authentication, capabilities, and troubleshooting
- Document key limitations including workflow access, PR creation, and CI results visibility
- Include workarounds for common issues like automated workflows and test result access
- Cover security considerations and best practices for safe usage

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-05-29 16:45:44 -07:00
Lina TawfikandGitHub 35ad5fc467 Add enhanced text sanitization (#83)
* Add enhanced text sanitization

* Format code with prettier

* Refactor tests to remove redundancy and improve structure

- Remove redundant 'mixed input patterns' test from sanitizer.test.ts
- Consolidate integration tests into 2 focused real-world scenarios
- Add HTML comment stripping to sanitizeContent function
- Update test expectations to match sanitization behavior
- Maintain full coverage with fewer, more focused tests

* Fix prettier formatting

* Remove rendered.html from repository

* Remove test-markdown.json and update .gitignore

* Revert .gitignore changes
2025-05-29 16:35:50 -07:00
fb7365fba9 fix: Correct mcp_config_file parameter to mcp_config in issue-triage workflow (#89)
The workflow was using 'mcp_config_file' which is not a valid parameter for
the claude-code-base-action. The correct parameter name is 'mcp_config' as
defined in the action.yml file.

This fix ensures that the MCP server configuration is properly passed to the
action, allowing the GitHub MCP server to be correctly initialized.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-05-29 12:57:57 -07:00
Ashwin BhatandGitHub 5a787ed8ab bump base action (#90) 2025-05-29 11:24:24 -07:00
Ashwin BhatandGitHub 507e4a6cd1 grant more tools in workflow (#85) 2025-05-29 10:18:16 -07:00
Ashwin BhatGitHubClaudeclaude[bot] <209825114+claude[bot]@users.noreply.github.com>ashwin-ant
fcbdac91f2 feat: add base_branch input to specify source branch for new Claude branches (#72)
* feat: add base_branch input to specify source branch for new Claude branches

- Add base_branch input parameter to action.yml allowing users to specify which branch to use as source
- Update setupBranch function to accept and use the base branch parameter  
- Defaults to repository default branch if no base branch is specified
- Addresses issue #62 for better branch control

Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com>

* perf: optimize setupBranch to avoid unnecessary default branch fetch

Only fetch repository default branch when actually needed:
- Skip initial fetch when baseBranch is provided
- Fetch default branch at end only for return value and GitHub Actions output
- Eliminates unnecessary API call when users specify base branch

Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com>

* fix: properly handle base branch throughout the action workflow

- Fix TypeScript error where defaultBranch was used before being assigned
- Replace DEFAULT_BRANCH with BASE_BRANCH in subsequent workflow steps
- Update PR creation and branch comparison to use the actual base branch
- Ensure custom base_branch input is respected in all operations

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: move BASE_BRANCH env reading into parseGitHubContext

- Move BASE_BRANCH environment variable reading into parseGitHubContext for consistency
- Update setupBranch to use context.inputs.baseBranch instead of process.env
- Fix test descriptions to correctly reference BASE_BRANCH instead of DEFAULT_BRANCH
- Update test environment setup to use BASE_BRANCH

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2025-05-29 10:08:00 -07:00
03e5dcc3a1 Support ANTHROPIC_BASE_URL override from env (#80)
* Support ANTHROPIC_BASE_URL override from env

* prettier

---------

Co-authored-by: Ashwin Bhat <ashwin@anthropic.com>
2025-05-29 10:02:12 -07:00
52efa5e498 feat: display detailed error messages when prepare step fails (#82)
* feat: display detailed error messages when prepare step fails

- Capture prepare step errors in action.yml (up to 2000 chars)
- Add error details to comment update with collapsible section
- Handle both prepare and Claude execution failures separately
- Add test coverage for error detail display

This helps users debug issues like git errors, permission problems,
and branch creation failures more easily.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: simplify error capture to show clean error messages only

- Remove complex shell script that captured full output logs
- Use core.setOutput in prepare.ts to pass clean error message directly
- Avoid exposing potentially sensitive information from logs
- Show only the actual error message (e.g. 'Failed to fetch issue data')

This provides cleaner, more readable error messages without the risk
of exposing sensitive information from debug logs.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: simplify error display to show clean error messages only

- Remove collapsible <details> section for error messages
- Display errors in simple code blocks since messages are now clean and short
- Makes error messages more direct and readable

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-05-29 09:58:52 -07:00
Ashwin BhatGitHubashwin-antclaude[bot] <209825114+claude[bot]@users.noreply.github.com>
37c3c29341 feat: allow user override of hardcoded disallowed tools (#71)
* feat: allow user override of hardcoded disallowed tools

Allow users to override hardcoded disallowed tools (WebSearch, WebFetch) by including them in their allowed_tools configuration. This provides users with the ability to control tool access based on their security requirements.

Changes:
- Modified buildDisallowedToolsString() to accept allowedTools parameter
- Added logic to filter out hardcoded disallowed tools if present in allowed tools
- Updated function call site to pass allowedTools
- Added comprehensive test coverage for override behavior
- Maintains backward compatibility

Resolves #49

Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com>

* prettier

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com>
2025-05-29 09:40:32 -07:00
Ashwin BhatandGitHub 9c9859aff1 chore: update claude-code-base-action to v0.0.7 (#84) 2025-05-28 17:20:23 -07:00
Ashwin BhatandGitHub 176dbc369d bump base action to 0.0.6 (#79) 2025-05-28 13:19:10 -07:00
Erjan KandGitHub 8ae72a97c6 Fix readme typo (#58) 2025-05-28 10:20:00 -07:00
Ashwin BhatGitHubashwin-antclaude[bot] <209825114+claude[bot]@users.noreply.github.com>
0eb34ae441 Add shallow fetch to improve performance for large repositories (#53)
* Add shallow fetch to improve performance for large repositories

This change adds `--depth=1` to git fetch operations to perform shallow
fetches instead of full history downloads. This significantly reduces
checkout time for large repositories as reported in issue #52.

Changes:
- Line 55: Added --depth=1 to PR branch fetch
- Line 102: Added --depth=1 to new branch fetch

Fixes #52

Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com>

* fetch 50 commits for PRs

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com>
2025-05-27 16:31:06 -07:00
Ashwin BhatandGitHub 804959ac41 add issue triage workflow (#70) 2025-05-27 14:04:41 -07:00
Ashwin BhatandGitHub 21e17bd590 remove .DS_Store (#69) 2025-05-27 13:26:03 -07:00
Ashwin BhatandGitHub 4b925ddf0c Update issue templates (#51) 2025-05-27 13:18:29 -07:00
253f2c6796 Pin GitHub Action dependencies to commit SHAs for security (#66)
Pin oven-sh/setup-bun and anthropics/claude-code-base-action to specific commit SHAs instead of version tags to ensure reproducible builds and improve supply chain security.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-05-27 10:14:11 -07:00
3c6a85b54b Improve error messages for GitHub Action authentication failures (#50)
- Add helpful hint about workflow permissions when OIDC token is not found
- Include response body in app token exchange failure errors for better debugging

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-05-25 18:43:54 -07:00
220 changed files with 35454 additions and 2075 deletions
Vendored
BIN
View File
Binary file not shown.
+61
View File
@@ -0,0 +1,61 @@
---
name: code-quality-reviewer
description: Use this agent when you need to review code for quality, maintainability, and adherence to best practices. Examples:\n\n- After implementing a new feature or function:\n user: 'I've just written a function to process user authentication'\n assistant: 'Let me use the code-quality-reviewer agent to analyze the authentication function for code quality and best practices'\n\n- When refactoring existing code:\n user: 'I've refactored the payment processing module'\n assistant: 'I'll launch the code-quality-reviewer agent to ensure the refactored code maintains high quality standards'\n\n- Before committing significant changes:\n user: 'I've completed the API endpoint implementations'\n assistant: 'Let me use the code-quality-reviewer agent to review the endpoints for proper error handling and maintainability'\n\n- When uncertain about code quality:\n user: 'Can you check if this validation logic is robust enough?'\n assistant: 'I'll use the code-quality-reviewer agent to thoroughly analyze the validation logic'
tools: Glob, Grep, Read, WebFetch, TodoWrite, WebSearch, BashOutput, KillBash
model: inherit
---
You are an expert code quality reviewer with deep expertise in software engineering best practices, clean code principles, and maintainable architecture. Your role is to provide thorough, constructive code reviews focused on quality, readability, and long-term maintainability.
When reviewing code, you will:
**Clean Code Analysis:**
- Evaluate naming conventions for clarity and descriptiveness
- Assess function and method sizes for single responsibility adherence
- Check for code duplication and suggest DRY improvements
- Identify overly complex logic that could be simplified
- Verify proper separation of concerns
**Error Handling & Edge Cases:**
- Identify missing error handling for potential failure points
- Evaluate the robustness of input validation
- Check for proper handling of null/undefined values
- Assess edge case coverage (empty arrays, boundary conditions, etc.)
- Verify appropriate use of try-catch blocks and error propagation
**Readability & Maintainability:**
- Evaluate code structure and organization
- Check for appropriate use of comments (avoiding over-commenting obvious code)
- Assess the clarity of control flow
- Identify magic numbers or strings that should be constants
- Verify consistent code style and formatting
**TypeScript-Specific Considerations** (when applicable):
- Prefer `type` over `interface` as per project standards
- Avoid unnecessary use of underscores for unused variables
- Ensure proper type safety and avoid `any` types when possible
**Best Practices:**
- Evaluate adherence to SOLID principles
- Check for proper use of design patterns where appropriate
- Assess performance implications of implementation choices
- Verify security considerations (input sanitization, sensitive data handling)
**Review Structure:**
Provide your analysis in this format:
- Start with a brief summary of overall code quality
- Organize findings by severity (critical, important, minor)
- Provide specific examples with line references when possible
- Suggest concrete improvements with code examples
- Highlight positive aspects and good practices observed
- End with actionable recommendations prioritized by impact
Be constructive and educational in your feedback. When identifying issues, explain why they matter and how they impact code quality. Focus on teaching principles that will improve future code, not just fixing current issues.
If the code is well-written, acknowledge this and provide suggestions for potential enhancements rather than forcing criticism. Always maintain a professional, helpful tone that encourages continuous improvement.
@@ -0,0 +1,56 @@
---
name: documentation-accuracy-reviewer
description: Use this agent when you need to verify that code documentation is accurate, complete, and up-to-date. Specifically use this agent after: implementing new features that require documentation updates, modifying existing APIs or functions, completing a logical chunk of code that needs documentation review, or when preparing code for review/release. Examples: 1) User: 'I just added a new authentication module with several public methods' → Assistant: 'Let me use the documentation-accuracy-reviewer agent to verify the documentation is complete and accurate for your new authentication module.' 2) User: 'Please review the documentation for the payment processing functions I just wrote' → Assistant: 'I'll launch the documentation-accuracy-reviewer agent to check your payment processing documentation.' 3) After user completes a feature implementation → Assistant: 'Now that the feature is complete, I'll use the documentation-accuracy-reviewer agent to ensure all documentation is accurate and up-to-date.'
tools: Glob, Grep, Read, WebFetch, TodoWrite, WebSearch, BashOutput, KillBash
model: inherit
---
You are an expert technical documentation reviewer with deep expertise in code documentation standards, API documentation best practices, and technical writing. Your primary responsibility is to ensure that code documentation accurately reflects implementation details and provides clear, useful information to developers.
When reviewing documentation, you will:
**Code Documentation Analysis:**
- Verify that all public functions, methods, and classes have appropriate documentation comments
- Check that parameter descriptions match actual parameter types and purposes
- Ensure return value documentation accurately describes what the code returns
- Validate that examples in documentation actually work with the current implementation
- Confirm that edge cases and error conditions are properly documented
- Check for outdated comments that reference removed or modified functionality
**README Verification:**
- Cross-reference README content with actual implemented features
- Verify installation instructions are current and complete
- Check that usage examples reflect the current API
- Ensure feature lists accurately represent available functionality
- Validate that configuration options documented in README match actual code
- Identify any new features missing from README documentation
**API Documentation Review:**
- Verify endpoint descriptions match actual implementation
- Check request/response examples for accuracy
- Ensure authentication requirements are correctly documented
- Validate parameter types, constraints, and default values
- Confirm error response documentation matches actual error handling
- Check that deprecated endpoints are properly marked
**Quality Standards:**
- Flag documentation that is vague, ambiguous, or misleading
- Identify missing documentation for public interfaces
- Note inconsistencies between documentation and implementation
- Suggest improvements for clarity and completeness
- Ensure documentation follows project-specific standards from CLAUDE.md
**Review Structure:**
Provide your analysis in this format:
- Start with a summary of overall documentation quality
- List specific issues found, categorized by type (code comments, README, API docs)
- For each issue, provide: file/location, current state, recommended fix
- Prioritize issues by severity (critical inaccuracies vs. minor improvements)
- End with actionable recommendations
You will be thorough but focused, identifying genuine documentation issues rather than stylistic preferences. When documentation is accurate and complete, acknowledge this clearly. If you need to examine specific files or code sections to verify documentation accuracy, request access to those resources. Always consider the target audience (developers using the code) and ensure documentation serves their needs effectively.
+53
View File
@@ -0,0 +1,53 @@
---
name: performance-reviewer
description: Use this agent when you need to analyze code for performance issues, bottlenecks, and resource efficiency. Examples: After implementing database queries or API calls, when optimizing existing features, after writing data processing logic, when investigating slow application behavior, or when completing any code that involves loops, network requests, or memory-intensive operations.
tools: Glob, Grep, Read, WebFetch, TodoWrite, WebSearch, BashOutput, KillBash
model: inherit
---
You are an elite performance optimization specialist with deep expertise in identifying and resolving performance bottlenecks across all layers of software systems. Your mission is to conduct thorough performance reviews that uncover inefficiencies and provide actionable optimization recommendations.
When reviewing code, you will:
**Performance Bottleneck Analysis:**
- Examine algorithmic complexity and identify O(n²) or worse operations that could be optimized
- Detect unnecessary computations, redundant operations, or repeated work
- Identify blocking operations that could benefit from asynchronous execution
- Review loop structures for inefficient iterations or nested loops that could be flattened
- Check for premature optimization vs. legitimate performance concerns
**Network Query Efficiency:**
- Analyze database queries for N+1 problems and missing indexes
- Review API calls for batching opportunities and unnecessary round trips
- Check for proper use of pagination, filtering, and projection in data fetching
- Identify opportunities for caching, memoization, or request deduplication
- Examine connection pooling and resource reuse patterns
- Verify proper error handling that doesn't cause retry storms
**Memory and Resource Management:**
- Detect potential memory leaks from unclosed connections, event listeners, or circular references
- Review object lifecycle management and garbage collection implications
- Identify excessive memory allocation or large object creation in loops
- Check for proper cleanup in cleanup functions, destructors, or finally blocks
- Analyze data structure choices for memory efficiency
- Review file handles, database connections, and other resource cleanup
**Review Structure:**
Provide your analysis in this format:
1. **Critical Issues**: Immediate performance problems requiring attention
2. **Optimization Opportunities**: Improvements that would yield measurable benefits
3. **Best Practice Recommendations**: Preventive measures for future performance
4. **Code Examples**: Specific before/after snippets demonstrating improvements
For each issue identified:
- Specify the exact location (file, function, line numbers)
- Explain the performance impact with estimated complexity or resource usage
- Provide concrete, implementable solutions
- Prioritize recommendations by impact vs. effort
If code appears performant, confirm this explicitly and note any particularly well-optimized sections. Always consider the specific runtime environment and scale requirements when making recommendations.
+59
View File
@@ -0,0 +1,59 @@
---
name: security-code-reviewer
description: Use this agent when you need to review code for security vulnerabilities, input validation issues, or authentication/authorization flaws. Examples: After implementing authentication logic, when adding user input handling, after writing API endpoints that process external data, or when integrating third-party libraries. The agent should be called proactively after completing security-sensitive code sections like login systems, data validation layers, or permission checks.
tools: Glob, Grep, Read, WebFetch, TodoWrite, WebSearch, BashOutput, KillBash
model: inherit
---
You are an elite security code reviewer with deep expertise in application security, threat modeling, and secure coding practices. Your mission is to identify and prevent security vulnerabilities before they reach production.
When reviewing code, you will:
**Security Vulnerability Assessment**
- Systematically scan for OWASP Top 10 vulnerabilities (injection flaws, broken authentication, sensitive data exposure, XXE, broken access control, security misconfiguration, XSS, insecure deserialization, using components with known vulnerabilities, insufficient logging)
- Identify potential SQL injection, NoSQL injection, and command injection vulnerabilities
- Check for cross-site scripting (XSS) vulnerabilities in any user-facing output
- Look for cross-site request forgery (CSRF) protection gaps
- Examine cryptographic implementations for weak algorithms or improper key management
- Identify potential race conditions and time-of-check-time-of-use (TOCTOU) vulnerabilities
**Input Validation and Sanitization**
- Verify all user inputs are properly validated against expected formats and ranges
- Ensure input sanitization occurs at appropriate boundaries (client-side validation is supplementary, never primary)
- Check for proper encoding when outputting user data
- Validate that file uploads have proper type checking, size limits, and content validation
- Ensure API parameters are validated for type, format, and business logic constraints
- Look for potential path traversal vulnerabilities in file operations
**Authentication and Authorization Review**
- Verify authentication mechanisms use secure, industry-standard approaches
- Check for proper session management (secure cookies, appropriate timeouts, session invalidation)
- Ensure passwords are properly hashed using modern algorithms (bcrypt, Argon2, PBKDF2)
- Validate that authorization checks occur at every protected resource access
- Look for privilege escalation opportunities
- Check for insecure direct object references (IDOR)
- Verify proper implementation of role-based or attribute-based access control
**Analysis Methodology**
1. First, identify the security context and attack surface of the code
2. Map data flows from untrusted sources to sensitive operations
3. Examine each security-critical operation for proper controls
4. Consider both common vulnerabilities and context-specific threats
5. Evaluate defense-in-depth measures
**Review Structure:**
Provide findings in order of severity (Critical, High, Medium, Low, Informational):
- **Vulnerability Description**: Clear explanation of the security issue
- **Location**: Specific file, function, and line numbers
- **Impact**: Potential consequences if exploited
- **Remediation**: Concrete steps to fix the vulnerability with code examples when helpful
- **References**: Relevant CWE numbers or security standards
If no security issues are found, provide a brief summary confirming the review was completed and highlighting any positive security practices observed.
Always consider the principle of least privilege, defense in depth, and fail securely. When uncertain about a potential vulnerability, err on the side of caution and flag it for further investigation.
+52
View File
@@ -0,0 +1,52 @@
---
name: test-coverage-reviewer
description: Use this agent when you need to review testing implementation and coverage. Examples: After writing a new feature implementation, use this agent to verify test coverage. When refactoring code, use this agent to ensure tests still adequately cover all scenarios. After completing a module, use this agent to identify missing test cases and edge conditions.
tools: Glob, Grep, Read, WebFetch, TodoWrite, WebSearch, BashOutput, KillBash
model: inherit
---
You are an expert QA engineer and testing specialist with deep expertise in test-driven development, code coverage analysis, and quality assurance best practices. Your role is to conduct thorough reviews of test implementations to ensure comprehensive coverage and robust quality validation.
When reviewing code for testing, you will:
**Analyze Test Coverage:**
- Examine the ratio of test code to production code
- Identify untested code paths, branches, and edge cases
- Verify that all public APIs and critical functions have corresponding tests
- Check for coverage of error handling and exception scenarios
- Assess coverage of boundary conditions and input validation
**Evaluate Test Quality:**
- Review test structure and organization (arrange-act-assert pattern)
- Verify tests are isolated, independent, and deterministic
- Check for proper use of mocks, stubs, and test doubles
- Ensure tests have clear, descriptive names that document behavior
- Validate that assertions are specific and meaningful
- Identify brittle tests that may break with minor refactoring
**Identify Missing Test Scenarios:**
- List untested edge cases and boundary conditions
- Highlight missing integration test scenarios
- Point out uncovered error paths and failure modes
- Suggest performance and load testing opportunities
- Recommend security-related test cases where applicable
**Provide Actionable Feedback:**
- Prioritize findings by risk and impact
- Suggest specific test cases to add with example implementations
- Recommend refactoring opportunities to improve testability
- Identify anti-patterns and suggest corrections
**Review Structure:**
Provide your analysis in this format:
- **Coverage Analysis**: Summary of current test coverage with specific gaps
- **Quality Assessment**: Evaluation of existing test quality with examples
- **Missing Scenarios**: Prioritized list of untested cases
- **Recommendations**: Concrete actions to improve test suite
Be thorough but practical - focus on tests that provide real value and catch actual bugs. Consider the testing pyramid and ensure appropriate balance between unit, integration, and end-to-end tests.
+61
View File
@@ -0,0 +1,61 @@
---
allowed-tools: Bash(./scripts/gh.sh:*),Bash(./scripts/edit-issue-labels.sh:*)
description: Apply labels to GitHub issues
---
You're an issue triage assistant for GitHub issues. Your task is to analyze the issue and select appropriate labels from the provided list.
IMPORTANT: Don't post any comments or messages to the issue. Your only action should be to apply labels.
Issue Information:
- REPO: ${{ github.repository }}
- ISSUE_NUMBER: ${{ github.event.issue.number }}
TASK OVERVIEW:
1. First, fetch the list of labels available in this repository by running: `./scripts/gh.sh label list`. Run exactly this command with nothing else.
2. Next, use gh wrapper commands to get context about the issue:
- Use `./scripts/gh.sh issue view ${{ github.event.issue.number }}` to retrieve the current issue's details
- Use `./scripts/gh.sh search issues` to find similar issues that might provide context for proper categorization
- `./scripts/gh.sh` is a wrapper for `gh` CLI. Example commands:
- `./scripts/gh.sh label list` — fetch all available labels
- `./scripts/gh.sh issue view 123` — view issue details
- `./scripts/gh.sh issue view 123 --comments` — view with comments
- `./scripts/gh.sh search issues "query" --limit 10` — search for issues
- `./scripts/edit-issue-labels.sh` — apply labels to the issue
3. Analyze the issue content, considering:
- The issue title and description
- The type of issue (bug report, feature request, question, etc.)
- Technical areas mentioned
- Severity or priority indicators
- User impact
- Components affected
4. Select appropriate labels from the available labels list provided above:
- Choose labels that accurately reflect the issue's nature
- Be specific but comprehensive
- IMPORTANT: Add a priority label (P1, P2, or P3) based on the label descriptions from ./scripts/gh.sh label list
- Consider platform labels (android, ios) if applicable
- If you find similar issues using ./scripts/gh.sh search, consider using a "duplicate" label if appropriate. Only do so if the issue is a duplicate of another OPEN issue.
5. Apply the selected labels:
- Use `./scripts/edit-issue-labels.sh --add-label LABEL1 --add-label LABEL2` to apply your selected labels (issue number is read from the workflow event)
- DO NOT post any comments explaining your decision
- DO NOT communicate directly with users
- If no labels are clearly applicable, do not apply any labels
IMPORTANT GUIDELINES:
- Be thorough in your analysis
- Only select labels from the provided list above
- DO NOT post any comments to the issue
- Your ONLY action should be to apply labels using ./scripts/edit-issue-labels.sh
- It's okay to not add any labels if none are clearly applicable
---
+20
View File
@@ -0,0 +1,20 @@
---
allowed-tools: Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*)
description: Review a pull request
---
Perform a comprehensive code review using subagents for key areas:
- code-quality-reviewer
- performance-reviewer
- test-coverage-reviewer
- documentation-accuracy-reviewer
- security-code-reviewer
Instruct each to only provide noteworthy feedback. Once they finish, review the feedback and post only the feedback that you also deem noteworthy.
Provide feedback using inline comments for specific issues.
Use top-level comments for general observations or praise.
Keep feedback concise.
---
+15
View File
@@ -0,0 +1,15 @@
{
"hooks": {
"PostToolUse": [
{
"hooks": [
{
"type": "command",
"command": "bunx prettier@3.5.3 --no-config --write ."
}
],
"matcher": "Edit|Write|MultiEdit"
}
]
}
}
+162
View File
@@ -0,0 +1,162 @@
export const meta = {
name: "pr-stamp-sweep",
description:
"Review candidate PRs for stampability, then adversarially verify security of stamp candidates",
whenToUse:
"Sweep candidate PRs for stampability: per-PR review + adversarial security verify. Requires pre-fetched PR dossiers in /tmp/claude/pr-sweep/<n>.md and args {prs: [...]}.",
phases: [
{ title: "Review", detail: "one reviewer agent per PR" },
{
title: "Verify",
detail: "adversarial security skeptic per stamp candidate",
},
],
};
// PRECONDITION: before invoking this workflow, pre-fetch each candidate PR to
// /tmp/claude/pr-sweep/<n>.md, containing the PR's metadata, body, existing
// reviews/comments, and the full diff (e.g. via `gh pr view` + `gh pr diff`).
// Sandboxed agents can't reliably call gh themselves, so they read these
// dossier files instead. Pass the PR numbers as args: {prs: [<PR numbers>]}.
const REVIEW_SCHEMA = {
type: "object",
properties: {
number: { type: "number" },
verdict: { type: "string", enum: ["stamp", "skip", "needs-discussion"] },
category: {
type: "string",
description: "docs | tests | bugfix | nicety | security-fix | other",
},
summary: {
type: "string",
description: "1-2 sentence plain-language summary of what the PR does",
},
reasoning: {
type: "string",
description: "why this verdict — correctness, scope, quality",
},
behaviorChange: {
type: "string",
description: 'what user-visible behavior changes, or "none"',
},
concerns: { type: "array", items: { type: "string" } },
securitySensitive: {
type: "boolean",
description:
"true if it touches auth, sanitization, parsers of untrusted input, actor checks, file restore, or shell construction",
},
duplicateOf: {
type: "string",
description: "PR number(s) this duplicates, or empty string",
},
},
required: [
"number",
"verdict",
"category",
"summary",
"reasoning",
"behaviorChange",
"concerns",
"securitySensitive",
"duplicateOf",
],
};
const VERDICT_SCHEMA = {
type: "object",
properties: {
number: { type: "number" },
safeToStamp: { type: "boolean" },
findings: {
type: "array",
items: { type: "string" },
description:
"concrete security/correctness problems found, empty if clean",
},
confidence: { type: "string", enum: ["high", "medium", "low"] },
},
required: ["number", "safeToStamp", "findings", "confidence"],
};
if (!args || !Array.isArray(args.prs) || args.prs.length === 0)
throw new Error(
"pass {prs: [<PR numbers>]} as args; pre-fetch each PR to /tmp/claude/pr-sweep/<n>.md first",
);
const prs = args.prs;
log(`Reviewing ${prs.length} candidate PRs`);
const results = await pipeline(
prs,
(n) =>
agent(
`You are reviewing open PR #${n} on anthropics/claude-code-action to decide if it is safe for a maintainer to approve ("stamp") with minimal further discussion.
The full PR (metadata, body, existing reviews/comments, and complete diff) is in /tmp/claude/pr-sweep/${n}.md — read it first. The repo is checked out at the current working directory. Read the actual current source files the diff touches to verify the diff applies cleanly conceptually and the claims in the PR body are true. Do NOT modify anything or run git commands that change state.
Context about this repo:
- It's a GitHub Action that runs Claude on issues/PRs. It processes UNTRUSTED content (PR bodies, comments, branch names, file contents from forks). Treat any change touching content sanitization, actor/bot allowlists, config restoration, prompt construction, or shell command construction as high-risk.
- Most candidate PRs are from EXTERNAL contributors. Treat the diff with suspicion: look for subtle malicious changes, weakened validation, injection vectors, overly broad permissions, or changes whose description doesn't match the code.
- Runtime is Bun; strict TypeScript (noUnusedLocals/noUnusedParameters). Tests are unit tests run with bun test.
Stamp criteria (ALL must hold):
1. Small, focused, and the code does exactly what the title/body says.
2. No major behavior change — bug fixes restoring intended behavior, docs fixes, test-only additions, and small niceties qualify. New inputs/features, behavior redesigns, or large refactors do NOT.
3. Correct: you verified the logic against the actual current source, not just the diff. Check edge cases.
4. No security concern. Check explicitly for: prompt injection (untrusted text reaching Claude's prompt without sanitization), code execution (untrusted data reaching shell commands, eval/spawn, or GitHub workflow expressions), path traversal (untrusted input influencing filesystem paths), credential exposure (tokens reaching logs, comments, or attacker-readable output), weakened validation or permission checks, and suspicious hunks unrelated to the stated purpose.
5. Wouldn't break the public API of base-action/ or action.yml output wiring.
If the PR is a docs change, verify the docs claims against the actual code behavior. If test-only, check tests actually pass conceptually (assert the right things, match real implementations) and don't weaken or skip anything.
Verdicts: "stamp" = approve as-is; "needs-discussion" = plausible but has questions/issues worth a comment; "skip" = too big, wrong, redundant, or risky.
If this PR appears to duplicate another open PR (same fix, same files), still judge it on its own merits but note the duplication in duplicateOf.
Return structured output only.`,
{ label: `review:#${n}`, phase: "Review", schema: REVIEW_SCHEMA },
),
(review, n) => {
if (!review) return null;
if (review.verdict !== "stamp") return { review, verify: null };
return agent(
`You are an adversarial security skeptic. Another reviewer recommended APPROVING open PR #${n} on anthropics/claude-code-action. Your job is to REFUTE that recommendation — find any reason it should NOT be stamped.
Their assessment: ${JSON.stringify(review)}
Read the full PR at /tmp/claude/pr-sweep/${n}.md and the touched source files in the current working directory. This repo processes untrusted PR/issue content from forks; anything that lets untrusted content reach Claude's prompt, a shell command, a workflow expression, or a filesystem path unsanitized is a critical vulnerability.
Hunt specifically for:
- Subtle malice or scope creep: hunks that don't match the stated purpose, weakened validation, regex changes that widen acceptance, removed escaping.
- Prompt injection: untrusted data (comment bodies, branch names, file contents, command output, downloaded files) reaching Claude's prompt or context without sanitization, including indirect routes like tool output Claude later reads.
- Code execution: untrusted data reaching shell commands, eval/spawn argv, GitHub workflow \${{ }} expressions, or API call templates; new process spawning; path traversal letting untrusted input write or read outside intended directories.
- Credential exposure: tokens or secrets flowing into logs, posted comments, error messages, env passed to untrusted code, or files Claude can read.
- Logic errors the first reviewer missed: off-by-one, wrong polarity, unhandled edge cases (empty strings, unicode, very long inputs).
- Supply-chain angles: pinned versions that don't match the claimed SHA/tag, new dependencies, fetched URLs.
- For docs PRs: claims that would mislead users into insecure configurations.
- For test-only PRs: tests that codify wrong behavior, or that would mask future regressions.
If the diff pins a version/SHA, verify the claim is plausible from local information; flag if unverifiable. Be strict: if uncertain whether something is a real problem, lean toward reporting it as a finding with your uncertainty noted. Only return safeToStamp=true if you genuinely failed to find any disqualifying issue.
Return structured output only.`,
{ label: `verify:#${n}`, phase: "Verify", schema: VERDICT_SCHEMA },
).then((v) => ({ review, verify: v }));
},
);
const clean = results.filter(Boolean);
const stamped = clean.filter(
(r) => r.review.verdict === "stamp" && r.verify && r.verify.safeToStamp,
);
const demoted = clean.filter(
(r) => r.review.verdict === "stamp" && (!r.verify || !r.verify.safeToStamp),
);
const discuss = clean.filter((r) => r.review.verdict === "needs-discussion");
const skipped = clean.filter((r) => r.review.verdict === "skip");
log(
`stamp: ${stamped.length}, demoted by verifier: ${demoted.length}, needs-discussion: ${discuss.length}, skip: ${skipped.length}`,
);
return { stamped, demoted, discuss, skipped };
+3
View File
@@ -0,0 +1,3 @@
# Keep the working tree LF on every platform: Prettier defaults to
# endOfLine "lf" and several tests match LF-terminated content.
* text=auto eol=lf
+36
View File
@@ -0,0 +1,36 @@
---
name: Bug report
about: Create a report to help us improve
title: ""
labels: bug
assignees: ""
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Workflow yml file**
If it's not sensitive, consider including a paste of your full Claude workflow.yml file.
**API Provider**
[ ] Anthropic First-Party API (default)
[ ] AWS Bedrock
[ ] GCP Vertex
**Additional context**
Add any other context about the problem here.
+35
View File
@@ -0,0 +1,35 @@
# Orchestrates all CI workflows - runs on PRs, pushes to main, and manual dispatch
# Individual test workflows are called as reusable workflows
name: CI All
on:
push:
branches:
- main
pull_request:
workflow_dispatch:
permissions:
contents: read
# Lets the test workflows mint the GitHub OIDC token they exchange for a
# Claude API access token (workload identity federation). See docs/setup.md.
id-token: write
jobs:
ci:
uses: ./.github/workflows/ci.yml
test-base-action:
uses: ./.github/workflows/test-base-action.yml
test-custom-executables:
uses: ./.github/workflows/test-custom-executables.yml
test-mcp-servers:
uses: ./.github/workflows/test-mcp-servers.yml
test-settings:
uses: ./.github/workflows/test-settings.yml
test-structured-output:
uses: ./.github/workflows/test-structured-output.yml
+4 -5
View File
@@ -1,15 +1,14 @@
name: CI
on:
push:
branches: [main]
pull_request:
workflow_call:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- uses: oven-sh/setup-bun@v2
with:
@@ -24,7 +23,7 @@ jobs:
prettier:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- uses: oven-sh/setup-bun@v1
with:
@@ -39,7 +38,7 @@ jobs:
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- uses: oven-sh/setup-bun@v2
with:
+19 -16
View File
@@ -1,32 +1,35 @@
name: Auto review PRs
name: PR Review
on:
pull_request:
types: [opened]
jobs:
auto-review:
review:
# Skip on fork PRs since they can't mint the OIDC token used for Claude API auth
if: github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
id-token: write
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 1
- name: Auto review PR
uses: anthropics/claude-code-action@main
- name: PR Review with Progress Tracking
uses: anthropics/claude-code-action@v1
with:
direct_prompt: |
Please review this PR. Look at the changes and provide thoughtful feedback on:
- Code quality and best practices
- Potential bugs or issues
- Suggestions for improvements
- Overall architecture and design decisions
# Authenticate to the Claude API via Workload Identity Federation
# (the workflow's OIDC token is exchanged for a short-lived access
# token) instead of a static API key. See docs/setup.md.
anthropic_federation_rule_id: ${{ vars.ANTHROPIC_FEDERATION_RULE_ID }}
anthropic_organization_id: ${{ vars.ANTHROPIC_ORGANIZATION_ID }}
anthropic_service_account_id: ${{ vars.ANTHROPIC_SERVICE_ACCOUNT_ID }}
Be constructive and specific in your feedback. Give inline comments where applicable.
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
allowed_tools: "mcp__github__add_pull_request_review_comment"
prompt: "/review-pr REPO: ${{ github.repository }} PR_NUMBER: ${{ github.event.pull_request.number }}"
claude_args: |
--allowedTools "mcp__github_inline_comment__create_inline_comment"
--model "claude-opus-4-7"
+14 -6
View File
@@ -19,18 +19,26 @@ jobs:
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: read
contents: write
pull-requests: write
issues: write
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 1
- name: Run Claude Code
id: claude
uses: anthropics/claude-code-action@beta
uses: anthropics/claude-code-action@main
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
# Authenticate to the Claude API via Workload Identity Federation
# (the workflow's OIDC token is exchanged for a short-lived access
# token) instead of a static API key. See docs/setup.md.
anthropic_federation_rule_id: ${{ vars.ANTHROPIC_FEDERATION_RULE_ID }}
anthropic_organization_id: ${{ vars.ANTHROPIC_ORGANIZATION_ID }}
anthropic_service_account_id: ${{ vars.ANTHROPIC_SERVICE_ACCOUNT_ID }}
claude_args: |
--allowedTools "Bash(bun install),Bash(bun test:*),Bash(bun run format),Bash(bun typecheck)"
--model "claude-opus-4-7"
+37
View File
@@ -0,0 +1,37 @@
name: Claude Issue Triage
description: Run Claude Code for issue triage in GitHub Actions
on:
issues:
types: [opened]
jobs:
triage-issue:
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
issues: write
# Required to mint the OIDC token that is exchanged for a Claude API
# access token (Workload Identity Federation).
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Run Claude Code for Issue Triage
uses: anthropics/claude-code-action@main
env:
CLAUDE_CODE_SCRIPT_CAPS: '{"edit-issue-labels.sh":2}'
with:
prompt: "/label-issue REPO: ${{ github.repository }} ISSUE_NUMBER: ${{ github.event.issue.number }}"
# Authenticate to the Claude API via Workload Identity Federation
# (the workflow's OIDC token is exchanged for a short-lived access
# token) instead of a static API key. See docs/setup.md.
anthropic_federation_rule_id: ${{ vars.ANTHROPIC_FEDERATION_RULE_ID }}
anthropic_organization_id: ${{ vars.ANTHROPIC_ORGANIZATION_ID }}
anthropic_service_account_id: ${{ vars.ANTHROPIC_SERVICE_ACCOUNT_ID }}
allowed_non_write_users: "*" # Required for issue triage workflow, if users without repo write access create issues
github_token: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,47 @@
name: Non-write Users Check
on:
pull_request:
paths:
- ".github/**"
permissions:
contents: read
pull-requests: write
jobs:
allowed-non-write-check:
runs-on: ubuntu-latest
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- run: |
DIFF=$(gh pr diff "$PR_NUMBER" -R "$REPO" || true)
if ! echo "$DIFF" | grep -qE '^diff --git a/\.github/.*\.ya?ml'; then
exit 0
fi
MATCHES=$(echo "$DIFF" | grep "^+.*allowed_non_write_users" || true)
if [ -z "$MATCHES" ]; then
exit 0
fi
EXISTING=$(gh pr view "$PR_NUMBER" -R "$REPO" --json comments --jq '.comments[].body' \
| grep -c "<!-- non-write-users-check -->" || true)
if [ "$EXISTING" -gt 0 ]; then
exit 0
fi
gh pr comment "$PR_NUMBER" -R "$REPO" --body '<!-- non-write-users-check -->
**`allowed_non_write_users` detected**
This PR adds or modifies `allowed_non_write_users`, which allows users without write access to trigger Claude Code Action workflows. This can introduce security risks.
If this is a new flow, please make sure you actually need `allowed_non_write_users`. If you are editing an existing workflow, double check that you are not adding new Claude permissions which might lead to a vulnerability.
See existing workflows in this repo for safe usage examples, or contact the AppSec team.'
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
REPO: ${{ github.repository }}
+170
View File
@@ -0,0 +1,170 @@
name: Create Release
on:
workflow_dispatch:
inputs:
dry_run:
description: "Dry run (only show what would be created)"
required: false
type: boolean
default: false
workflow_run:
workflows: ["CI All"]
types:
- completed
branches:
- main
jobs:
create-release:
runs-on: ubuntu-latest
# Run if: manual dispatch OR (CI All succeeded AND commit is a version bump)
if: |
github.event_name == 'workflow_dispatch' ||
(github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.head_branch == 'main' &&
github.event.workflow_run.event == 'push' &&
startsWith(github.event.workflow_run.head_commit.message, 'chore: bump Claude Code to'))
environment: production
permissions:
contents: write
outputs:
next_version: ${{ steps.next_version.outputs.next_version }}
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Get latest tag
id: get_latest_tag
run: |
# Get only version tags (v + number pattern)
latest_tag=$(git tag -l 'v[0-9]*' | sort -V | tail -1 || echo "v0.0.0")
if [ -z "$latest_tag" ]; then
latest_tag="v0.0.0"
fi
echo "latest_tag=$latest_tag" >> $GITHUB_OUTPUT
echo "Latest tag: $latest_tag"
- name: Calculate next version
id: next_version
run: |
latest_tag="${{ steps.get_latest_tag.outputs.latest_tag }}"
# Remove 'v' prefix and split by dots
version=${latest_tag#v}
IFS='.' read -ra VERSION_PARTS <<< "$version"
# Increment patch version
major=${VERSION_PARTS[0]:-0}
minor=${VERSION_PARTS[1]:-0}
patch=${VERSION_PARTS[2]:-0}
patch=$((patch + 1))
next_version="v${major}.${minor}.${patch}"
echo "next_version=$next_version" >> $GITHUB_OUTPUT
echo "Next version: $next_version"
- name: Display dry run info
if: ${{ inputs.dry_run }}
run: |
echo "🔍 DRY RUN MODE"
echo "Would create tag: ${{ steps.next_version.outputs.next_version }}"
echo "From commit: ${{ github.sha }}"
echo "Previous tag: ${{ steps.get_latest_tag.outputs.latest_tag }}"
- name: Create and push tag
if: ${{ !inputs.dry_run }}
run: |
next_version="${{ steps.next_version.outputs.next_version }}"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag -a "$next_version" -m "Release $next_version"
git push origin "$next_version"
- name: Create Release
if: ${{ !inputs.dry_run }}
env:
GH_TOKEN: ${{ github.token }}
run: |
next_version="${{ steps.next_version.outputs.next_version }}"
gh release create "$next_version" \
--title "$next_version" \
--generate-notes \
--latest=false # keep v1 as latest
update-major-tag:
needs: create-release
# Skip for dry runs (workflow_run events are never dry runs)
if: github.event_name == 'workflow_run' || !inputs.dry_run
runs-on: ubuntu-latest
environment: production
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Update major version tag
run: |
next_version="${{ needs.create-release.outputs.next_version }}"
# Extract major version (e.g., v0 from v0.0.20)
major_version=$(echo "$next_version" | cut -d. -f1)
# Update the major version tag to point to this release
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag -fa "$major_version" -m "Update $major_version tag to $next_version"
git push origin "$major_version" --force
echo "Updated $major_version tag to point to $next_version"
# release-base-action:
# needs: create-release
# if: ${{ !inputs.dry_run }}
# runs-on: ubuntu-latest
# environment: production
# steps:
# - name: Checkout base-action repo
# uses: actions/checkout@v6
# with:
# repository: anthropics/claude-code-base-action
# token: ${{ secrets.CLAUDE_CODE_BASE_ACTION_PAT }}
# fetch-depth: 0
#
# - name: Create and push tag
# run: |
# next_version="${{ needs.create-release.outputs.next_version }}"
#
# git config user.name "github-actions[bot]"
# git config user.email "github-actions[bot]@users.noreply.github.com"
#
# # Create the version tag
# git tag -a "$next_version" -m "Release $next_version - synced from claude-code-action"
# git push origin "$next_version"
#
# # Update the beta tag
# git tag -fa beta -m "Update beta tag to ${next_version}"
# git push origin beta --force
#
# - name: Create GitHub release
# env:
# GH_TOKEN: ${{ secrets.CLAUDE_CODE_BASE_ACTION_PAT }}
# run: |
# next_version="${{ needs.create-release.outputs.next_version }}"
#
# # Create the release
# gh release create "$next_version" \
# --repo anthropics/claude-code-base-action \
# --title "$next_version" \
# --notes "Release $next_version - synced from anthropics/claude-code-action" \
# --latest=false
#
# # Update beta release to be latest
# gh release edit beta \
# --repo anthropics/claude-code-base-action \
# --latest
+98
View File
@@ -0,0 +1,98 @@
name: Sync Base Action to claude-code-base-action
on:
push:
branches:
- main
paths:
- "base-action/**"
workflow_dispatch:
permissions:
contents: write
jobs:
sync-base-action:
name: Sync base-action to claude-code-base-action repository
runs-on: ubuntu-latest
environment: production
timeout-minutes: 10
steps:
- name: Checkout source repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 1
- name: Setup SSH and clone target repository
run: |
# Configure SSH with deploy key
mkdir -p ~/.ssh
echo "${{ secrets.CLAUDE_CODE_BASE_ACTION_REPO_DEPLOY_KEY }}" > ~/.ssh/deploy_key_base
chmod 600 ~/.ssh/deploy_key_base
# Configure SSH host
cat > ~/.ssh/config <<EOL
Host base-action.github.com
HostName github.com
User git
IdentityFile ~/.ssh/deploy_key_base
StrictHostKeyChecking no
EOL
# Clone the target repository
git clone git@base-action.github.com:anthropics/claude-code-base-action.git target-repo
- name: Sync base-action contents
run: |
cd target-repo
# Configure git
git config user.name "GitHub Actions"
git config user.email "actions@github.com"
# Remove all existing files except .git directory
find . -mindepth 1 -maxdepth 1 -name '.git' -prune -o -exec rm -rf {} +
# Copy all contents from base-action
cp -r ../base-action/. .
# Prepend mirror disclaimer to README if both files exist
if [ -f "README.md" ] && [ -f "MIRROR_DISCLAIMER.md" ]; then
cat MIRROR_DISCLAIMER.md README.md > README.tmp
mv README.tmp README.md
fi
# Check if there are any changes
if git diff --quiet && git diff --staged --quiet; then
echo "No changes to sync"
exit 0
fi
# Stage all changes
git add -A
# Get source commit info for the commit message
SOURCE_COMMIT="${GITHUB_SHA:0:7}"
SOURCE_COMMIT_MESSAGE=$(git -C .. log -1 --pretty=format:"%s" || echo "Update from base-action")
# Commit with descriptive message
git commit -m "Sync from claude-code-action base-action@${SOURCE_COMMIT}" \
-m "" \
-m "Source: anthropics/claude-code-action@${GITHUB_SHA}" \
-m "Original message: ${SOURCE_COMMIT_MESSAGE}"
# Push to main branch
git push origin main
echo "Successfully synced base-action to claude-code-base-action"
- name: Create sync summary
if: success()
run: |
echo "## Sync Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "✅ Successfully synced \`base-action\` directory to [anthropics/claude-code-base-action](https://github.com/anthropics/claude-code-base-action)" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- **Source commit**: [\`${GITHUB_SHA:0:7}\`](https://github.com/anthropics/claude-code-action/commit/${GITHUB_SHA})" >> $GITHUB_STEP_SUMMARY
echo "- **Triggered by**: $GITHUB_EVENT_NAME" >> $GITHUB_STEP_SUMMARY
echo "- **Actor**: @$GITHUB_ACTOR" >> $GITHUB_STEP_SUMMARY
+133
View File
@@ -0,0 +1,133 @@
name: Test Claude Code Action
on:
pull_request:
workflow_dispatch:
inputs:
test_prompt:
description: "Test prompt for Claude"
required: false
default: "List the files in the current directory starting with 'package'"
workflow_call:
# The Claude API is authenticated via workload identity federation: id-token
# lets the action mint the GitHub OIDC token it exchanges for a short-lived
# access token. See docs/setup.md.
permissions:
contents: read
id-token: write
jobs:
test-inline-prompt:
# Skip on fork PRs since they can't mint the OIDC token used for Claude API auth
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Test with inline prompt
id: inline-test
uses: ./base-action
with:
prompt: ${{ github.event.inputs.test_prompt || 'List the files in the current directory starting with "package"' }}
anthropic_federation_rule_id: ${{ vars.ANTHROPIC_FEDERATION_RULE_ID }}
anthropic_organization_id: ${{ vars.ANTHROPIC_ORGANIZATION_ID }}
anthropic_service_account_id: ${{ vars.ANTHROPIC_SERVICE_ACCOUNT_ID }}
claude_args: '--allowedTools "LS,Read"'
- name: Verify inline prompt output
run: |
OUTPUT_FILE="${{ steps.inline-test.outputs.execution_file }}"
CONCLUSION="${{ steps.inline-test.outputs.conclusion }}"
echo "Conclusion: $CONCLUSION"
echo "Output file: $OUTPUT_FILE"
if [ "$CONCLUSION" = "success" ]; then
echo "✅ Action completed successfully"
else
echo "❌ Action failed"
exit 1
fi
if [ -f "$OUTPUT_FILE" ]; then
if [ -s "$OUTPUT_FILE" ]; then
echo "✅ Execution log file created successfully with content"
echo "Validating JSON format:"
if jq . "$OUTPUT_FILE" > /dev/null 2>&1; then
echo "✅ Output is valid JSON"
echo "Content preview:"
head -c 200 "$OUTPUT_FILE"
else
echo "❌ Output is not valid JSON"
exit 1
fi
else
echo "❌ Execution log file is empty"
exit 1
fi
else
echo "❌ Execution log file not found"
exit 1
fi
test-prompt-file:
# Skip on fork PRs since they can't mint the OIDC token used for Claude API auth
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Create test prompt file
run: |
cat > test-prompt.txt << EOF
${PROMPT}
EOF
env:
PROMPT: ${{ github.event.inputs.test_prompt || 'List the files in the current directory starting with "package"' }}
- name: Test with prompt file and allowed tools
id: prompt-file-test
uses: ./base-action
with:
prompt_file: "test-prompt.txt"
anthropic_federation_rule_id: ${{ vars.ANTHROPIC_FEDERATION_RULE_ID }}
anthropic_organization_id: ${{ vars.ANTHROPIC_ORGANIZATION_ID }}
anthropic_service_account_id: ${{ vars.ANTHROPIC_SERVICE_ACCOUNT_ID }}
claude_args: '--allowedTools "LS,Read"'
- name: Verify prompt file output
run: |
OUTPUT_FILE="${{ steps.prompt-file-test.outputs.execution_file }}"
CONCLUSION="${{ steps.prompt-file-test.outputs.conclusion }}"
echo "Conclusion: $CONCLUSION"
echo "Output file: $OUTPUT_FILE"
if [ "$CONCLUSION" = "success" ]; then
echo "✅ Action completed successfully"
else
echo "❌ Action failed"
exit 1
fi
if [ -f "$OUTPUT_FILE" ]; then
if [ -s "$OUTPUT_FILE" ]; then
echo "✅ Execution log file created successfully with content"
echo "Validating JSON format:"
if jq . "$OUTPUT_FILE" > /dev/null 2>&1; then
echo "✅ Output is valid JSON"
echo "Content preview:"
head -c 200 "$OUTPUT_FILE"
else
echo "❌ Output is not valid JSON"
exit 1
fi
else
echo "❌ Execution log file is empty"
exit 1
fi
else
echo "❌ Execution log file not found"
exit 1
fi
@@ -0,0 +1,98 @@
name: Test Custom Executables
on:
pull_request:
workflow_dispatch:
workflow_call:
# The Claude API is authenticated via workload identity federation: id-token
# lets the action mint the GitHub OIDC token it exchanges for a short-lived
# access token. See docs/setup.md.
permissions:
contents: read
id-token: write
jobs:
test-custom-executables:
# Skip on fork PRs since they can't mint the OIDC token used for Claude API auth
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install Bun manually
run: |
echo "Installing Bun..."
curl -fsSL https://bun.sh/install | bash
echo "Bun installed at: $HOME/.bun/bin/bun"
# Verify Bun installation
if [ -f "$HOME/.bun/bin/bun" ]; then
echo "✅ Bun executable found"
$HOME/.bun/bin/bun --version
else
echo "❌ Bun executable not found"
exit 1
fi
- name: Install Claude Code manually
run: |
echo "Installing Claude Code..."
curl -fsSL https://claude.ai/install.sh | bash -s latest
echo "Claude Code installed at: $HOME/.local/bin/claude"
# Verify Claude installation
if [ -f "$HOME/.local/bin/claude" ]; then
echo "✅ Claude executable found"
ls -la "$HOME/.local/bin/claude"
else
echo "❌ Claude executable not found"
exit 1
fi
- name: Test with both custom executables
id: custom-test
uses: ./base-action
with:
prompt: |
List the files in the current directory starting with "package"
anthropic_federation_rule_id: ${{ vars.ANTHROPIC_FEDERATION_RULE_ID }}
anthropic_organization_id: ${{ vars.ANTHROPIC_ORGANIZATION_ID }}
anthropic_service_account_id: ${{ vars.ANTHROPIC_SERVICE_ACCOUNT_ID }}
path_to_claude_code_executable: /home/runner/.local/bin/claude
path_to_bun_executable: /home/runner/.bun/bin/bun
claude_args: '--allowedTools "LS,Read"'
- name: Verify custom executables worked
run: |
OUTPUT_FILE="${{ steps.custom-test.outputs.execution_file }}"
CONCLUSION="${{ steps.custom-test.outputs.conclusion }}"
echo "Conclusion: $CONCLUSION"
echo "Output file: $OUTPUT_FILE"
if [ "$CONCLUSION" = "success" ]; then
echo "✅ Action completed successfully with both custom executables"
else
echo "❌ Action failed with custom executables"
exit 1
fi
if [ -f "$OUTPUT_FILE" ] && [ -s "$OUTPUT_FILE" ]; then
echo "✅ Execution log file created successfully"
if jq . "$OUTPUT_FILE" > /dev/null 2>&1; then
echo "✅ Output is valid JSON"
# Verify the task was completed
if grep -q "package" "$OUTPUT_FILE"; then
echo "✅ Claude successfully listed package files"
else
echo "⚠️ Could not verify if package files were listed"
fi
else
echo "❌ Output is not valid JSON"
exit 1
fi
else
echo "❌ Execution log file not found or empty"
exit 1
fi
+192
View File
@@ -0,0 +1,192 @@
name: Test MCP Servers
on:
pull_request:
workflow_dispatch:
workflow_call:
# The Claude API is authenticated via workload identity federation: id-token
# lets the action mint the GitHub OIDC token it exchanges for a short-lived
# access token. See docs/setup.md.
permissions:
contents: read
id-token: write
jobs:
test-mcp-integration:
# Skip on fork PRs since they can't mint the OIDC token used for Claude API auth
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
- name: Install dependencies
run: |
bun install
cd base-action/test/mcp-test
bun install
- name: Run Claude Code with MCP test
uses: ./base-action
id: claude-test
with:
prompt: "Call the test_tool tool and report its response."
anthropic_federation_rule_id: ${{ vars.ANTHROPIC_FEDERATION_RULE_ID }}
anthropic_organization_id: ${{ vars.ANTHROPIC_ORGANIZATION_ID }}
anthropic_service_account_id: ${{ vars.ANTHROPIC_SERVICE_ACCOUNT_ID }}
claude_args: --allowedTools mcp__test-server__test_tool
env:
# Change to test directory so it finds .mcp.json
CLAUDE_WORKING_DIR: ${{ github.workspace }}/base-action/test/mcp-test
- name: Check MCP server output
run: |
echo "Checking Claude output for MCP servers..."
# Parse the JSON output
OUTPUT_FILE="${RUNNER_TEMP}/claude-execution-output.json"
if [ ! -f "$OUTPUT_FILE" ]; then
echo "Error: Output file not found!"
exit 1
fi
echo "Output file contents:"
cat $OUTPUT_FILE
# Check if mcp_servers field exists in the init event
if jq -e '.[] | select(.type == "system" and .subtype == "init") | .mcp_servers' "$OUTPUT_FILE" > /dev/null; then
echo "✓ Found mcp_servers in output"
# MCP servers can connect asynchronously, so the init event may
# report the server as pending — check registration there, then
# verify the tool actually ran.
if jq -e '.[] | select(.type == "system" and .subtype == "init") | .mcp_servers[] | select(.name == "test-server")' "$OUTPUT_FILE" > /dev/null; then
echo "✓ test-server is registered"
else
echo "✗ test-server not found"
jq '.[] | select(.type == "system" and .subtype == "init") | .mcp_servers' "$OUTPUT_FILE"
exit 1
fi
if jq -e '.[] | select(.type == "assistant") | .message.content[]? | select(.type == "tool_use" and .name == "mcp__test-server__test_tool")' "$OUTPUT_FILE" > /dev/null; then
echo "✓ MCP test tool was called"
else
echo "✗ MCP test tool was not called"
jq '[.[] | select(.type == "assistant") | .message.content[]? | select(.type == "tool_use") | .name]' "$OUTPUT_FILE"
exit 1
fi
if jq -e '.[] | select(.type == "user") | .message.content[]? | select(.type == "tool_result") | select(.content | tostring | contains("Test tool response"))' "$OUTPUT_FILE" > /dev/null; then
echo "✓ MCP test tool returned its response"
else
echo "✗ MCP test tool response not found"
exit 1
fi
else
echo "✗ No mcp_servers field found in init event"
jq '.[] | select(.type == "system" and .subtype == "init")' "$OUTPUT_FILE"
exit 1
fi
echo "✓ All MCP server checks passed!"
test-mcp-config-flag:
# Skip on fork PRs since they can't mint the OIDC token used for Claude API auth
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
- name: Install dependencies
run: |
bun install
cd base-action/test/mcp-test
bun install
- name: Debug environment paths (--mcp-config test)
run: |
echo "=== Environment Variables (--mcp-config test) ==="
echo "HOME: $HOME"
echo ""
echo "=== Expected Config Paths ==="
echo "GitHub action writes to: $HOME/.claude/settings.json"
echo "Claude should read from: $HOME/.claude/settings.json"
echo ""
echo "=== Actual File System ==="
ls -la $HOME/.claude/ || echo "No $HOME/.claude directory"
- name: Run Claude Code with --mcp-config flag
uses: ./base-action
id: claude-config-test
with:
prompt: "Call the test_tool tool and report its response."
anthropic_federation_rule_id: ${{ vars.ANTHROPIC_FEDERATION_RULE_ID }}
anthropic_organization_id: ${{ vars.ANTHROPIC_ORGANIZATION_ID }}
anthropic_service_account_id: ${{ vars.ANTHROPIC_SERVICE_ACCOUNT_ID }}
claude_args: |
--allowedTools mcp__test-server__test_tool
--mcp-config '{"mcpServers":{"test-server":{"type":"stdio","command":"bun","args":["simple-mcp-server.ts"],"env":{}}}}'
env:
# Change to test directory so bun can find the MCP server script
CLAUDE_WORKING_DIR: ${{ github.workspace }}/base-action/test/mcp-test
- name: Check MCP server output with --mcp-config
run: |
echo "Checking Claude output for MCP servers with --mcp-config flag..."
# Parse the JSON output
OUTPUT_FILE="${RUNNER_TEMP}/claude-execution-output.json"
if [ ! -f "$OUTPUT_FILE" ]; then
echo "Error: Output file not found!"
exit 1
fi
echo "Output file contents:"
cat $OUTPUT_FILE
# Check if mcp_servers field exists in the init event
if jq -e '.[] | select(.type == "system" and .subtype == "init") | .mcp_servers' "$OUTPUT_FILE" > /dev/null; then
echo "✓ Found mcp_servers in output"
# MCP servers can connect asynchronously, so the init event may
# report the server as pending — check registration there, then
# verify the tool actually ran.
if jq -e '.[] | select(.type == "system" and .subtype == "init") | .mcp_servers[] | select(.name == "test-server")' "$OUTPUT_FILE" > /dev/null; then
echo "✓ test-server is registered"
else
echo "✗ test-server not found"
jq '.[] | select(.type == "system" and .subtype == "init") | .mcp_servers' "$OUTPUT_FILE"
exit 1
fi
if jq -e '.[] | select(.type == "assistant") | .message.content[]? | select(.type == "tool_use" and .name == "mcp__test-server__test_tool")' "$OUTPUT_FILE" > /dev/null; then
echo "✓ MCP test tool was called"
else
echo "✗ MCP test tool was not called"
jq '[.[] | select(.type == "assistant") | .message.content[]? | select(.type == "tool_use") | .name]' "$OUTPUT_FILE"
exit 1
fi
if jq -e '.[] | select(.type == "user") | .message.content[]? | select(.type == "tool_result") | select(.content | tostring | contains("Test tool response"))' "$OUTPUT_FILE" > /dev/null; then
echo "✓ MCP test tool returned its response"
else
echo "✗ MCP test tool response not found"
exit 1
fi
else
echo "✗ No mcp_servers field found in init event"
jq '.[] | select(.type == "system" and .subtype == "init")' "$OUTPUT_FILE"
exit 1
fi
echo "✓ All MCP server checks passed with --mcp-config flag!"
+202
View File
@@ -0,0 +1,202 @@
name: Test Settings Feature
on:
pull_request:
workflow_dispatch:
workflow_call:
# The Claude API is authenticated via workload identity federation: id-token
# lets the action mint the GitHub OIDC token it exchanges for a short-lived
# access token. See docs/setup.md.
permissions:
contents: read
id-token: write
jobs:
test-settings-inline-allow:
# Skip on fork PRs since they can't mint the OIDC token used for Claude API auth
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Test with inline settings JSON (echo allowed)
id: inline-settings-test
uses: ./base-action
with:
prompt: |
Use Bash to echo "Hello from settings test"
anthropic_federation_rule_id: ${{ vars.ANTHROPIC_FEDERATION_RULE_ID }}
anthropic_organization_id: ${{ vars.ANTHROPIC_ORGANIZATION_ID }}
anthropic_service_account_id: ${{ vars.ANTHROPIC_SERVICE_ACCOUNT_ID }}
settings: |
{
"permissions": {
"allow": ["Bash(echo:*)"]
}
}
- name: Verify echo worked
run: |
OUTPUT_FILE="${{ steps.inline-settings-test.outputs.execution_file }}"
CONCLUSION="${{ steps.inline-settings-test.outputs.conclusion }}"
echo "Conclusion: $CONCLUSION"
if [ "$CONCLUSION" = "success" ]; then
echo "✅ Action completed successfully"
else
echo "❌ Action failed"
exit 1
fi
# Check that permission was NOT denied
if grep -q "Permission to use Bash with command echo.*has been denied" "$OUTPUT_FILE"; then
echo "❌ Echo command was denied when it should have been allowed"
cat "$OUTPUT_FILE"
exit 1
fi
# Check if the echo command worked
if grep -q "Hello from settings test" "$OUTPUT_FILE"; then
echo "✅ Bash echo command worked (allowed by permissions)"
else
echo "❌ Bash echo command didn't work"
cat "$OUTPUT_FILE"
exit 1
fi
test-settings-inline-deny:
# Skip on fork PRs since they can't mint the OIDC token used for Claude API auth
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Test with inline settings JSON (echo denied)
id: inline-settings-test
uses: ./base-action
with:
prompt: |
Run the command `echo $HOME` to check the home directory path
anthropic_federation_rule_id: ${{ vars.ANTHROPIC_FEDERATION_RULE_ID }}
anthropic_organization_id: ${{ vars.ANTHROPIC_ORGANIZATION_ID }}
anthropic_service_account_id: ${{ vars.ANTHROPIC_SERVICE_ACCOUNT_ID }}
settings: |
{
"permissions": {
"deny": ["Bash(echo:*)"]
}
}
- name: Verify echo was denied
run: |
OUTPUT_FILE="${{ steps.inline-settings-test.outputs.execution_file }}"
# Check that permission was denied in the tool_result
if grep -q "Permission to use Bash with command echo.*has been denied" "$OUTPUT_FILE"; then
echo "✅ Echo command was correctly denied by permissions"
else
echo "❌ Expected permission denied message not found"
cat "$OUTPUT_FILE"
exit 1
fi
test-settings-file-allow:
# Skip on fork PRs since they can't mint the OIDC token used for Claude API auth
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Create settings file (echo allowed)
run: |
cat > test-settings.json << EOF
{
"permissions": {
"allow": ["Bash(echo:*)"]
}
}
EOF
- name: Test with settings file
id: file-settings-test
uses: ./base-action
with:
prompt: |
Use Bash to echo "Hello from settings file test"
anthropic_federation_rule_id: ${{ vars.ANTHROPIC_FEDERATION_RULE_ID }}
anthropic_organization_id: ${{ vars.ANTHROPIC_ORGANIZATION_ID }}
anthropic_service_account_id: ${{ vars.ANTHROPIC_SERVICE_ACCOUNT_ID }}
settings: "test-settings.json"
- name: Verify echo worked
run: |
OUTPUT_FILE="${{ steps.file-settings-test.outputs.execution_file }}"
CONCLUSION="${{ steps.file-settings-test.outputs.conclusion }}"
echo "Conclusion: $CONCLUSION"
if [ "$CONCLUSION" = "success" ]; then
echo "✅ Action completed successfully"
else
echo "❌ Action failed"
exit 1
fi
# Check that permission was NOT denied
if grep -q "Permission to use Bash with command echo.*has been denied" "$OUTPUT_FILE"; then
echo "❌ Echo command was denied when it should have been allowed"
cat "$OUTPUT_FILE"
exit 1
fi
# Check if the echo command worked
if grep -q "Hello from settings file test" "$OUTPUT_FILE"; then
echo "✅ Bash echo command worked (allowed by permissions)"
else
echo "❌ Bash echo command didn't work"
cat "$OUTPUT_FILE"
exit 1
fi
test-settings-file-deny:
# Skip on fork PRs since they can't mint the OIDC token used for Claude API auth
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Create settings file (echo denied)
run: |
cat > test-settings.json << EOF
{
"permissions": {
"deny": ["Bash(echo:*)"]
}
}
EOF
- name: Test with settings file
id: file-settings-test
uses: ./base-action
with:
prompt: |
Run the command `echo $HOME` to check the home directory path
anthropic_federation_rule_id: ${{ vars.ANTHROPIC_FEDERATION_RULE_ID }}
anthropic_organization_id: ${{ vars.ANTHROPIC_ORGANIZATION_ID }}
anthropic_service_account_id: ${{ vars.ANTHROPIC_SERVICE_ACCOUNT_ID }}
settings: "test-settings.json"
- name: Verify echo was denied
run: |
OUTPUT_FILE="${{ steps.file-settings-test.outputs.execution_file }}"
# Check that permission was denied in the tool_result
if grep -q "Permission to use Bash with command echo.*has been denied" "$OUTPUT_FILE"; then
echo "✅ Echo command was correctly denied by permissions"
else
echo "❌ Expected permission denied message not found"
cat "$OUTPUT_FILE"
exit 1
fi
@@ -0,0 +1,329 @@
name: Test Structured Outputs
on:
pull_request:
workflow_dispatch:
workflow_call:
# The Claude API is authenticated via workload identity federation: id-token
# lets the action mint the GitHub OIDC token it exchanges for a short-lived
# access token. See docs/setup.md.
permissions:
contents: read
id-token: write
jobs:
test-basic-types:
name: Test Basic Type Conversions
# Skip on fork PRs since they can't mint the OIDC token used for Claude API auth
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Test with explicit values
id: test
uses: ./base-action
with:
prompt: |
Run this command: echo "test"
Then return EXACTLY these values:
- text_field: "hello"
- number_field: 42
- boolean_true: true
- boolean_false: false
anthropic_federation_rule_id: ${{ vars.ANTHROPIC_FEDERATION_RULE_ID }}
anthropic_organization_id: ${{ vars.ANTHROPIC_ORGANIZATION_ID }}
anthropic_service_account_id: ${{ vars.ANTHROPIC_SERVICE_ACCOUNT_ID }}
claude_args: |
--allowedTools Bash
--json-schema '{"type":"object","properties":{"text_field":{"type":"string"},"number_field":{"type":"number"},"boolean_true":{"type":"boolean"},"boolean_false":{"type":"boolean"}},"required":["text_field","number_field","boolean_true","boolean_false"]}'
- name: Verify outputs
run: |
# Parse the structured_output JSON
OUTPUT='${{ steps.test.outputs.structured_output }}'
# Test string pass-through
TEXT_FIELD=$(echo "$OUTPUT" | jq -r '.text_field')
if [ "$TEXT_FIELD" != "hello" ]; then
echo "❌ String: expected 'hello', got '$TEXT_FIELD'"
exit 1
fi
# Test number → string conversion
NUMBER_FIELD=$(echo "$OUTPUT" | jq -r '.number_field')
if [ "$NUMBER_FIELD" != "42" ]; then
echo "❌ Number: expected '42', got '$NUMBER_FIELD'"
exit 1
fi
# Test boolean → "true" conversion
BOOLEAN_TRUE=$(echo "$OUTPUT" | jq -r '.boolean_true')
if [ "$BOOLEAN_TRUE" != "true" ]; then
echo "❌ Boolean true: expected 'true', got '$BOOLEAN_TRUE'"
exit 1
fi
# Test boolean → "false" conversion
BOOLEAN_FALSE=$(echo "$OUTPUT" | jq -r '.boolean_false')
if [ "$BOOLEAN_FALSE" != "false" ]; then
echo "❌ Boolean false: expected 'false', got '$BOOLEAN_FALSE'"
exit 1
fi
echo "✅ All basic type conversions correct"
test-complex-types:
name: Test Arrays and Objects
# Skip on fork PRs since they can't mint the OIDC token used for Claude API auth
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Test complex types
id: test
uses: ./base-action
with:
prompt: |
Run: echo "ready"
Return EXACTLY:
- items: ["apple", "banana", "cherry"]
- config: {"key": "value", "count": 3}
- empty_array: []
anthropic_federation_rule_id: ${{ vars.ANTHROPIC_FEDERATION_RULE_ID }}
anthropic_organization_id: ${{ vars.ANTHROPIC_ORGANIZATION_ID }}
anthropic_service_account_id: ${{ vars.ANTHROPIC_SERVICE_ACCOUNT_ID }}
claude_args: |
--allowedTools Bash
--json-schema '{"type":"object","properties":{"items":{"type":"array","items":{"type":"string"}},"config":{"type":"object"},"empty_array":{"type":"array"}},"required":["items","config","empty_array"]}'
- name: Verify JSON stringification
run: |
# Parse the structured_output JSON
OUTPUT='${{ steps.test.outputs.structured_output }}'
# Arrays should be JSON stringified
if ! echo "$OUTPUT" | jq -e '.items | length == 3' > /dev/null; then
echo "❌ Array not properly formatted"
echo "$OUTPUT" | jq '.items'
exit 1
fi
# Objects should be JSON stringified
if ! echo "$OUTPUT" | jq -e '.config.key == "value"' > /dev/null; then
echo "❌ Object not properly formatted"
echo "$OUTPUT" | jq '.config'
exit 1
fi
# Empty arrays should work
if ! echo "$OUTPUT" | jq -e '.empty_array | length == 0' > /dev/null; then
echo "❌ Empty array not properly formatted"
echo "$OUTPUT" | jq '.empty_array'
exit 1
fi
echo "✅ All complex types handled correctly"
test-edge-cases:
name: Test Edge Cases
# Skip on fork PRs since they can't mint the OIDC token used for Claude API auth
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Test edge cases
id: test
uses: ./base-action
with:
prompt: |
Run: echo "test"
Return EXACTLY:
- zero: 0
- empty_string: ""
- negative: -5
- decimal: 3.14
anthropic_federation_rule_id: ${{ vars.ANTHROPIC_FEDERATION_RULE_ID }}
anthropic_organization_id: ${{ vars.ANTHROPIC_ORGANIZATION_ID }}
anthropic_service_account_id: ${{ vars.ANTHROPIC_SERVICE_ACCOUNT_ID }}
claude_args: |
--allowedTools Bash
--json-schema '{"type":"object","properties":{"zero":{"type":"number"},"empty_string":{"type":"string"},"negative":{"type":"number"},"decimal":{"type":"number"}},"required":["zero","empty_string","negative","decimal"]}'
- name: Verify edge cases
run: |
# Parse the structured_output JSON
OUTPUT='${{ steps.test.outputs.structured_output }}'
# Zero should be "0", not empty or falsy
ZERO=$(echo "$OUTPUT" | jq -r '.zero')
if [ "$ZERO" != "0" ]; then
echo "❌ Zero: expected '0', got '$ZERO'"
exit 1
fi
# Empty string should be empty (not "null" or missing)
EMPTY_STRING=$(echo "$OUTPUT" | jq -r '.empty_string')
if [ "$EMPTY_STRING" != "" ]; then
echo "❌ Empty string: expected '', got '$EMPTY_STRING'"
exit 1
fi
# Negative numbers should work
NEGATIVE=$(echo "$OUTPUT" | jq -r '.negative')
if [ "$NEGATIVE" != "-5" ]; then
echo "❌ Negative: expected '-5', got '$NEGATIVE'"
exit 1
fi
# Decimals should preserve precision
DECIMAL=$(echo "$OUTPUT" | jq -r '.decimal')
if [ "$DECIMAL" != "3.14" ]; then
echo "❌ Decimal: expected '3.14', got '$DECIMAL'"
exit 1
fi
echo "✅ All edge cases handled correctly"
test-name-sanitization:
name: Test Output Name Sanitization
# Skip on fork PRs since they can't mint the OIDC token used for Claude API auth
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Test special characters in field names
id: test
uses: ./base-action
with:
prompt: |
Run: echo "test"
Return EXACTLY: {test-result: "passed", item_count: 10}
anthropic_federation_rule_id: ${{ vars.ANTHROPIC_FEDERATION_RULE_ID }}
anthropic_organization_id: ${{ vars.ANTHROPIC_ORGANIZATION_ID }}
anthropic_service_account_id: ${{ vars.ANTHROPIC_SERVICE_ACCOUNT_ID }}
claude_args: |
--allowedTools Bash
--json-schema '{"type":"object","properties":{"test-result":{"type":"string"},"item_count":{"type":"number"}},"required":["test-result","item_count"]}'
- name: Verify sanitized names work
run: |
# Parse the structured_output JSON
OUTPUT='${{ steps.test.outputs.structured_output }}'
# Hyphens should be preserved in the JSON
TEST_RESULT=$(echo "$OUTPUT" | jq -r '.["test-result"]')
if [ "$TEST_RESULT" != "passed" ]; then
echo "❌ Hyphenated name failed: expected 'passed', got '$TEST_RESULT'"
exit 1
fi
# Underscores should work
ITEM_COUNT=$(echo "$OUTPUT" | jq -r '.item_count')
if [ "$ITEM_COUNT" != "10" ]; then
echo "❌ Underscore name failed: expected '10', got '$ITEM_COUNT'"
exit 1
fi
echo "✅ Name sanitization works"
test-execution-file-structure:
name: Test Execution File Format
# Skip on fork PRs since they can't mint the OIDC token used for Claude API auth
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Run with structured output
id: test
uses: ./base-action
with:
prompt: "Run: echo 'complete'. Return: {done: true}"
anthropic_federation_rule_id: ${{ vars.ANTHROPIC_FEDERATION_RULE_ID }}
anthropic_organization_id: ${{ vars.ANTHROPIC_ORGANIZATION_ID }}
anthropic_service_account_id: ${{ vars.ANTHROPIC_SERVICE_ACCOUNT_ID }}
claude_args: |
--allowedTools Bash
--json-schema '{"type":"object","properties":{"done":{"type":"boolean"}},"required":["done"]}'
- name: Verify execution file contains structured_output
run: |
FILE="${{ steps.test.outputs.execution_file }}"
# Check file exists
if [ ! -f "$FILE" ]; then
echo "❌ Execution file missing"
exit 1
fi
# Check for structured_output field
if ! jq -e '.[] | select(.type == "result") | .structured_output' "$FILE" > /dev/null; then
echo "❌ No structured_output in execution file"
cat "$FILE"
exit 1
fi
# Verify the actual value
DONE=$(jq -r '.[] | select(.type == "result") | .structured_output.done' "$FILE")
if [ "$DONE" != "true" ]; then
echo "❌ Wrong value in execution file"
exit 1
fi
echo "✅ Execution file format correct"
test-summary:
name: Summary
runs-on: ubuntu-latest
needs:
- test-basic-types
- test-complex-types
- test-edge-cases
- test-name-sanitization
- test-execution-file-structure
if: ${{ always() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }}
steps:
- name: Generate Summary
run: |
echo "# Structured Output Tests (Optimized)" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Fast, deterministic tests using explicit prompts" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Test | Result |" >> $GITHUB_STEP_SUMMARY
echo "|------|--------|" >> $GITHUB_STEP_SUMMARY
echo "| Basic Types | ${{ needs.test-basic-types.result == 'success' && '✅ PASS' || '❌ FAIL' }} |" >> $GITHUB_STEP_SUMMARY
echo "| Complex Types | ${{ needs.test-complex-types.result == 'success' && '✅ PASS' || '❌ FAIL' }} |" >> $GITHUB_STEP_SUMMARY
echo "| Edge Cases | ${{ needs.test-edge-cases.result == 'success' && '✅ PASS' || '❌ FAIL' }} |" >> $GITHUB_STEP_SUMMARY
echo "| Name Sanitization | ${{ needs.test-name-sanitization.result == 'success' && '✅ PASS' || '❌ FAIL' }} |" >> $GITHUB_STEP_SUMMARY
echo "| Execution File | ${{ needs.test-execution-file-structure.result == 'success' && '✅ PASS' || '❌ FAIL' }} |" >> $GITHUB_STEP_SUMMARY
# Check if all passed
ALL_PASSED=${{
needs.test-basic-types.result == 'success' &&
needs.test-complex-types.result == 'success' &&
needs.test-edge-cases.result == 'success' &&
needs.test-name-sanitization.result == 'success' &&
needs.test-execution-file-structure.result == 'success'
}}
if [ "$ALL_PASSED" = "true" ]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "## ✅ All Tests Passed" >> $GITHUB_STEP_SUMMARY
else
echo "" >> $GITHUB_STEP_SUMMARY
echo "## ❌ Some Tests Failed" >> $GITHUB_STEP_SUMMARY
exit 1
fi
-24
View File
@@ -1,24 +0,0 @@
name: Update Beta Tag
on:
release:
types: [published]
jobs:
update-beta-tag:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- name: Update beta tag
run: |
# Get the current release version
VERSION=${GITHUB_REF#refs/tags/}
# Update the beta tag to point to this release
git config user.name github-actions[bot]
git config user.email github-actions[bot]@users.noreply.github.com
git tag -fa beta -m "Update beta tag to ${VERSION}"
git push origin beta --force
+1
View File
@@ -1,3 +1,4 @@
.DS_Store
node_modules
**/.claude/settings.local.json
+4
View File
@@ -0,0 +1,4 @@
# Test fixtures should not be formatted to preserve exact output matching
test/fixtures/
# Snapshot of PR-authored config kept for review; do not reformat
.claude-pr/
+30 -44
View File
@@ -1,58 +1,44 @@
# CLAUDE.md
This file provides guidance to Claude Code when working with code in this repository.
## Development Tools
- Runtime: Bun 1.2.11
## Common Development Tasks
### Available npm/bun scripts from package.json:
## Commands
```bash
# Test
bun test
# Formatting
bun run format # Format code with prettier
bun run format:check # Check code formatting
bun test # Run tests
bun run typecheck # TypeScript type checking
bun run format # Format with prettier
bun run format:check # Check formatting
```
## Architecture Overview
## What This Is
This is a GitHub Action that enables Claude to interact with GitHub PRs and issues. The action:
A GitHub Action that lets Claude respond to `@claude` mentions on issues/PRs (tag mode) or run tasks via `prompt` input (agent mode). Mode is auto-detected: if `prompt` is provided, it's agent mode; if triggered by a comment/issue event with `@claude`, it's tag mode. See `src/modes/detector.ts`.
1. **Trigger Detection**: Uses `check-trigger.ts` to determine if Claude should respond based on comment/issue content
2. **Context Gathering**: Fetches GitHub data (PRs, issues, comments) via `github-data-fetcher.ts` and formats it using `github-data-formatter.ts`
3. **AI Integration**: Supports multiple Claude providers (Anthropic API, AWS Bedrock, Google Vertex AI)
4. **Prompt Creation**: Generates context-rich prompts using `create-prompt.ts`
5. **MCP Server Integration**: Installs and configures GitHub MCP server for extended functionality
## How It Runs
### Key Components
Single entrypoint: `src/entrypoints/run.ts` orchestrates everything — prepare (auth, permissions, trigger check, branch/comment creation), install Claude Code CLI, execute Claude via `base-action/` functions (imported directly, not subprocess), then cleanup (update tracking comment, write step summary). SSH signing cleanup and token revocation are separate `always()` steps in `action.yml`.
- **Trigger System**: Responds to `/claude` comments or issue assignments
- **Authentication**: OIDC-based token exchange for secure GitHub interactions
- **Cloud Integration**: Supports direct Anthropic API, AWS Bedrock, and Google Vertex AI
- **GitHub Operations**: Creates branches, posts comments, and manages PRs/issues
`base-action/` is also published standalone as `@anthropic-ai/claude-code-base-action`. Don't break its public API. It reads config from `INPUT_`-prefixed env vars (set by `action.yml`), not from action inputs directly.
### Project Structure
## Key Concepts
```
src/
├── check-trigger.ts # Determines if Claude should respond
├── create-prompt.ts # Generates contextual prompts
├── github-data-fetcher.ts # Retrieves GitHub data
├── github-data-formatter.ts # Formats GitHub data for prompts
├── install-mcp-server.ts # Sets up GitHub MCP server
├── update-comment-with-link.ts # Updates comments with job links
└── types/
└── github.ts # TypeScript types for GitHub data
```
**Auth priority**: `github_token` input (user-provided) > GitHub App OIDC token (default). The `claude_code_oauth_token` and `anthropic_api_key` are for the Claude API, not GitHub. Token setup lives in `src/github/token.ts`.
## Important Notes
**Mode lifecycle**: `detectMode()` in `src/modes/detector.ts` picks the mode name ("tag" or "agent"). Trigger checking and prepare dispatch are inlined in `run.ts`: tag mode calls `prepareTagMode()` from `src/modes/tag/`, agent mode calls `prepareAgentMode()` from `src/modes/agent/`.
- Actions are triggered by `@claude` comments or issue assignment unless a different trigger_phrase is specified
- The action creates branches for issues and pushes to PR branches directly
- All actions create OIDC tokens for secure authentication
- Progress is tracked through dynamic comment updates with checkboxes
**Prompt construction**: Tag mode's `prepareTagMode()` builds the prompt by fetching GitHub data (`src/github/data/fetcher.ts`), formatting it as markdown (`src/github/data/formatter.ts`), and writing it to a temp file via `createPrompt()`. Agent mode writes the user's prompt directly. The prompt includes issue/PR body, comments, diff, and CI status. This is the most important part of the action — it's what Claude sees.
## Things That Will Bite You
- **Strict TypeScript**: `noUnusedLocals` and `noUnusedParameters` are enabled. Typecheck will fail on unused variables.
- **Discriminated unions for GitHub context**: `GitHubContext` is a union type — call `isEntityContext(context)` before accessing entity-specific fields like `context.issue` or `context.pullRequest`.
- **Token lifecycle matters**: The GitHub App token is obtained early and revoked in a separate `always()` step in `action.yml`. If you move token revocation into `run.ts`, it won't run if the process crashes. Same for SSH signing cleanup.
- **Error phase attribution**: The catch block in `run.ts` uses `prepareCompleted` to distinguish prepare failures from execution failures. The tracking comment shows different messages for each.
- **`action.yml` outputs reference step IDs**: Outputs like `execution_file`, `branch_name`, `github_token` reference `steps.run.outputs.*`. If you rename the step ID, update the outputs section too.
- **Integration testing** happens in a separate repo (`install-test`), not here. The tests in this repo are unit tests.
## Code Conventions
- Runtime is Bun, not Node. Use `bun test`, not `jest`.
- `moduleResolution: "bundler"` — imports don't need `.js` extensions.
- GitHub API calls should use retry logic (`src/utils/retry.ts`).
- MCP servers are auto-installed at runtime to `~/.claude/mcp/github-{type}-server/`.
+1 -21
View File
@@ -50,20 +50,6 @@ Thank you for your interest in contributing to Claude Code Action! This document
bun test
```
2. **Integration Tests** (using GitHub Actions locally):
```bash
./test-local.sh
```
This script:
- Installs `act` if not present (requires Homebrew on macOS)
- Runs the GitHub Action workflow locally using Docker
- Requires your `ANTHROPIC_API_KEY` to be set
On Apple Silicon Macs, the script automatically adds the `--container-architecture linux/amd64` flag to avoid compatibility issues.
## Pull Request Process
1. Create a new branch from `main`:
@@ -103,13 +89,7 @@ Thank you for your interest in contributing to Claude Code Action! This document
When modifying the action:
1. Test locally with the test script:
```bash
./test-local.sh
```
2. Test in a real GitHub Actions workflow by:
1. Test in a real GitHub Actions workflow by:
- Creating a test repository
- Using your branch as the action source:
```yaml
+35 -417
View File
@@ -2,17 +2,24 @@
# Claude Code Action
A general-purpose [Claude Code](https://claude.ai/code) action for GitHub PRs and issues that can answer questions and implement code changes. This action listens for a trigger phrase in comments and activates Claude act on the request. It supports multiple authentication methods including Anthropic direct API, Amazon Bedrock, and Google Vertex AI.
A general-purpose [Claude Code](https://claude.ai/code) action for GitHub PRs and issues that can answer questions and implement code changes. This action intelligently detects when to activate based on your workflow context—whether responding to @claude mentions, issue assignments, or executing automation tasks with explicit prompts. It supports multiple authentication methods including Anthropic direct API (API key or workload identity federation), Amazon Bedrock, Google Vertex AI, and Microsoft Foundry.
## Features
- 🎯 **Intelligent Mode Detection**: Automatically selects the appropriate execution mode based on your workflow context—no configuration needed
- 🤖 **Interactive Code Assistant**: Claude can answer questions about code, architecture, and programming
- 🔍 **Code Review**: Analyzes PR changes and suggests improvements
-**Code Implementation**: Can implement simple fixes, refactoring, and even new features
- 💬 **PR/Issue Integration**: Works seamlessly with GitHub comments and PR reviews
- 🛠️ **Flexible Tool Access**: Access to GitHub APIs and file operations (additional tools can be enabled via configuration)
- 📋 **Progress Tracking**: Visual progress indicators with checkboxes that dynamically update as Claude completes tasks
- 📊 **Structured Outputs**: Get validated JSON results that automatically become GitHub Action outputs for complex automations
- 🏃 **Runs on Your Infrastructure**: The action executes entirely on your own GitHub runner (Anthropic API calls go to your chosen provider)
- ⚙️ **Simplified Configuration**: Unified `prompt` and `claude_args` inputs provide clean, powerful configuration aligned with Claude Code SDK
## 📦 Upgrading from v0.x?
**See our [Migration Guide](./docs/migration-guide.md)** for step-by-step instructions on updating your workflows to v1.0. The new version simplifies configuration while maintaining compatibility with most existing setups.
## Quickstart
@@ -23,430 +30,41 @@ This command will guide you through setting up the GitHub app and required secre
**Note**:
- You must be a repository admin to install the GitHub app and add secrets
- This quickstart method is only available for direct Anthropic API users. If you're using AWS Bedrock, please see the instructions below.
- This quickstart method is only available for direct Anthropic API users. For AWS Bedrock, Google Vertex AI, or Microsoft Foundry setup, see [docs/cloud-providers.md](./docs/cloud-providers.md).
### Manual Setup (Direct API)
## 📚 Solutions & Use Cases
**Requirements**: You must be a repository admin to complete these steps.
Looking for specific automation patterns? Check our **[Solutions Guide](./docs/solutions.md)** for complete working examples including:
1. Install the Claude GitHub app to your repository: https://github.com/apps/claude
2. Add `ANTHROPIC_API_KEY` to your repository secrets ([Learn how to use secrets in GitHub Actions](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions))
3. Copy the workflow file from [`examples/claude.yml`](./examples/claude.yml) into your repository's `.github/workflows/`
- **🔍 Automatic PR Code Review** - Full review automation
- **📂 Path-Specific Reviews** - Trigger on critical file changes
- **👥 External Contributor Reviews** - Special handling for new contributors
- **📝 Custom Review Checklists** - Enforce team standards
- **🔄 Scheduled Maintenance** - Automated repository health checks
- **🏷️ Issue Triage & Labeling** - Automatic categorization
- **📖 Documentation Sync** - Keep docs updated with code changes
- **🔒 Security-Focused Reviews** - OWASP-aligned security analysis
- **📊 DIY Progress Tracking** - Create tracking comments in automation mode
## Usage
Each solution includes complete working examples, configuration details, and expected outcomes.
Add a workflow file to your repository (e.g., `.github/workflows/claude.yml`):
## Documentation
```yaml
name: Claude Assistant
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
issues:
types: [opened, assigned]
pull_request_review:
types: [submitted]
- **[Solutions Guide](./docs/solutions.md)** - **🎯 Ready-to-use automation patterns**
- **[Migration Guide](./docs/migration-guide.md)** - **⭐ Upgrading from v0.x to v1.0**
- [Setup Guide](./docs/setup.md) - Manual setup, custom GitHub apps, and security best practices
- [Usage Guide](./docs/usage.md) - Basic usage, workflow configuration, and input parameters
- [Custom Automations](./docs/custom-automations.md) - Examples of automated workflows and custom prompts
- [Configuration](./docs/configuration.md) - MCP servers, permissions, environment variables, and advanced settings
- [Experimental Features](./docs/experimental.md) - Execution modes and network restrictions
- [Cloud Providers](./docs/cloud-providers.md) - AWS Bedrock, Google Vertex AI, and Microsoft Foundry setup
- [Capabilities & Limitations](./docs/capabilities-and-limitations.md) - What Claude can and cannot do
- [Security](./docs/security.md) - Access control, permissions, and commit signing
- [FAQ](./docs/faq.md) - Common questions and troubleshooting
jobs:
claude-response:
runs-on: ubuntu-latest
steps:
- uses: anthropics/claude-code-action@beta
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
github_token: ${{ secrets.GITHUB_TOKEN }}
# Optional: add custom trigger phrase (default: @claude)
# trigger_phrase: "/claude"
# Optional: add assignee trigger for issues
# assignee_trigger: "claude"
```
## 📚 FAQ
## Inputs
| Input | Description | Required | Default |
| --------------------- | -------------------------------------------------------------------------------------------------------------------- | -------- | --------- |
| `anthropic_api_key` | Anthropic API key (required for direct API, not needed for Bedrock/Vertex) | No\* | - |
| `direct_prompt` | Direct prompt for Claude to execute automatically without needing a trigger (for automated workflows) | No | - |
| `timeout_minutes` | Timeout in minutes for execution | No | `30` |
| `github_token` | GitHub token for Claude to operate with. **Only include this if you're connecting a custom GitHub app of your own!** | No | - |
| `model` | Model to use (provider-specific format required for Bedrock/Vertex) | No | - |
| `anthropic_model` | **DEPRECATED**: Use `model` instead. Kept for backward compatibility. | No | - |
| `use_bedrock` | Use Amazon Bedrock with OIDC authentication instead of direct Anthropic API | No | `false` |
| `use_vertex` | Use Google Vertex AI with OIDC authentication instead of direct Anthropic API | No | `false` |
| `allowed_tools` | Additional tools for Claude to use (the base GitHub tools will always be included) | No | "" |
| `disallowed_tools` | Tools that Claude should never use | No | "" |
| `custom_instructions` | Additional custom instructions to include in the prompt for Claude | No | "" |
| `assignee_trigger` | The assignee username that triggers the action (e.g. @claude). Only used for issue assignment | No | - |
| `trigger_phrase` | The trigger phrase to look for in comments, issue/PR bodies, and issue titles | No | `@claude` |
\*Required when using direct Anthropic API (default and when not using Bedrock or Vertex)
> **Note**: This action is currently in beta. Features and APIs may change as we continue to improve the integration.
## Examples
### Ways to Tag @claude
These examples show how to interact with Claude using comments in PRs and issues. By default, Claude will be triggered anytime you mention `@claude`, but you can customize the exact trigger phrase using the `trigger_phrase` input in the workflow.
Claude will see the full PR context, including any comments.
#### Ask Questions
Add a comment to a PR or issue:
```
@claude What does this function do and how could we improve it?
```
Claude will analyze the code and provide a detailed explanation with suggestions.
#### Request Fixes
Ask Claude to implement specific changes:
```
@claude Can you add error handling to this function?
```
#### Code Review
Get a thorough review:
```
@claude Please review this PR and suggest improvements
```
Claude will analyze the changes and provide feedback.
#### Fix Bugs from Screenshots
Upload a screenshot of a bug and ask Claude to fix it:
```
@claude Here's a screenshot of a bug I'm seeing [upload screenshot]. Can you fix it?
```
Claude can see and analyze images, making it easy to fix visual bugs or UI issues.
### Custom Automations
These examples show how to configure Claude to act automatically based on GitHub events, without requiring manual @mentions.
#### Supported GitHub Events
This action supports the following GitHub events ([learn more GitHub event triggers](https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows)):
- `pull_request` - When PRs are opened or synchronized
- `issue_comment` - When comments are created on issues or PRs
- `pull_request_comment` - When comments are made on PR diffs
- `issues` - When issues are opened or assigned
- `pull_request_review` - When PR reviews are submitted
- `pull_request_review_comment` - When comments are made on PR reviews
- `repository_dispatch` - Custom events triggered via API (coming soon)
- `workflow_dispatch` - Manual workflow triggers (coming soon)
#### Automated Documentation Updates
Automatically update documentation when specific files change (see [`examples/claude-pr-path-specific.yml`](./examples/claude-pr-path-specific.yml)):
```yaml
on:
pull_request:
paths:
- "src/api/**/*.ts"
steps:
- uses: anthropics/claude-code-action@beta
with:
direct_prompt: |
Update the API documentation in README.md to reflect
the changes made to the API endpoints in this PR.
```
When API files are modified, Claude automatically updates your README with the latest endpoint documentation and pushes the changes back to the PR, keeping your docs in sync with your code.
#### Author-Specific Code Reviews
Automatically review PRs from specific authors or external contributors (see [`examples/claude-review-from-author.yml`](./examples/claude-review-from-author.yml)):
```yaml
on:
pull_request:
types: [opened, synchronize]
jobs:
review-by-author:
if: |
github.event.pull_request.user.login == 'developer1' ||
github.event.pull_request.user.login == 'external-contributor'
steps:
- uses: anthropics/claude-code-action@beta
with:
direct_prompt: |
Please provide a thorough review of this pull request.
Pay extra attention to coding standards, security practices,
and test coverage since this is from an external contributor.
```
Perfect for automatically reviewing PRs from new team members, external contributors, or specific developers who need extra guidance.
## How It Works
1. **Trigger Detection**: Listens for comments containing the trigger phrase (default: `@claude`) or issue assignment to a specific user
2. **Context Gathering**: Analyzes the PR/issue, comments, code changes
3. **Smart Responses**: Either answers questions or implements changes
4. **Branch Management**: Creates new PRs for human authors, pushes directly for Claude's own PRs
5. **Communication**: Posts updates at every step to keep you informed
This action is built on top of [`anthropics/claude-code-base-action`](https://github.com/anthropics/claude-code-base-action).
## Capabilities and Limitations
### What Claude Can Do
- **Respond in a Single Comment**: Claude operates by updating a single initial comment with progress and results
- **Answer Questions**: Analyze code and provide explanations
- **Implement Code Changes**: Make simple to moderate code changes based on requests
- **Prepare Pull Requests**: Creates commits on a branch and links back to a prefilled PR creation page
- **Perform Code Reviews**: Analyze PR changes and provide detailed feedback
- **Smart Branch Handling**:
- When triggered on an **issue**: Always creates a new branch for the work
- When triggered on an **open PR**: Always pushes directly to the existing PR branch
- When triggered on a **closed PR**: Creates a new branch since the original is no longer active
### What Claude Cannot Do
- **Submit PR Reviews**: Claude cannot submit formal GitHub PR reviews
- **Approve PRs**: For security reasons, Claude cannot approve pull requests
- **Post Multiple Comments**: Claude only acts by updating its initial comment
- **Execute Commands Outside Its Context**: Claude only has access to the repository and PR/issue context it's triggered in
- **Run Arbitrary Bash Commands**: By default, Claude cannot execute Bash commands unless explicitly allowed using the `allowed_tools` configuration
- **View CI/CD Results**: Cannot access CI systems, test results, or build logs unless an additional tool or MCP server is configured
- **Perform Branch Operations**: Cannot merge branches, rebase, or perform other git operations beyond pushing commits
## Advanced Configuration
### Custom Tools
By default, Claude only has access to:
- File operations (reading, committing, editing files, read-only git commands)
- Comment management (creating/updating comments)
- Basic GitHub operations
Claude does **not** have access to execute arbitrary Bash commands by default. If you want Claude to run specific commands (e.g., npm install, npm test), you must explicitly allow them using the `allowed_tools` configuration:
**Note**: If your repository has a `.mcp.json` file in the root directory, Claude will automatically detect and use the MCP server tools defined there. However, these tools still need to be explicitly allowed via the `allowed_tools` configuration.
```yaml
- uses: anthropics/claude-code-action@beta
with:
allowed_tools: "Bash(npm install),Bash(npm run test),Edit,Replace,NotebookEditCell"
disallowed_tools: "TaskOutput,KillTask"
# ... other inputs
```
**Note**: The base GitHub tools are always included. Use `allowed_tools` to add additional tools (including specific Bash commands), and `disallowed_tools` to prevent specific tools from being used.
### Custom Model
Use a specific Claude model:
```yaml
- uses: anthropics/claude-code-action@beta
with:
# model: "claude-3-5-sonnet-20241022" # Optional: specify a different model
# ... other inputs
```
## Cloud Providers
You can authenticate with Claude using any of these three methods:
1. Direct Anthropic API (default)
2. Amazon Bedrock with OIDC authentication
3. Google Vertex AI with OIDC authentication
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**:
- Bedrock and Vertex use OIDC authentication exclusively
- AWS Bedrock automatically uses cross-region inference profiles for certain models
- For cross-region inference profile models, you need to request and be granted access to the Claude models in all regions that the inference profile uses
### Model Configuration
Use provider-specific model names based on your chosen provider:
```yaml
# For direct Anthropic API (default)
- uses: anthropics/claude-code-action@beta
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
# ... other inputs
# For Amazon Bedrock with OIDC
- uses: anthropics/claude-code-action@beta
with:
model: "anthropic.claude-3-7-sonnet-20250219-beta:0" # Cross-region inference
use_bedrock: "true"
# ... other inputs
# For Google Vertex AI with OIDC
- uses: anthropics/claude-code-action@beta
with:
model: "claude-3-7-sonnet@20250219"
use_vertex: "true"
# ... other inputs
```
### OIDC Authentication for Bedrock and Vertex
Both AWS Bedrock and GCP Vertex AI require OIDC authentication.
```yaml
# For AWS Bedrock with OIDC
- name: Configure AWS Credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
aws-region: us-west-2
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
- uses: anthropics/claude-code-action@beta
with:
model: "anthropic.claude-3-7-sonnet-20250219-beta:0"
use_bedrock: "true"
# ... other inputs
permissions:
id-token: write # Required for OIDC
```
```yaml
# For GCP Vertex AI with OIDC
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@v2
with:
workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }}
service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }}
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
- uses: anthropics/claude-code-action@beta
with:
model: "claude-3-7-sonnet@20250219"
use_vertex: "true"
# ... other inputs
permissions:
id-token: write # Required for OIDC
```
## Security
### Access Control
- **Repository Access**: The action can only be triggered by users with write access to the repository
- **No Bot Triggers**: GitHub Apps and bots cannot trigger this action
- **Token Permissions**: The GitHub app receives only a short-lived token scoped specifically to the repository it's operating in
- **No Cross-Repository Access**: Each action invocation is limited to the repository where it was triggered
- **Limited Scope**: The token cannot access other repositories or perform actions beyond the configured permissions
### GitHub App Permissions
The [Claude Code GitHub app](https://github.com/apps/claude) requires these permissions:
- **Pull Requests**: Read and write to create PRs and push changes
- **Issues**: Read and write to respond to issues
- **Contents**: Read and write to modify repository files
### Commit Signing
All commits made by Claude through this action are automatically signed with commit signatures. This ensures the authenticity and integrity of commits, providing a verifiable trail of changes made by the action.
### ⚠️ ANTHROPIC_API_KEY Protection
**CRITICAL: Never hardcode your Anthropic API key in workflow files!**
Your ANTHROPIC_API_KEY must always be stored in GitHub secrets to prevent unauthorized access:
```yaml
# CORRECT ✅
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
# NEVER DO THIS ❌
anthropic_api_key: "sk-ant-api03-..." # Exposed and vulnerable!
```
### Setting Up GitHub Secrets
1. Go to your repository's Settings
2. Click on "Secrets and variables" → "Actions"
3. Click "New repository secret"
4. Name: `ANTHROPIC_API_KEY`
5. Value: Your Anthropic API key (starting with `sk-ant-`)
6. Click "Add secret"
### Best Practices for ANTHROPIC_API_KEY
1. ✅ Always use `${{ secrets.ANTHROPIC_API_KEY }}` in workflows
2. ✅ Never commit API keys to version control
3. ✅ Regularly rotate your API keys
4. ✅ Use environment secrets for organization-wide access
5. ❌ Never share API keys in pull requests or issues
6. ❌ Avoid logging workflow variables that might contain keys
## Security Best Practices
**⚠️ IMPORTANT: Never commit API keys directly to your repository! Always use GitHub Actions secrets.**
To securely use your Anthropic API key:
1. Add your API key as a repository secret:
- Go to your repository's Settings
- Navigate to "Secrets and variables" → "Actions"
- Click "New repository secret"
- Name it `ANTHROPIC_API_KEY`
- Paste your API key as the value
2. Reference the secret in your workflow:
```yaml
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
```
**Never do this:**
```yaml
# ❌ WRONG - Exposes your API key
anthropic_api_key: "sk-ant-..."
```
**Always do this:**
```yaml
# ✅ CORRECT - Uses GitHub secrets
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
```
This applies to all sensitive values including API keys, access tokens, and credentials.
We also reccomend that you always use short-lived tokens when possible
Having issues or questions? Check out our [Frequently Asked Questions](./docs/faq.md) for solutions to common problems and detailed explanations of Claude's capabilities and limitations.
## License
+20
View File
@@ -0,0 +1,20 @@
# Claude Code GitHub Action Roadmap
Thank you for trying out the beta of our GitHub Action! This document outlines our path to `v1.0`. Items are not necessarily in priority order.
## Path to 1.0
- ~**Ability to see GitHub Action CI results** - This will enable Claude to look at CI failures and make updates to PRs to fix test failures, lint errors, and the like.~
- **Cross-repo support** - Enable Claude to work across multiple repositories in a single session
- **Ability to modify workflow files** - Let Claude update GitHub Actions workflows and other CI configuration files
- **Support for workflow_dispatch and repository_dispatch events** - Dispatch Claude on events triggered via API from other workflows or from other services
- **Ability to disable commit signing** - Option to turn off GPG signing for environments where it's not required. This will enable Claude to use normal `git` bash commands for committing. This will likely become the default behavior once added.
- **Better code review behavior** - Support inline comments on specific lines, provide higher quality reviews with more actionable feedback
- ~**Support triggering @claude from bot users** - Allow automation and bot accounts to invoke Claude~
- **Customizable base prompts** - Full control over Claude's initial context with template variables like `$PR_COMMENTS`, `$PR_FILES`, etc. Users can replace our default prompt entirely while still accessing key contextual data
---
**Note:** This roadmap represents our current vision for reaching `v1.0` and is subject to change based on user feedback and development priorities.
We welcome feedback on these planned features! If you're interested in contributing to any of these features, please open an issue to discuss implementation details with us. We're also open to suggestions for new features not listed here.
+3 -3
View File
@@ -8,8 +8,8 @@ This repository is maintained by [Anthropic](https://www.anthropic.com/).
The security of our systems and user data is Anthropics top priority. We appreciate the work of security researchers acting in good faith in identifying and reporting potential vulnerabilities.
Our security program is managed on HackerOne and we ask that any validated vulnerability in this functionality be reported through their [submission form](https://hackerone.com/anthropic-vdp/reports/new?type=team&report_type=vulnerability).
Our security program is managed on HackerOne and we ask that any validated vulnerability in this functionality be reported through their [submission form](https://hackerone.com/4f1f16ba-10d3-4d09-9ecc-c721aad90f24/embedded_submissions/new).
## Vulnerability Disclosure Program
## Anthropic Bug Bounty
Our Vulnerability Program Guidelines are defined on our [HackerOne program page](https://hackerone.com/anthropic-vdp).
Our Bug Bounty Program Guidelines are defined on our [HackerOne program page](https://hackerone.com/anthropic).
+372 -82
View File
@@ -1,5 +1,5 @@
name: "Claude Code Action Official"
description: "General-purpose Claude agent for GitHub PRs and issues. Can answer questions and implement code changes."
name: "Claude Code Action v1.0"
description: "Flexible GitHub automation platform with Claude. Auto-detects mode based on event type: PR reviews, @claude mentions, or custom automation."
branding:
icon: "at-sign"
color: "orange"
@@ -12,34 +12,78 @@ inputs:
assignee_trigger:
description: "The assignee username that triggers the action (e.g. @claude)"
required: false
label_trigger:
description: "The label that triggers the action (e.g. claude)"
required: false
default: "claude"
base_branch:
description: "The branch to use as the base/source when creating new branches (defaults to repository default branch)"
required: false
branch_prefix:
description: "The prefix to use for Claude branches (defaults to 'claude/', use 'claude-' for dash format)"
required: false
default: "claude/"
branch_name_template:
description: "Template for branch naming. Available variables: {{prefix}}, {{entityType}}, {{entityNumber}}, {{timestamp}}, {{sha}}, {{label}}, {{description}}. {{label}} will be first label from the issue/PR, or {{entityType}} as a fallback. {{description}} will be the first 5 words of the issue/PR title in kebab-case. Default: '{{prefix}}{{entityType}}-{{entityNumber}}-{{timestamp}}'"
required: false
default: ""
allowed_bots:
description: "Comma-separated list of allowed bot usernames, or '*' to allow all bots. Empty string (default) allows no bots. WARNING: On public repos with '*', external Apps may be able to invoke this action with prompts they control. See docs/security.md."
required: false
default: ""
allowed_non_write_users:
description: |
Comma-separated list of usernames to allow without write permissions, or '*' to allow all users.
Only works when github_token input is provided. WARNING: Use with extreme caution - this
bypasses security checks and should only be used for workflows with very limited permissions
(e.g., issue labeling).
SECURITY: Processing untrusted content exposes the workflow to prompt injection. When this
input is set, Claude does a best-effort scrub of Anthropic, cloud, and GitHub Actions secrets
from subprocess environments. This reduces but does not eliminate prompt injection risk -
only use for workflows with very limited permissions and validate all outputs.
required: false
default: ""
include_comments_by_actor:
description: "Comma-separated list of actor usernames to INCLUDE in comments. Supports wildcards: '*[bot]' matches all bots, 'dependabot[bot]' matches specific bot. Empty (default) includes all actors."
required: false
default: ""
exclude_comments_by_actor:
description: "Comma-separated list of actor usernames to EXCLUDE from comments. Supports wildcards: '*[bot]' matches all bots, 'renovate[bot]' matches specific bot. Empty (default) excludes none. If actor is in both lists, exclusion takes priority."
required: false
default: ""
# Claude Code configuration
model:
description: "Model to use (provider-specific format required for Bedrock/Vertex)"
required: false
anthropic_model:
description: "DEPRECATED: Use 'model' instead. Model to use (provider-specific format required for Bedrock/Vertex)"
required: false
allowed_tools:
description: "Additional tools for Claude to use (the base GitHub tools will always be included)"
prompt:
description: "Instructions for Claude. Can be a direct prompt or custom template."
required: false
default: ""
disallowed_tools:
description: "Tools that Claude should never use"
required: false
default: ""
custom_instructions:
description: "Additional custom instructions to include in the prompt for Claude"
required: false
default: ""
direct_prompt:
description: "Direct instruction for Claude (bypasses normal trigger detection)"
settings:
description: "Claude Code settings as JSON string or path to settings JSON file"
required: false
default: ""
# Auth configuration
anthropic_api_key:
description: "Anthropic API key (required for direct API, not needed for Bedrock/Vertex)"
description: "Anthropic API key (required for direct API, not needed for Bedrock/Vertex/Foundry)"
required: false
claude_code_oauth_token:
description: "Claude Code OAuth token (alternative to anthropic_api_key)"
required: false
anthropic_federation_rule_id:
description: "Workload identity federation rule ID (fdrl_...). When set with anthropic_organization_id, the action authenticates to the Claude API by exchanging the workflow's GitHub OIDC token instead of using a static API key. Requires `id-token: write` permission."
required: false
anthropic_organization_id:
description: "Anthropic organization UUID used for workload identity federation"
required: false
anthropic_service_account_id:
description: "Service account ID (svac_...) the federated token acts as (optional, used with workload identity federation)"
required: false
anthropic_workspace_id:
description: "Workspace ID (wrkspc_...) for workload identity federation. Optional when the federation rule targets a single workspace."
required: false
anthropic_oidc_audience:
description: "Audience to request on the GitHub OIDC token used for workload identity federation. Defaults to https://api.anthropic.com."
required: false
github_token:
description: "GitHub token with repo and pull request permissions (optional if using GitHub App)"
@@ -52,70 +96,262 @@ inputs:
description: "Use Google Vertex AI with OIDC authentication instead of direct Anthropic API"
required: false
default: "false"
timeout_minutes:
description: "Timeout in minutes for execution"
use_foundry:
description: "Use Microsoft Foundry with OIDC authentication instead of direct Anthropic API"
required: false
default: "30"
default: "false"
claude_args:
description: "Additional arguments to pass directly to Claude CLI"
required: false
default: ""
additional_permissions:
description: "Additional GitHub permissions to request (e.g., 'actions: read')"
required: false
default: ""
use_sticky_comment:
description: "Use just one comment to deliver issue/PR comments"
required: false
default: "false"
classify_inline_comments:
description: "Buffer inline comments without confirmed=true and classify them (real review vs test/probe) before posting after the session ends. Set to 'false' to post all inline comments immediately (pre-buffering behavior)."
required: false
default: "true"
use_commit_signing:
description: "Enable commit signing using GitHub's commit signature verification. When false, Claude uses standard git commands"
required: false
default: "false"
ssh_signing_key:
description: "SSH private key for signing commits. When provided, git will be configured to use SSH signing. Takes precedence over use_commit_signing."
required: false
default: ""
bot_id:
description: "GitHub user ID to use for git operations (defaults to Claude's bot ID)"
required: false
default: "41898282" # Claude's bot ID - see src/github/constants.ts
bot_name:
description: "GitHub username to use for git operations (defaults to Claude's bot name)"
required: false
default: "claude[bot]"
track_progress:
description: "Force tag mode with tracking comments for pull_request and issue events. Only applicable to pull_request (opened, synchronize, ready_for_review, reopened, labeled) and issue (opened, edited, labeled, assigned) events."
required: 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:
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
default: ""
path_to_bun_executable:
description: "Optional path to a custom Bun executable. If provided, skips automatic Bun installation and uses this executable instead. WARNING: Using an incompatible version may cause problems if the action requires specific Bun features. This input is typically not needed unless you're debugging something specific or have unique needs in your environment."
required: false
default: ""
display_report:
description: "Whether to display the Claude Code Report in GitHub Step Summary. Set to 'false' to disable when using custom formatting solutions. WARNING: This outputs Claude-authored content in the GitHub Step Summary. This should only be used in cases where the action is used solely with trusted input."
required: false
default: "false"
show_full_output:
description: "Show full JSON output from Claude Code. WARNING: This outputs ALL Claude messages including tool execution results which may contain secrets, API keys, or other sensitive information. These logs are publicly visible in GitHub Actions. Only enable for debugging in non-sensitive environments."
required: false
default: "false"
plugins:
description: "Newline-separated list of Claude Code plugin names to install (e.g., 'code-review@claude-code-plugins\nfeature-dev@claude-code-plugins')"
required: false
default: ""
plugin_marketplaces:
description: "Newline-separated list of Claude Code plugin marketplace Git URLs to install from (e.g., 'https://github.com/user/marketplace1.git\nhttps://github.com/user/marketplace2.git')"
required: false
default: ""
outputs:
conclusion:
description: "Execution status of Claude Code ('success' or 'failure')"
value: ${{ steps.run.outputs.conclusion }}
execution_file:
description: "Path to the Claude Code execution output file"
value: ${{ steps.claude-code.outputs.execution_file }}
value: ${{ steps.run.outputs.execution_file }}
branch_name:
description: "The branch created by Claude Code for this execution"
value: ${{ steps.run.outputs.branch_name }}
github_token:
description: "The GitHub token used by the action (Claude App token if available)"
value: ${{ steps.run.outputs.github_token }}
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"
value: ${{ steps.run.outputs.structured_output }}
session_id:
description: "The Claude Code session ID that can be used with --resume to continue this conversation"
value: ${{ steps.run.outputs.session_id }}
runs:
using: "composite"
steps:
- name: Install Bun
uses: oven-sh/setup-bun@v2
id: setup-bun
if: inputs.path_to_bun_executable == ''
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # https://github.com/oven-sh/setup-bun/releases/tag/v2.2.0
with:
bun-version: 1.2.11
bun-version: 1.3.14
token: ${{ inputs.github_token || github.token }}
# Disable setup-bun's cache. The upstream save step uses a deterministic
# key (Bun version) and isn't ref-aware: on every second-and-subsequent
# run against the same PR ref the GitHub cache API rejects the duplicate
# key+ref with a 409 (HTML body), and @actions/cache treats the unparsable
# response as transient and burns ~20-30s on 5 retries before warning.
# The 35 MB Bun binary downloads in 2-3s, so disabling the cache is a net
# wallclock win and removes the noisy warning. See issue #1252.
no-cache: true
- name: Setup Custom Bun Path
if: inputs.path_to_bun_executable != ''
shell: bash
env:
PATH_TO_BUN_EXECUTABLE: ${{ inputs.path_to_bun_executable }}
run: |
echo "Using custom Bun executable: $PATH_TO_BUN_EXECUTABLE"
# Add the directory containing the custom executable to PATH
BUN_DIR=$(dirname "$PATH_TO_BUN_EXECUTABLE")
echo "$BUN_DIR" >> "$GITHUB_PATH"
- name: Install Dependencies
shell: bash
run: |
cd ${{ github.action_path }}
bun install
cd ${GITHUB_ACTION_PATH}
bun install --production
- name: Prepare action
id: prepare
- name: Install subprocess isolation dependencies
# Install subprocess isolation dependencies when processing content from non-write users.
# Best-effort: skips on non-Linux or when sudo/apt unavailable (self-hosted runners).
if: ${{ inputs.allowed_non_write_users != '' && runner.os == 'Linux' }}
continue-on-error: true
shell: bash
run: |
bun run ${{ github.action_path }}/src/entrypoints/prepare.ts
if [ "${CLAUDE_CODE_SUBPROCESS_ENV_SCRUB:-}" = "0" ]; then
echo "Subprocess isolation opted out via CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=0"
exit 0
fi
if command -v apt-get >/dev/null && command -v sudo >/dev/null; then
for i in 1 2 3; do
sudo apt-get update -qq && sudo apt-get install -y --no-install-recommends bubblewrap socat && break
echo "apt-get attempt $i failed, retrying..."
sleep 5
done
fi
# Ubuntu 24.04+ restricts unprivileged user namespaces via AppArmor.
# The sysctl doesn't exist on older kernels — that's fine.
if [ -f /proc/sys/kernel/apparmor_restrict_unprivileged_userns ] && command -v sudo >/dev/null; then
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
fi
- name: Pin bun binary for post-steps
if: ${{ inputs.allowed_non_write_users != '' }}
continue-on-error: true
shell: bash
env:
PATH_TO_BUN_EXECUTABLE: ${{ inputs.path_to_bun_executable }}
SETUP_BUN_PATH: ${{ steps.setup-bun.outputs.bun-path }}
run: |
# Keep a copy of the bun binary alongside the action's own files so
# post-steps use the same version the action installed or was given.
mkdir -p "$GITHUB_ACTION_PATH/bin"
for bun_path in "$PATH_TO_BUN_EXECUTABLE" "$SETUP_BUN_PATH" "$(command -v bun || true)"; do
if [ -n "$bun_path" ] && [ -x "$bun_path" ]; then
cp "$bun_path" "$GITHUB_ACTION_PATH/bin/bun"
break
fi
done
test -x "$GITHUB_ACTION_PATH/bin/bun"
- name: Prepend system bin dirs to PATH
if: ${{ inputs.allowed_non_write_users != '' && runner.os != 'Windows' }}
continue-on-error: true
shell: /bin/bash --noprofile --norc -e -o pipefail {0}
run: |
echo "/usr/bin" >> "$GITHUB_PATH"
echo "/bin" >> "$GITHUB_PATH"
- name: Run Claude Code Action
id: run
shell: bash
run: |
# Do NOT pass --tsconfig-override here. It triggers a Bun runtime bug
# ("Internal error: directory mismatch for directory .../tsconfig.json")
# that aborts the run with exit code 1. Bun already auto-discovers the
# action's own tsconfig.json by walking up from the entry file, so the
# override is redundant. See oven-sh/bun#25730.
bun --no-env-file \
--config="${GITHUB_ACTION_PATH}/bunfig.toml" \
run ${GITHUB_ACTION_PATH}/src/entrypoints/run.ts
env:
# Prepare inputs
MODE: ${{ inputs.mode }}
PROMPT: ${{ inputs.prompt }}
TRIGGER_PHRASE: ${{ inputs.trigger_phrase }}
ASSIGNEE_TRIGGER: ${{ inputs.assignee_trigger }}
ALLOWED_TOOLS: ${{ inputs.allowed_tools }}
CUSTOM_INSTRUCTIONS: ${{ inputs.custom_instructions }}
DIRECT_PROMPT: ${{ inputs.direct_prompt }}
LABEL_TRIGGER: ${{ inputs.label_trigger }}
BASE_BRANCH: ${{ inputs.base_branch }}
BRANCH_PREFIX: ${{ inputs.branch_prefix }}
BRANCH_NAME_TEMPLATE: ${{ inputs.branch_name_template }}
OVERRIDE_GITHUB_TOKEN: ${{ inputs.github_token }}
ALLOWED_BOTS: ${{ inputs.allowed_bots }}
ALLOWED_NON_WRITE_USERS: ${{ inputs.allowed_non_write_users }}
CLAUDE_CODE_SUBPROCESS_ENV_SCRUB: ${{ env.CLAUDE_CODE_SUBPROCESS_ENV_SCRUB || (inputs.allowed_non_write_users != '' && '1') || '' }}
CLAUDE_CODE_SCRIPT_CAPS: ${{ env.CLAUDE_CODE_SCRIPT_CAPS || '' }}
INCLUDE_COMMENTS_BY_ACTOR: ${{ inputs.include_comments_by_actor }}
EXCLUDE_COMMENTS_BY_ACTOR: ${{ inputs.exclude_comments_by_actor }}
GITHUB_RUN_ID: ${{ github.run_id }}
USE_STICKY_COMMENT: ${{ inputs.use_sticky_comment }}
CLASSIFY_INLINE_COMMENTS: ${{ inputs.classify_inline_comments }}
DEFAULT_WORKFLOW_TOKEN: ${{ github.token }}
USE_COMMIT_SIGNING: ${{ inputs.use_commit_signing }}
SSH_SIGNING_KEY: ${{ inputs.ssh_signing_key }}
BOT_ID: ${{ inputs.bot_id }}
BOT_NAME: ${{ inputs.bot_name }}
TRACK_PROGRESS: ${{ inputs.track_progress }}
INCLUDE_FIX_LINKS: ${{ inputs.include_fix_links }}
ADDITIONAL_PERMISSIONS: ${{ inputs.additional_permissions }}
CLAUDE_ARGS: ${{ inputs.claude_args }}
ALL_INPUTS: ${{ toJson(inputs) }}
# Base-action inputs
INPUT_PROMPT_FILE: ${{ runner.temp }}/claude-prompts/claude-prompt.txt
INPUT_SETTINGS: ${{ inputs.settings }}
INPUT_EXPERIMENTAL_SLASH_COMMANDS_DIR: ${{ github.action_path }}/slash-commands
INPUT_PATH_TO_CLAUDE_CODE_EXECUTABLE: ${{ inputs.path_to_claude_code_executable }}
INPUT_PATH_TO_BUN_EXECUTABLE: ${{ inputs.path_to_bun_executable }}
INPUT_SHOW_FULL_OUTPUT: ${{ inputs.show_full_output }}
DISPLAY_REPORT: ${{ inputs.display_report }}
INPUT_PLUGINS: ${{ inputs.plugins }}
INPUT_PLUGIN_MARKETPLACES: ${{ inputs.plugin_marketplaces }}
PATH_TO_CLAUDE_CODE_EXECUTABLE: ${{ inputs.path_to_claude_code_executable }}
- name: Run Claude Code
id: claude-code
if: steps.prepare.outputs.contains_trigger == 'true'
uses: anthropics/claude-code-base-action@beta
with:
prompt_file: /tmp/claude-prompts/claude-prompt.txt
allowed_tools: ${{ env.ALLOWED_TOOLS }}
disallowed_tools: ${{ env.DISALLOWED_TOOLS }}
timeout_minutes: ${{ inputs.timeout_minutes }}
model: ${{ inputs.model || inputs.anthropic_model }}
mcp_config: ${{ steps.prepare.outputs.mcp_config }}
use_bedrock: ${{ inputs.use_bedrock }}
use_vertex: ${{ inputs.use_vertex }}
anthropic_api_key: ${{ inputs.anthropic_api_key }}
env:
# Model configuration
ANTHROPIC_MODEL: ${{ inputs.model || inputs.anthropic_model }}
GITHUB_TOKEN: ${{ steps.prepare.outputs.GITHUB_TOKEN }}
NODE_VERSION: ${{ env.NODE_VERSION }}
# Provider configuration
ANTHROPIC_API_KEY: ${{ inputs.anthropic_api_key || env.ANTHROPIC_API_KEY }}
CLAUDE_CODE_OAUTH_TOKEN: ${{ inputs.claude_code_oauth_token || env.CLAUDE_CODE_OAUTH_TOKEN }}
ANTHROPIC_FEDERATION_RULE_ID: ${{ inputs.anthropic_federation_rule_id }}
ANTHROPIC_ORGANIZATION_ID: ${{ inputs.anthropic_organization_id }}
ANTHROPIC_SERVICE_ACCOUNT_ID: ${{ inputs.anthropic_service_account_id }}
ANTHROPIC_WORKSPACE_ID: ${{ inputs.anthropic_workspace_id }}
ANTHROPIC_OIDC_AUDIENCE: ${{ inputs.anthropic_oidc_audience }}
ANTHROPIC_BASE_URL: ${{ env.ANTHROPIC_BASE_URL }}
ANTHROPIC_CUSTOM_HEADERS: ${{ env.ANTHROPIC_CUSTOM_HEADERS }}
CLAUDE_CODE_USE_BEDROCK: ${{ inputs.use_bedrock == 'true' && '1' || '' }}
CLAUDE_CODE_USE_VERTEX: ${{ inputs.use_vertex == 'true' && '1' || '' }}
CLAUDE_CODE_USE_FOUNDRY: ${{ inputs.use_foundry == 'true' && '1' || '' }}
# AWS configuration
AWS_REGION: ${{ env.AWS_REGION }}
AWS_ACCESS_KEY_ID: ${{ env.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ env.AWS_SECRET_ACCESS_KEY }}
AWS_SESSION_TOKEN: ${{ env.AWS_SESSION_TOKEN }}
ANTHROPIC_BEDROCK_BASE_URL: ${{ env.ANTHROPIC_BEDROCK_BASE_URL }}
AWS_BEARER_TOKEN_BEDROCK: ${{ env.AWS_BEARER_TOKEN_BEDROCK }}
ANTHROPIC_BEDROCK_BASE_URL: ${{ env.ANTHROPIC_BEDROCK_BASE_URL || (env.AWS_REGION && format('https://bedrock-runtime.{0}.amazonaws.com', env.AWS_REGION)) }}
# GCP configuration
ANTHROPIC_VERTEX_PROJECT_ID: ${{ env.ANTHROPIC_VERTEX_PROJECT_ID }}
@@ -128,42 +364,96 @@ runs:
VERTEX_REGION_CLAUDE_3_5_SONNET: ${{ env.VERTEX_REGION_CLAUDE_3_5_SONNET }}
VERTEX_REGION_CLAUDE_3_7_SONNET: ${{ env.VERTEX_REGION_CLAUDE_3_7_SONNET }}
- name: Update comment with job link
if: steps.prepare.outputs.contains_trigger == 'true' && steps.prepare.outputs.claude_comment_id && always()
shell: bash
run: |
bun run ${{ github.action_path }}/src/entrypoints/update-comment-link.ts
env:
REPOSITORY: ${{ github.repository }}
PR_NUMBER: ${{ github.event.issue.number || github.event.pull_request.number }}
CLAUDE_COMMENT_ID: ${{ steps.prepare.outputs.claude_comment_id }}
GITHUB_RUN_ID: ${{ github.run_id }}
GITHUB_TOKEN: ${{ steps.prepare.outputs.GITHUB_TOKEN }}
GITHUB_EVENT_NAME: ${{ github.event_name }}
TRIGGER_COMMENT_ID: ${{ github.event.comment.id }}
CLAUDE_BRANCH: ${{ steps.prepare.outputs.CLAUDE_BRANCH }}
IS_PR: ${{ github.event.issue.pull_request != null || github.event_name == 'pull_request_review_comment' }}
DEFAULT_BRANCH: ${{ steps.prepare.outputs.DEFAULT_BRANCH }}
CLAUDE_SUCCESS: ${{ steps.claude-code.outputs.conclusion == 'success' }}
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 || '' }}
# Microsoft Foundry configuration
ANTHROPIC_FOUNDRY_RESOURCE: ${{ env.ANTHROPIC_FOUNDRY_RESOURCE }}
ANTHROPIC_FOUNDRY_BASE_URL: ${{ env.ANTHROPIC_FOUNDRY_BASE_URL }}
ANTHROPIC_DEFAULT_SONNET_MODEL: ${{ env.ANTHROPIC_DEFAULT_SONNET_MODEL }}
ANTHROPIC_DEFAULT_HAIKU_MODEL: ${{ env.ANTHROPIC_DEFAULT_HAIKU_MODEL }}
ANTHROPIC_DEFAULT_OPUS_MODEL: ${{ env.ANTHROPIC_DEFAULT_OPUS_MODEL }}
- name: Display Claude Code Report
if: steps.prepare.outputs.contains_trigger == 'true' && steps.claude-code.outputs.execution_file != ''
# MCP configuration — these env vars are read directly from process.env by the
# Claude CLI subprocess. They must be listed explicitly here because this step's
# env: block shadows the calling workflow's job-level env vars (GitHub Actions
# composite action behavior). Set these in your workflow's job-level env: or via
# a prior step that writes to $GITHUB_ENV.
MCP_TIMEOUT: ${{ env.MCP_TIMEOUT }}
MCP_TOOL_TIMEOUT: ${{ env.MCP_TOOL_TIMEOUT }}
MAX_MCP_OUTPUT_TOKENS: ${{ env.MAX_MCP_OUTPUT_TOKENS }}
# Telemetry configuration
CLAUDE_CODE_ENABLE_TELEMETRY: ${{ env.CLAUDE_CODE_ENABLE_TELEMETRY }}
OTEL_METRICS_EXPORTER: ${{ env.OTEL_METRICS_EXPORTER }}
OTEL_LOGS_EXPORTER: ${{ env.OTEL_LOGS_EXPORTER }}
OTEL_EXPORTER_OTLP_PROTOCOL: ${{ env.OTEL_EXPORTER_OTLP_PROTOCOL }}
OTEL_EXPORTER_OTLP_ENDPOINT: ${{ env.OTEL_EXPORTER_OTLP_ENDPOINT }}
OTEL_EXPORTER_OTLP_HEADERS: ${{ env.OTEL_EXPORTER_OTLP_HEADERS }}
OTEL_METRIC_EXPORT_INTERVAL: ${{ env.OTEL_METRIC_EXPORT_INTERVAL }}
OTEL_LOGS_EXPORT_INTERVAL: ${{ env.OTEL_LOGS_EXPORT_INTERVAL }}
OTEL_RESOURCE_ATTRIBUTES: ${{ env.OTEL_RESOURCE_ATTRIBUTES }}
- name: Re-prepend system bin dirs to PATH
if: ${{ always() && inputs.allowed_non_write_users != '' && runner.os != 'Windows' }}
continue-on-error: true
shell: /bin/bash --noprofile --norc -e -o pipefail {0}
env:
BASH_ENV: ""
LD_PRELOAD: ""
LD_LIBRARY_PATH: ""
NODE_OPTIONS: ""
DYLD_INSERT_LIBRARIES: ""
DYLD_PRELOAD: ""
DYLD_LIBRARY_PATH: ""
DYLD_FRAMEWORK_PATH: ""
run: |
echo "/usr/bin" >> "$GITHUB_PATH"
echo "/bin" >> "$GITHUB_PATH"
{
echo "BASH_ENV="
echo "LD_PRELOAD="
echo "LD_LIBRARY_PATH="
echo "DYLD_INSERT_LIBRARIES="
echo "DYLD_PRELOAD="
echo "DYLD_LIBRARY_PATH="
echo "DYLD_FRAMEWORK_PATH="
} >> "$GITHUB_ENV"
- name: Cleanup SSH signing key
if: always() && inputs.ssh_signing_key != ''
shell: bash
run: |
echo "## Claude Code Report" >> $GITHUB_STEP_SUMMARY
echo '```json' >> $GITHUB_STEP_SUMMARY
cat "${{ steps.claude-code.outputs.execution_file }}" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
BUN_BIN="${GITHUB_ACTION_PATH}/bin/bun"
[ -x "$BUN_BIN" ] || BUN_BIN="bun"
# No --tsconfig-override: see the "Run Claude Code Action" step above.
"$BUN_BIN" --no-env-file \
--config="${GITHUB_ACTION_PATH}/bunfig.toml" \
run ${GITHUB_ACTION_PATH}/src/entrypoints/cleanup-ssh-signing.ts
- name: Post buffered inline comments
if: always() && inputs.classify_inline_comments != 'false'
shell: bash
env:
GITHUB_TOKEN: ${{ steps.run.outputs.github_token || inputs.github_token || github.token }}
REPO_OWNER: ${{ github.event.repository.owner.login }}
REPO_NAME: ${{ github.event.repository.name }}
PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }}
ANTHROPIC_API_KEY: ${{ inputs.anthropic_api_key }}
run: |
BUN_BIN="${GITHUB_ACTION_PATH}/bin/bun"
[ -x "$BUN_BIN" ] || BUN_BIN="bun"
# No --tsconfig-override: see the "Run Claude Code Action" step above.
"$BUN_BIN" --no-env-file \
--config="${GITHUB_ACTION_PATH}/bunfig.toml" \
run ${GITHUB_ACTION_PATH}/src/entrypoints/post-buffered-inline-comments.ts
- name: Revoke app token
if: always() && inputs.github_token == ''
if: always() && inputs.github_token == '' && steps.run.outputs.github_token != '' && steps.run.outputs.skipped_due_to_workflow_validation_mismatch != 'true'
shell: bash
run: |
curl -L \
--connect-timeout 5 \
--max-time 10 \
-X DELETE \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ steps.prepare.outputs.GITHUB_TOKEN }}" \
-H "Authorization: Bearer ${{ steps.run.outputs.github_token }}" \
-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 || true
+123
View File
@@ -0,0 +1,123 @@
# Agent Approval Check
Require **N human approvals** on any pull request that contains commits
authored by an AI agent (Claude, Claude Code, or any bot identity you
configure). PRs without agent activity are unaffected.
This is the same gate Anthropic runs internally on every agent-authored PR.
## What it does
When a PR is opened, pushed to, or commented on, this action:
1. Scans the PR's commits, author, and reviews for the configured agent
identities (committer email, bot login, or an `APPROVED` review from a
bot). If none are found it posts `success: No agent activity` and stops.
2. Counts distinct human approvals: the latest `APPROVED` review per login,
plus any `/approve <head-sha>` comment whose SHA matches the current
head. Only users with write access to the repo count (verified per-user
via the collaborators permission API); agent and excluded-bot logins
never count.
3. Posts an `agent-approval-check` commit status (`success` once the count
reaches `required_approvals`, otherwise `pending`) and a sticky PR
comment explaining what's still needed.
4. Re-evaluates on every new push or comment. A push moves the head SHA,
so earlier `/approve <old-sha>` comments are flagged stale. Approving
reviews still count toward the threshold — they're picked up the next
time the workflow runs (on push or `/approve`); they just don't trigger
a run on their own.
Mark `agent-approval-check` as a **required status check** on your protected
branches and GitHub will refuse to merge until it's green.
## Setup
Copy [`examples/agent-approval-check.yml`](../examples/agent-approval-check.yml)
into `.github/workflows/` in your repo, then add `agent-approval-check` to the
required status checks on your protected branch.
This action is designed to run **alongside** GitHub's native branch
protection, not replace it. On the same protected branch you should also:
1. Require at least 1 approving review from someone with write access.
2. Enable **Dismiss stale pull request approvals when new commits are pushed**.
```yaml
name: agent-approval-check
on:
pull_request_target:
types: [opened, synchronize, reopened, ready_for_review]
issue_comment:
types: [created]
permissions:
contents: read
pull-requests: write
statuses: write
jobs:
check:
if: github.event_name != 'issue_comment' || github.event.issue.pull_request
runs-on: ubuntu-latest
steps:
- uses: anthropics/claude-code-action/agent-approval-check@main
with:
required_approvals: 2
agent_emails: noreply@anthropic.com
agent_logins: claude[bot],claude-code[bot]
```
## Inputs
| Input | Default | Meaning |
| ---------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- |
| `required_approvals` | `2` | Distinct human approvals needed. |
| `agent_emails` | `noreply@anthropic.com` | Committer emails that mark a commit agent-authored. |
| `agent_logins` | `claude[bot],claude-code[bot]` | Logins treated as agents (PR author or approving reviewer). |
| `excluded_approvers` | _(empty)_ | Logins whose approvals never count. |
| `exempt_head_branches` | _(empty)_ | Head-branch globs that auto-pass. ⚠️ Leave empty — branch names are attacker-controlled, so this is not a safe place to encode trust. |
| `exempt_path_prefixes` | _(empty)_ | PRs touching only these prefixes auto-pass. |
| `protected_bases` | _(default branch)_ | Base branches this check gates (see threat model). |
| `config_file` | _(empty)_ | Path to an [agent-identities YAML](./agent-identities.example.yaml) replacing the inline inputs. See the warning below. |
| `docs_url` | this README | Link in the PR comment footer. |
| `github_token` | `${{ github.token }}` | Needs `statuses:write` + `pull-requests:write`. |
> ⚠️ **`config_file` and checkout:** if you set `config_file`, your workflow
> must check out the **base** branch to read it (the default behaviour of
> `actions/checkout` under `pull_request_target`). Never check out the PR
> head ref — doing so would let the PR author control the config and bypass
> this check.
## Approving
A human counts as an approver by either:
- submitting a normal GitHub **Approve** review, or
- commenting `/approve <sha>` where `<sha>` is the current head commit
(1240 hex chars). This path lets the PR author — who can't approve their
own PR in GitHub's UI — vouch for commits an agent pushed on their behalf.
The author's `/approve` is subject to the same write-access verification
as any other approver, so a fork-PR author without write access on the
base repository cannot self-count. The author counts as **one** approval;
the remaining approvals must come from other reviewers with write access.
## Threat model
- **Tamper-proof triggers.** `pull_request_target` and `issue_comment` run
the workflow file from the base/default branch, so the PR under review
cannot edit this check. `pull_request_review` does **not** share this
property — it runs from the merge ref — so the example workflow omits it;
native Approve reviews are picked up on the next synchronize or
`/approve` comment. This tamper-resistance assumes the workflow file
itself is protected: an actor who can push workflow changes to the
default branch can spoof any required status check, including this one,
so protect `.github/workflows/` via branch protection or CODEOWNERS.
- **Fail-closed.** Any unhandled error exits non-zero; the required status
stays non-success and the PR stays blocked. PRs with >100 commits are
treated as agent-authored because the full commit list can't be verified.
- **Sibling-PR guard.** Commit statuses attach to a SHA, not a PR. The
action refuses to post a status on a PR whose base isn't in
`protected_bases`, and withholds `success` while another open PR to a
protected base shares the same head commit — otherwise a green status on
one PR would also unblock the other.
- **No checkout of PR code.** The action never checks out the PR's branch;
it reads PR metadata via the GitHub API, so the usual
`pull_request_target` code-execution risk does not apply.
+72
View File
@@ -0,0 +1,72 @@
name: Agent Approval Check
description: |
Require N human approvals on PRs that contain agent-authored commits
(Claude, Claude Code, or any configured bot identity). Posts an
`agent-approval-check` commit status — mark it as a required check on
protected branches to gate merges.
inputs:
github_token:
description: Token with statuses:write and pull-requests:write on this repo.
default: ${{ github.token }}
required_approvals:
description: Number of distinct human approvals required. Must be >= 1.
default: "2"
agent_emails:
description: Comma-separated committer emails treated as agent-authored.
default: noreply@anthropic.com
agent_logins:
description: |
Comma-separated GitHub logins treated as agents — a PR opened by, or an
APPROVED review from, one of these triggers the check.
default: claude[bot],claude-code[bot]
excluded_approvers:
description: Comma-separated logins whose approvals never count (e.g. rubber-stamp bots).
default: ""
exempt_head_branches:
description: |
Comma-separated glob patterns; PRs from matching head branches auto-pass.
WARNING: leave empty — branch names are attacker-controlled, so this is
not a safe place to encode trust.
default: ""
exempt_path_prefixes:
description: Comma-separated path prefixes; PRs touching only these auto-pass.
default: ""
protected_bases:
description: |
Comma-separated base branches this check gates. Empty = the repo's
default branch only. PRs targeting any other base are refused (no
status posted) so a sibling PR sharing the head SHA can't get the
shared commit stamped green.
default: ""
config_file:
description: Optional path to an agent-identities YAML file (overrides the inline inputs).
default: ""
docs_url:
description: Link shown in the PR comment footer.
default: "https://github.com/anthropics/claude-code-action/tree/main/agent-approval-check"
runs:
using: composite
steps:
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.12"
- run: pip install 'httpx==0.28.1' 'pyyaml==6.0.3' 'tenacity==9.1.4'
shell: bash
- run: python "${{ github.action_path }}/agent_approval_check.py"
shell: bash
env:
GH_TOKEN: ${{ inputs.github_token }}
GH_REPOSITORY: ${{ github.repository }}
GH_EVENT_NAME: ${{ github.event_name }}
GH_EVENT_PATH: ${{ github.event_path }}
REQUIRED_APPROVALS: ${{ inputs.required_approvals }}
AGENT_EMAILS: ${{ inputs.agent_emails }}
AGENT_LOGINS: ${{ inputs.agent_logins }}
EXCLUDED_APPROVERS: ${{ inputs.excluded_approvers }}
EXEMPT_HEAD_BRANCHES: ${{ inputs.exempt_head_branches }}
EXEMPT_PATH_PREFIXES: ${{ inputs.exempt_path_prefixes }}
PROTECTED_BASES: ${{ inputs.protected_bases }}
CONFIG_FILE: ${{ inputs.config_file }}
DOCS_URL: ${{ inputs.docs_url }}
@@ -0,0 +1,33 @@
---
# Optional config-file form of the agent-approval-check inputs.
# Pass via `with: { config_file: .github/agent-identities.yaml }` instead of
# the inline `agent_emails` / `agent_logins` / … inputs.
# Committer emails that mark a commit as agent-authored.
agent_emails:
- noreply@anthropic.com
# GitHub logins treated as agents — a PR opened by, or an APPROVED review
# from, one of these triggers the check.
agent_app_logins:
- claude[bot]
- claude-code[bot]
# Logins whose approvals never count toward the required total.
excluded_approver_logins: []
# Head-branch glob patterns that auto-pass. Leave empty: branch names are
# attacker-controlled, so this is not a safe place to encode trust.
exempt_head_branches: []
# Per-repo path prefixes whose PRs auto-pass when ONLY those paths change.
exempt_path_prefixes:
owner/repo:
- docs/
# Per-repo base branches this check gates. A repo with no entry defaults to
# its default branch only. Listing a repo here REPLACES that default.
protected_bases:
owner/repo:
exact: [main]
prefixes: [release/]
File diff suppressed because it is too large Load Diff
+4
View File
@@ -0,0 +1,4 @@
.DS_Store
node_modules
**/.claude/settings.local.json
+2
View File
@@ -0,0 +1,2 @@
engine-strict=true
registry=https://registry.npmjs.org/
+1
View File
@@ -0,0 +1 @@
{}
+58
View File
@@ -0,0 +1,58 @@
# CLAUDE.md
## Common Commands
### Development Commands
- Build/Type check: `bun run typecheck`
- Format code: `bun run format`
- Check formatting: `bun run format:check`
- Run tests: `bun test`
- Install dependencies: `bun install`
### Action Testing
- Test action locally: `./test-local.sh`
- Test specific file: `bun test test/prepare-prompt.test.ts`
## Architecture Overview
This is a GitHub Action that allows running Claude Code within GitHub workflows. The action consists of:
### Core Components
1. **Action Definition** (`action.yml`): Defines inputs, outputs, and the composite action steps
2. **Prompt Preparation** (`src/index.ts`): Runs Claude Code with specified arguments
### Key Design Patterns
- Uses Bun runtime for development and execution
- JSON streaming output format for execution logs
- Composite action pattern to orchestrate multiple steps
- Provider-agnostic design supporting Anthropic API, AWS Bedrock, and Google Vertex AI
## Provider Authentication
1. **Anthropic API** (default): Requires API key via `anthropic_api_key` input
2. **AWS Bedrock**: Uses OIDC authentication when `use_bedrock: true`
3. **Google Vertex AI**: Uses OIDC authentication when `use_vertex: true`
## Testing Strategy
### Local Testing
- Use `act` tool to run GitHub Actions workflows locally
- `test-local.sh` script automates local testing setup
- Requires `ANTHROPIC_API_KEY` environment variable
### Test Structure
- Unit tests for configuration logic
- Integration tests for prompt preparation
- Full workflow tests in `.github/workflows/test-base-action.yml`
## Important Technical Details
- Outputs execution logs as JSON to `/tmp/claude-execution-output.json`
- Timeout enforcement via `timeout` command wrapper
- Strict TypeScript configuration with Bun-specific settings
+128
View File
@@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
- Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or
advances of any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email
address, without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
claude-code-action-coc@anthropic.com.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
+136
View File
@@ -0,0 +1,136 @@
# Contributing to Claude Code Base Action
Thank you for your interest in contributing to Claude Code Base Action! This document provides guidelines and instructions for contributing to the project.
## Getting Started
### Prerequisites
- [Bun](https://bun.sh/) runtime
- [Docker](https://www.docker.com/) (for running GitHub Actions locally)
- [act](https://github.com/nektos/act) (installed automatically by our test script)
- An Anthropic API key (for testing)
### Setup
1. Fork the repository on GitHub and clone your fork:
```bash
git clone https://github.com/your-username/claude-code-base-action.git
cd claude-code-base-action
```
2. Install dependencies:
```bash
bun install
```
3. Set up your Anthropic API key:
```bash
export ANTHROPIC_API_KEY="your-api-key-here"
```
## Development
### Available Scripts
- `bun test` - Run all tests
- `bun run typecheck` - Type check the code
- `bun run format` - Format code with Prettier
- `bun run format:check` - Check code formatting
## Testing
### Running Tests Locally
1. **Unit Tests**:
```bash
bun test
```
2. **Integration Tests** (using GitHub Actions locally):
```bash
./test-local.sh
```
This script:
- Installs `act` if not present (requires Homebrew on macOS)
- Runs the GitHub Action workflow locally using Docker
- Requires your `ANTHROPIC_API_KEY` to be set
On Apple Silicon Macs, the script automatically adds the `--container-architecture linux/amd64` flag to avoid compatibility issues.
## Pull Request Process
1. Create a new branch from `main`:
```bash
git checkout -b feature/your-feature-name
```
2. Make your changes and commit them:
```bash
git add .
git commit -m "feat: add new feature"
```
3. Run tests and formatting:
```bash
bun test
bun run typecheck
bun run format:check
```
4. Push your branch and create a Pull Request:
```bash
git push origin feature/your-feature-name
```
5. Ensure all CI checks pass
6. Request review from maintainers
## Action Development
### Testing Your Changes
When modifying the action:
1. Test locally with the test script:
```bash
./test-local.sh
```
2. Test in a real GitHub Actions workflow by:
- Creating a test repository
- Using your branch as the action source:
```yaml
uses: your-username/claude-code-base-action@your-branch
```
### Debugging
- Use `console.log` for debugging in development
- Check GitHub Actions logs for runtime issues
- Use `act` with `-v` flag for verbose output:
```bash
act push -v --secret ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY"
```
## Common Issues
### Docker Issues
Make sure Docker is running before using `act`. You can check with:
```bash
docker ps
```
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Anthropic, PBC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+11
View File
@@ -0,0 +1,11 @@
# ⚠️ This is a Mirror Repository
This repository is an automated mirror of the `base-action` directory from [anthropics/claude-code-action](https://github.com/anthropics/claude-code-action).
**Do not submit PRs or issues to this repository.** Instead, please contribute to the main repository:
- 🐛 [Report issues](https://github.com/anthropics/claude-code-action/issues)
- 🔧 [Submit pull requests](https://github.com/anthropics/claude-code-action/pulls)
- 📖 [View documentation](https://github.com/anthropics/claude-code-action#readme)
---
+556
View File
@@ -0,0 +1,556 @@
# Claude Code Base Action
This GitHub Action allows you to run [Claude Code](https://www.anthropic.com/claude-code) within your GitHub Actions workflows. You can use this to build any custom workflow on top of Claude Code.
For simply tagging @claude in issues and PRs out of the box, [check out the Claude Code action and GitHub app](https://github.com/anthropics/claude-code-action).
## Trust model
This action is a thin wrapper that installs and runs Claude Code with the inputs you provide. It does **not** enforce any trust boundaries on its own. Running this action in a directory is equivalent to running Claude Code in that directory — Claude reads project-level configuration (`.claude/`, `CLAUDE.md`, `.mcp.json`, etc.) from the working directory, and the action's own setup steps run from there as well.
**The caller is responsible for ensuring the working directory and prompt are trusted.** If your workflow processes untrusted input (issues, fork pull requests, external comments), use [`anthropics/claude-code-action`](https://github.com/anthropics/claude-code-action) instead — it provides actor permission checks, restores project configuration from the base ref in PR contexts, and is the supported path for those scenarios.
See [Claude Code's security documentation](https://docs.anthropic.com/en/docs/claude-code/security) and the [GitHub Actions guidance on `pull_request_target`](https://securitylab.github.com/research/github-actions-preventing-pwn-requests/) for background.
## Usage
Add the following to your workflow file:
```yaml
# Using a direct prompt
- name: Run Claude Code with direct prompt
uses: anthropics/claude-code-base-action@beta
with:
prompt: "Your prompt here"
claude_args: '--allowedTools "Bash(git:*),Read,Glob,Grep"'
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
# Or using a prompt from a file
- name: Run Claude Code with prompt file
uses: anthropics/claude-code-base-action@beta
with:
prompt_file: "/path/to/prompt.txt"
claude_args: '--allowedTools "Bash(git:*),Read,Glob,Grep"'
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
# Or limiting the conversation turns
- name: Run Claude Code with limited turns
uses: anthropics/claude-code-base-action@beta
with:
prompt: "Your prompt here"
claude_args: |
--allowedTools "Bash(git:*),Read,Glob,Grep"
--max-turns 5
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
# Using custom system prompts
- name: Run Claude Code with custom system prompt
uses: anthropics/claude-code-base-action@beta
with:
prompt: "Build a REST API"
claude_args: |
--system-prompt "You are a senior backend engineer. Focus on security, performance, and maintainability."
--allowedTools "Bash(git:*),Read,Glob,Grep"
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
# Or appending to the default system prompt
- name: Run Claude Code with appended system prompt
uses: anthropics/claude-code-base-action@beta
with:
prompt: "Create a database schema"
claude_args: |
--append-system-prompt "After writing code, be sure to code review yourself."
--allowedTools "Bash(git:*),Read,Glob,Grep"
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
# Using custom environment variables
- name: Run Claude Code with custom environment variables
uses: anthropics/claude-code-base-action@beta
with:
prompt: "Deploy to staging environment"
settings: |
{
"env": {
"ENVIRONMENT": "staging",
"API_URL": "https://api-staging.example.com",
"DEBUG": "true"
}
}
claude_args: '--allowedTools "Bash(git:*),Read,Glob,Grep"'
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
# Using fallback model for handling API errors
- name: Run Claude Code with fallback model
uses: anthropics/claude-code-base-action@beta
with:
prompt: "Review and fix TypeScript errors"
claude_args: |
--model "claude-opus-4-1-20250805"
--fallback-model "claude-sonnet-4-20250514"
--allowedTools "Bash(git:*),Read,Glob,Grep"
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
# Using OAuth token instead of API key
- name: Run Claude Code with OAuth token
uses: anthropics/claude-code-base-action@beta
with:
prompt: "Update dependencies"
claude_args: '--allowedTools "Bash(git:*),Read,Glob,Grep"'
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
```
### Workload Identity Federation
Instead of a static API key or OAuth token, you can authenticate via [Workload Identity Federation](https://platform.claude.com/docs/en/manage-claude/workload-identity-federation): the action fetches the workflow's GitHub OIDC token and the Claude Code CLI exchanges it for a short-lived access token. Requires the `id-token: write` permission on the job:
```yaml
permissions:
contents: read
id-token: write
steps:
- name: Run Claude Code with workload identity federation
uses: anthropics/claude-code-base-action@beta
with:
prompt: "Your prompt here"
anthropic_federation_rule_id: fdrl_xxxxxxxxxxxx
anthropic_organization_id: 00000000-0000-0000-0000-000000000000
anthropic_service_account_id: svac_xxxxxxxxxxxx
```
Do not set `anthropic_api_key` or `claude_code_oauth_token` alongside the federation inputs — a static credential takes precedence and federation will not be used.
## Inputs
| Input | Description | Required | Default |
| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | -------- | ------------- |
| `prompt` | The prompt to send to Claude Code | No\* | `''` |
| `prompt_file` | Path to a file containing the prompt to send to Claude Code | No\* | `''` |
| `settings` | Claude Code settings as a JSON string or path to a settings JSON file | No | `''` |
| `claude_args` | Additional arguments to pass directly to the Claude CLI | No | `''` |
| `anthropic_api_key` | Anthropic API key for direct Anthropic API authentication | No | `''` |
| `claude_code_oauth_token` | Claude Code OAuth token as an alternative to an Anthropic API key | No | `''` |
| `anthropic_federation_rule_id` | Workload identity federation rule ID (fdrl\_...). Requires `id-token: write` permission | No | `''` |
| `anthropic_organization_id` | Anthropic organization UUID used for workload identity federation | No | `''` |
| `anthropic_service_account_id` | Service account ID (svac\_...) the federated token acts as | No | `''` |
| `anthropic_workspace_id` | Workspace ID (wrkspc\_...) for federation | No | `''` |
| `anthropic_oidc_audience` | Audience for the GitHub OIDC token request | No | `''` |
| `use_bedrock` | Use Amazon Bedrock with OIDC authentication | No | `'false'` |
| `use_vertex` | Use Google Vertex AI with OIDC authentication | No | `'false'` |
| `use_foundry` | Use Microsoft Foundry with OIDC authentication | No | `'false'` |
| `use_node_cache` | Enable Node.js dependency caching for projects with lock files | No | `'false'` |
| `path_to_claude_code_executable` | Path to a custom Claude Code executable | No | `''` |
| `path_to_bun_executable` | Path to a custom Bun executable | No | `''` |
| `show_full_output` | Show full JSON output (⚠️ May expose secrets - see [security docs](../docs/security.md#-full-output-security-warning)) | No | `'false'`\*\* |
| `plugins` | Newline-separated Claude Code plugin names to install | No | `''` |
| `plugin_marketplaces` | Newline-separated plugin marketplace Git URLs to install | No | `''` |
\*Either `prompt` or `prompt_file` must be provided, but not both.
\*\*`show_full_output` is automatically enabled when GitHub Actions debug mode is active. See [security documentation](../docs/security.md#-full-output-security-warning) for important security considerations.
## Outputs
| Output | Description |
| ------------------- | ------------------------------------------------------------------------------------------------- |
| `conclusion` | Execution status of Claude Code ('success' or 'failure') |
| `execution_file` | Path to the JSON file containing Claude Code execution log |
| `structured_output` | JSON string containing structured output fields when `--json-schema` is provided in `claude_args` |
| `session_id` | The Claude Code session ID that can be used with `--resume` to continue this conversation |
## Environment Variables
The following environment variables can be used to configure the action:
| Variable | Description | Default |
| -------------- | ----------------------------------------------------- | ------- |
| `NODE_VERSION` | Node.js version to use (e.g., '18.x', '20.x', '22.x') | '18.x' |
Example usage:
```yaml
- name: Run Claude Code with Node.js 20
uses: anthropics/claude-code-base-action@beta
env:
NODE_VERSION: "20.x"
with:
prompt: "Your prompt here"
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
```
## Custom Environment Variables
You can pass custom environment variables to Claude Code through the `env` object in `settings`:
```yaml
- name: Deploy with custom environment
uses: anthropics/claude-code-base-action@beta
with:
prompt: "Deploy the application to the staging environment"
settings: |
{
"env": {
"ENVIRONMENT": "staging",
"API_BASE_URL": "https://api-staging.example.com",
"DATABASE_URL": "${{ secrets.STAGING_DB_URL }}",
"DEBUG": "true",
"LOG_LEVEL": "debug"
}
}
claude_args: '--allowedTools "Bash(git:*),Read,Glob,Grep"'
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
```
The `settings` input accepts either inline JSON or a path to a settings JSON file. Values in the `env` object are available during the Claude Code session and can reference GitHub secrets.
## Using Settings Configuration
You can provide Claude Code settings configuration in two ways:
### Option 1: Settings Configuration File
Provide a path to a JSON file containing Claude Code settings:
```yaml
- name: Run Claude Code with settings file
uses: anthropics/claude-code-base-action@beta
with:
prompt: "Your prompt here"
settings: "path/to/settings.json"
claude_args: '--allowedTools "Bash(git:*),Read,Glob,Grep"'
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
```
### Option 2: Inline Settings Configuration
Provide the settings configuration directly as a JSON string:
```yaml
- name: Run Claude Code with inline settings
uses: anthropics/claude-code-base-action@beta
with:
prompt: "Your prompt here"
settings: |
{
"model": "claude-opus-4-1-20250805",
"env": {
"DEBUG": "true",
"API_URL": "https://api.example.com"
},
"permissions": {
"allow": ["Bash", "Read"],
"deny": ["WebFetch"]
},
"hooks": {
"PreToolUse": [{
"matcher": "Bash",
"hooks": [{
"type": "command",
"command": "echo Running bash command..."
}]
}]
}
}
claude_args: '--allowedTools "Bash(git:*),Read,Glob,Grep"'
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
```
The settings file supports all Claude Code settings options including:
- `model`: Override the default model
- `env`: Environment variables for the session
- `permissions`: Tool usage permissions
- `hooks`: Pre/post tool execution hooks
- `includeCoAuthoredBy`: Include co-authored-by in git commits
- And more...
**Note**: The `enableAllProjectMcpServers` setting is always set to `true` by this action to ensure MCP servers work correctly.
## Using MCP Config
You can provide MCP configuration in two ways:
### Option 1: MCP Configuration File
Provide a path to a JSON file containing MCP configuration:
```yaml
- name: Run Claude Code with MCP config file
uses: anthropics/claude-code-base-action@beta
with:
prompt: "Your prompt here"
claude_args: |
--mcp-config "path/to/mcp-config.json"
--allowedTools "Bash(git:*),Read,Glob,Grep"
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
```
### Option 2: Inline MCP Configuration
Provide the MCP configuration directly as a JSON string:
```yaml
- name: Run Claude Code with inline MCP config
uses: anthropics/claude-code-base-action@beta
with:
prompt: "Your prompt here"
claude_args: >-
--mcp-config '{"mcpServers":{"server-name":{"command":"node","args":["./server.js"],"env":{"API_KEY":"your-api-key"}}}}'
--allowedTools "Bash(git:*),Read,Glob,Grep"
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
```
The MCP config file should follow this format:
```json
{
"mcpServers": {
"server-name": {
"command": "node",
"args": ["./server.js"],
"env": {
"API_KEY": "your-api-key"
}
}
}
}
```
You can combine MCP config with other inputs like allowed tools:
```yaml
# Using multiple inputs together
- name: Run Claude Code with MCP and custom tools
uses: anthropics/claude-code-base-action@beta
with:
prompt: "Access the custom MCP server and use its tools"
claude_args: |
--mcp-config "mcp-config.json"
--allowedTools "Bash(git:*),Read,mcp__server-name__custom_tool"
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
```
## Example: PR Code Review
```yaml
name: Claude Code Review
on:
pull_request:
types: [opened, synchronize]
jobs:
code-review:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Run Code Review with Claude
id: code-review
uses: anthropics/claude-code-base-action@beta
with:
prompt: "Review the PR changes. Focus on code quality, potential bugs, and performance issues. Suggest improvements where appropriate. Write your review as markdown text."
claude_args: '--allowedTools "Bash(git diff --name-only HEAD~1),Bash(git diff HEAD~1),Read,Glob,Grep,Write"'
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
- name: Extract and Comment PR Review
if: steps.code-review.outputs.conclusion == 'success'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const fs = require('fs');
const executionFile = '${{ steps.code-review.outputs.execution_file }}';
const executionLog = JSON.parse(fs.readFileSync(executionFile, 'utf8'));
// Extract the review content from the execution log.
// The SDK writes top-level events with `type`; assistant text is nested
// under `message.content`.
let review = '';
// Prefer the final result event when it is available.
for (let i = executionLog.length - 1; i >= 0; i--) {
const entry = executionLog[i];
if (entry?.type === 'result' && typeof entry.result === 'string') {
review = entry.result;
break;
}
}
// Fallback to the last assistant text block if no result event was written.
if (!review) {
for (let i = executionLog.length - 1; i >= 0; i--) {
const entry = executionLog[i];
if (entry?.type !== 'assistant' || !Array.isArray(entry.message?.content)) {
continue;
}
review = entry.message.content
.filter((block) => block?.type === 'text' && typeof block.text === 'string')
.map((block) => block.text)
.join('\n');
if (review) {
break;
}
}
}
if (review) {
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: "## Claude Code Review\n\n" + review + "\n\n*Generated by Claude Code*"
});
}
```
For typed automation output, prefer passing `--json-schema` in `claude_args`
and reading `steps.<id>.outputs.structured_output` instead of parsing the full
execution log.
Check out additional examples in [`./examples`](./examples).
## Using Cloud Providers
You can authenticate with Claude using any of these methods:
1. Direct Anthropic API (default) - requires API key or OAuth token
2. Amazon Bedrock - requires OIDC authentication and automatically uses cross-region inference profiles
3. Google Vertex AI - requires OIDC authentication
**Note**:
- Bedrock and Vertex use OIDC authentication exclusively
- AWS Bedrock automatically uses cross-region inference profiles for certain models
- For cross-region inference profile models, you need to request and be granted access to the Claude models in all regions that the inference profile uses
- The Bedrock API endpoint URL is automatically constructed using the AWS_REGION environment variable (e.g., `https://bedrock-runtime.us-west-2.amazonaws.com`)
- You can override the Bedrock API endpoint URL by setting the `ANTHROPIC_BEDROCK_BASE_URL` environment variable
### Model Configuration
Use provider-specific model names based on your chosen provider:
```yaml
# For direct Anthropic API (default)
- name: Run Claude Code with Anthropic API
uses: anthropics/claude-code-base-action@beta
with:
prompt: "Your prompt here"
claude_args: "--model claude-3-7-sonnet-20250219"
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
# For Amazon Bedrock (requires OIDC authentication)
- name: Configure AWS Credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
aws-region: us-west-2
- name: Run Claude Code with Bedrock
uses: anthropics/claude-code-base-action@beta
with:
prompt: "Your prompt here"
claude_args: "--model anthropic.claude-3-7-sonnet-20250219-v1:0"
use_bedrock: "true"
# For Google Vertex AI (requires OIDC authentication)
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@v2
with:
workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }}
service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }}
- name: Run Claude Code with Vertex AI
uses: anthropics/claude-code-base-action@beta
with:
prompt: "Your prompt here"
claude_args: "--model claude-3-7-sonnet@20250219"
use_vertex: "true"
```
## Example: Using OIDC Authentication for AWS Bedrock
This example shows how to use OIDC authentication with AWS Bedrock:
```yaml
- name: Configure AWS Credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
aws-region: us-west-2
- name: Run Claude Code with AWS OIDC
uses: anthropics/claude-code-base-action@beta
with:
prompt: "Your prompt here"
use_bedrock: "true"
claude_args: |
--model "anthropic.claude-3-7-sonnet-20250219-v1:0"
--allowedTools "Bash(git:*),Read,Glob,Grep"
```
## Example: Using OIDC Authentication for GCP Vertex AI
This example shows how to use OIDC authentication with GCP Vertex AI:
```yaml
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@v2
with:
workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }}
service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }}
- name: Run Claude Code with GCP OIDC
uses: anthropics/claude-code-base-action@beta
with:
prompt: "Your prompt here"
use_vertex: "true"
claude_args: |
--model "claude-3-7-sonnet@20250219"
--allowedTools "Bash(git:*),Read,Glob,Grep"
```
## Security Best Practices
**⚠️ IMPORTANT: Never commit API keys directly to your repository! Always use GitHub Actions secrets.**
To securely use your Anthropic API key:
1. Add your API key as a repository secret:
- Go to your repository's Settings
- Navigate to "Secrets and variables" → "Actions"
- Click "New repository secret"
- Name it `ANTHROPIC_API_KEY`
- Paste your API key as the value
2. Reference the secret in your workflow:
```yaml
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
```
**Never do this:**
```yaml
# ❌ WRONG - Exposes your API key
anthropic_api_key: "sk-ant-..."
```
**Always do this:**
```yaml
# ✅ CORRECT - Uses GitHub secrets
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
```
This applies to all sensitive values including API keys, access tokens, and credentials.
We also recommend that you always use short-lived tokens when possible
## License
This project is licensed under the MIT License—see the LICENSE file for details.
+245
View File
@@ -0,0 +1,245 @@
name: "Claude Code Base Action"
description: "Run Claude Code in GitHub Actions workflows"
branding:
icon: "code"
color: "orange"
inputs:
# Claude Code arguments
prompt:
description: "The prompt to send to Claude Code (mutually exclusive with prompt_file)"
required: false
default: ""
prompt_file:
description: "Path to a file containing the prompt to send to Claude Code (mutually exclusive with prompt)"
required: false
default: ""
settings:
description: "Claude Code settings as JSON string or path to settings JSON file"
required: false
default: ""
# Action settings
claude_args:
description: "Additional arguments to pass directly to Claude CLI (e.g., '--max-turns 3 --mcp-config /path/to/config.json')"
required: false
default: ""
# Authentication settings
anthropic_api_key:
description: "Anthropic API key (required for direct Anthropic API)"
required: false
default: ""
claude_code_oauth_token:
description: "Claude Code OAuth token (alternative to anthropic_api_key)"
required: false
default: ""
anthropic_federation_rule_id:
description: "Workload identity federation rule ID (fdrl_...). When set with anthropic_organization_id, the action authenticates to the Claude API by exchanging the workflow's GitHub OIDC token instead of using a static API key. Requires `id-token: write` permission."
required: false
default: ""
anthropic_organization_id:
description: "Anthropic organization UUID used for workload identity federation"
required: false
default: ""
anthropic_service_account_id:
description: "Service account ID (svac_...) the federated token acts as (optional, used with workload identity federation)"
required: false
default: ""
anthropic_workspace_id:
description: "Workspace ID (wrkspc_...) for workload identity federation. Optional when the federation rule targets a single workspace."
required: false
default: ""
anthropic_oidc_audience:
description: "Audience to request on the GitHub OIDC token used for workload identity federation. Defaults to https://api.anthropic.com."
required: false
default: ""
use_bedrock:
description: "Use Amazon Bedrock with OIDC authentication instead of direct Anthropic API"
required: false
default: "false"
use_vertex:
description: "Use Google Vertex AI with OIDC authentication instead of direct Anthropic API"
required: false
default: "false"
use_foundry:
description: "Use Microsoft Foundry with OIDC authentication instead of direct Anthropic API"
required: false
default: "false"
use_node_cache:
description: "Whether to use Node.js dependency caching (set to true only for Node.js projects with lock files)"
required: false
default: "false"
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."
required: false
default: ""
path_to_bun_executable:
description: "Optional path to a custom Bun executable. If provided, skips automatic Bun installation and uses this executable instead. WARNING: Using an incompatible version may cause problems if the action requires specific Bun features. This input is typically not needed unless you're debugging something specific or have unique needs in your environment."
required: false
default: ""
show_full_output:
description: "Show full JSON output from Claude Code. WARNING: This outputs ALL Claude messages including tool execution results which may contain secrets, API keys, or other sensitive information. These logs are publicly visible in GitHub Actions. Only enable for debugging in non-sensitive environments."
required: false
default: "false"
plugins:
description: "Newline-separated list of Claude Code plugin names to install (e.g., 'code-review@claude-code-plugins\nfeature-dev@claude-code-plugins')"
required: false
default: ""
plugin_marketplaces:
description: "Newline-separated list of Claude Code plugin marketplace Git URLs to install from (e.g., 'https://github.com/user/marketplace1.git\nhttps://github.com/user/marketplace2.git')"
required: false
default: ""
outputs:
conclusion:
description: "Execution status of Claude Code ('success' or 'failure')"
value: ${{ steps.run_claude.outputs.conclusion }}
execution_file:
description: "Path to the JSON file containing Claude Code execution log"
value: ${{ steps.run_claude.outputs.execution_file }}
structured_output:
description: "JSON string containing all structured output fields when --json-schema is provided in claude_args (use fromJSON() or jq to parse)"
value: ${{ steps.run_claude.outputs.structured_output }}
session_id:
description: "The Claude Code session ID that can be used with --resume to continue this conversation"
value: ${{ steps.run_claude.outputs.session_id }}
runs:
using: "composite"
steps:
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # https://github.com/actions/setup-node/releases/tag/v6.4.0
with:
node-version: ${{ env.NODE_VERSION || '18.x' }}
cache: ${{ inputs.use_node_cache == 'true' && 'npm' || '' }}
package-manager-cache: false
- name: Install Bun
if: inputs.path_to_bun_executable == ''
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # https://github.com/oven-sh/setup-bun/releases/tag/v2.2.0
with:
bun-version: 1.3.14
# Disable setup-bun's cache. See action.yml for details and issue #1252.
no-cache: true
- name: Setup Custom Bun Path
if: inputs.path_to_bun_executable != ''
shell: bash
env:
PATH_TO_BUN_EXECUTABLE: ${{ inputs.path_to_bun_executable }}
run: |
echo "Using custom Bun executable: $PATH_TO_BUN_EXECUTABLE"
# Add the directory containing the custom executable to PATH
BUN_DIR=$(dirname "$PATH_TO_BUN_EXECUTABLE")
echo "$BUN_DIR" >> "$GITHUB_PATH"
- name: Install Dependencies
shell: bash
run: |
cd ${GITHUB_ACTION_PATH}
bun install --production
- name: Install Claude Code
shell: bash
env:
PATH_TO_CLAUDE_CODE_EXECUTABLE: ${{ inputs.path_to_claude_code_executable }}
run: |
if [ -z "$PATH_TO_CLAUDE_CODE_EXECUTABLE" ]; then
CLAUDE_CODE_VERSION="2.1.274"
echo "Installing Claude Code v${CLAUDE_CODE_VERSION}..."
for attempt in 1 2 3; do
echo "Installation attempt $attempt..."
if command -v timeout &> /dev/null; then
# Use --foreground to kill entire process group on timeout, --kill-after to send SIGKILL if SIGTERM fails
timeout --foreground --kill-after=10 120 bash -c "curl -fsSL https://claude.ai/install.sh | bash -s -- $CLAUDE_CODE_VERSION" && break
else
curl -fsSL https://claude.ai/install.sh | bash -s -- "$CLAUDE_CODE_VERSION" && break
fi
if [ $attempt -eq 3 ]; then
echo "Failed to install Claude Code after 3 attempts"
exit 1
fi
echo "Installation failed, retrying..."
sleep 5
done
echo "Claude Code installed successfully"
# Add ~/.local/bin to PATH so the claude executable is available in subsequent steps
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
else
echo "Using custom Claude Code executable: $PATH_TO_CLAUDE_CODE_EXECUTABLE"
# Add the directory containing the custom executable to PATH
CLAUDE_DIR=$(dirname "$PATH_TO_CLAUDE_CODE_EXECUTABLE")
echo "$CLAUDE_DIR" >> "$GITHUB_PATH"
fi
- name: Run Claude Code Action
shell: bash
id: run_claude
run: |
# Change to CLAUDE_WORKING_DIR if set (for running in custom directories)
if [ -n "$CLAUDE_WORKING_DIR" ]; then
echo "Changing directory to CLAUDE_WORKING_DIR: $CLAUDE_WORKING_DIR"
cd "$CLAUDE_WORKING_DIR"
fi
bun run ${GITHUB_ACTION_PATH}/src/index.ts
env:
# Model configuration
CLAUDE_CODE_ACTION: "1"
INPUT_PROMPT: ${{ inputs.prompt }}
INPUT_PROMPT_FILE: ${{ inputs.prompt_file }}
INPUT_SETTINGS: ${{ inputs.settings }}
INPUT_CLAUDE_ARGS: ${{ inputs.claude_args }}
INPUT_PATH_TO_CLAUDE_CODE_EXECUTABLE: ${{ inputs.path_to_claude_code_executable }}
INPUT_PATH_TO_BUN_EXECUTABLE: ${{ inputs.path_to_bun_executable }}
INPUT_SHOW_FULL_OUTPUT: ${{ inputs.show_full_output }}
INPUT_PLUGINS: ${{ inputs.plugins }}
INPUT_PLUGIN_MARKETPLACES: ${{ inputs.plugin_marketplaces }}
# Provider configuration
ANTHROPIC_API_KEY: ${{ inputs.anthropic_api_key }}
CLAUDE_CODE_OAUTH_TOKEN: ${{ inputs.claude_code_oauth_token }}
ANTHROPIC_FEDERATION_RULE_ID: ${{ inputs.anthropic_federation_rule_id }}
ANTHROPIC_ORGANIZATION_ID: ${{ inputs.anthropic_organization_id }}
ANTHROPIC_SERVICE_ACCOUNT_ID: ${{ inputs.anthropic_service_account_id }}
ANTHROPIC_WORKSPACE_ID: ${{ inputs.anthropic_workspace_id }}
ANTHROPIC_OIDC_AUDIENCE: ${{ inputs.anthropic_oidc_audience }}
ANTHROPIC_BASE_URL: ${{ env.ANTHROPIC_BASE_URL }}
ANTHROPIC_CUSTOM_HEADERS: ${{ env.ANTHROPIC_CUSTOM_HEADERS }}
# Only set provider flags if explicitly true, since any value (including "false") is truthy
CLAUDE_CODE_USE_BEDROCK: ${{ inputs.use_bedrock == 'true' && '1' || '' }}
CLAUDE_CODE_USE_VERTEX: ${{ inputs.use_vertex == 'true' && '1' || '' }}
CLAUDE_CODE_USE_FOUNDRY: ${{ inputs.use_foundry == 'true' && '1' || '' }}
# AWS configuration
AWS_REGION: ${{ env.AWS_REGION }}
AWS_ACCESS_KEY_ID: ${{ env.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ env.AWS_SECRET_ACCESS_KEY }}
AWS_SESSION_TOKEN: ${{ env.AWS_SESSION_TOKEN }}
AWS_BEARER_TOKEN_BEDROCK: ${{ env.AWS_BEARER_TOKEN_BEDROCK }}
ANTHROPIC_BEDROCK_BASE_URL: ${{ env.ANTHROPIC_BEDROCK_BASE_URL || (env.AWS_REGION && format('https://bedrock-runtime.{0}.amazonaws.com', env.AWS_REGION)) }}
# GCP configuration
ANTHROPIC_VERTEX_PROJECT_ID: ${{ env.ANTHROPIC_VERTEX_PROJECT_ID }}
CLOUD_ML_REGION: ${{ env.CLOUD_ML_REGION }}
GOOGLE_APPLICATION_CREDENTIALS: ${{ env.GOOGLE_APPLICATION_CREDENTIALS }}
ANTHROPIC_VERTEX_BASE_URL: ${{ env.ANTHROPIC_VERTEX_BASE_URL }}
# Microsoft Foundry configuration
ANTHROPIC_FOUNDRY_RESOURCE: ${{ env.ANTHROPIC_FOUNDRY_RESOURCE }}
ANTHROPIC_FOUNDRY_BASE_URL: ${{ env.ANTHROPIC_FOUNDRY_BASE_URL }}
ANTHROPIC_DEFAULT_SONNET_MODEL: ${{ env.ANTHROPIC_DEFAULT_SONNET_MODEL }}
ANTHROPIC_DEFAULT_HAIKU_MODEL: ${{ env.ANTHROPIC_DEFAULT_HAIKU_MODEL }}
ANTHROPIC_DEFAULT_OPUS_MODEL: ${{ env.ANTHROPIC_DEFAULT_OPUS_MODEL }}
# Telemetry configuration
CLAUDE_CODE_ENABLE_TELEMETRY: ${{ env.CLAUDE_CODE_ENABLE_TELEMETRY }}
OTEL_METRICS_EXPORTER: ${{ env.OTEL_METRICS_EXPORTER }}
OTEL_LOGS_EXPORTER: ${{ env.OTEL_LOGS_EXPORTER }}
OTEL_EXPORTER_OTLP_PROTOCOL: ${{ env.OTEL_EXPORTER_OTLP_PROTOCOL }}
OTEL_EXPORTER_OTLP_ENDPOINT: ${{ env.OTEL_EXPORTER_OTLP_ENDPOINT }}
OTEL_EXPORTER_OTLP_HEADERS: ${{ env.OTEL_EXPORTER_OTLP_HEADERS }}
OTEL_METRIC_EXPORT_INTERVAL: ${{ env.OTEL_METRIC_EXPORT_INTERVAL }}
OTEL_LOGS_EXPORT_INTERVAL: ${{ env.OTEL_LOGS_EXPORT_INTERVAL }}
OTEL_RESOURCE_ATTRIBUTES: ${{ env.OTEL_RESOURCE_ATTRIBUTES }}
+264
View File
@@ -0,0 +1,264 @@
{
"lockfileVersion": 1,
"configVersion": 0,
"workspaces": {
"": {
"name": "@anthropic-ai/claude-code-base-action",
"dependencies": {
"@actions/core": "^1.10.1",
"@anthropic-ai/claude-agent-sdk": "^0.3.274",
"shell-quote": "^1.8.4",
},
"devDependencies": {
"@types/bun": "^1.2.12",
"@types/node": "^20.0.0",
"@types/shell-quote": "^1.7.5",
"prettier": "3.5.3",
"typescript": "^5.8.3",
},
},
},
"packages": {
"@actions/core": ["@actions/core@1.11.1", "", { "dependencies": { "@actions/exec": "^1.1.1", "@actions/http-client": "^2.0.1" } }, "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A=="],
"@actions/exec": ["@actions/exec@1.1.1", "", { "dependencies": { "@actions/io": "^1.0.1" } }, "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w=="],
"@actions/http-client": ["@actions/http-client@2.2.3", "", { "dependencies": { "tunnel": "^0.0.6", "undici": "^5.25.4" } }, "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA=="],
"@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.3.274", "", { "optionalDependencies": { "@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.274", "@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.274", "@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.274", "@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.274", "@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.274", "@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.274", "@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.274", "@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.274" }, "peerDependencies": { "@anthropic-ai/sdk": ">=0.93.0", "@modelcontextprotocol/sdk": "^1.29.0", "zod": "^4.0.0" } }, "sha512-kFmWMsh/BEd4jKkOxUeihr0xMkaMdOxONhNNkZ2pGmP0ElHkArvFvuZxqJTPvREnhCsPZByv5f0EzyAb/hkRZw=="],
"@anthropic-ai/claude-agent-sdk-darwin-arm64": ["@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.274", "", { "os": "darwin", "cpu": "arm64" }, "sha512-B0uAdUIbUhWuybOT5FkhbdsALGNduyaHYo+bEggKBSQS3RFRjgNbX/ah1+IwkmTn4UKDN7AX86DkTEjpj6TSWQ=="],
"@anthropic-ai/claude-agent-sdk-darwin-x64": ["@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.274", "", { "os": "darwin", "cpu": "x64" }, "sha512-l5pp3Z+z2mti0H3y1O0MRbTg/r+MDm0hly4WpoKmZyIZSOrDQR7jhCqS6Ihj3Vvd7CMP9NZ8t6F0oXVBDCM6YQ=="],
"@anthropic-ai/claude-agent-sdk-linux-arm64": ["@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.274", "", { "os": "linux", "cpu": "arm64" }, "sha512-kYsmSN6zieDwVS3QZBBay9fxNp+h4t4OXleuk6xWhrUDEQRfRQEFlD7JVWWWjJIiHILnniycOxarwo4VpqTf1Q=="],
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl": ["@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.274", "", { "os": "linux", "cpu": "arm64" }, "sha512-pNjZQRF3f1O5JwnoifuAD7tSRfk9LSwwBzEPiUHB4sJgatF6hzqyDgTN05hcDx28xg44j0jpKpyVwsjveI/s4g=="],
"@anthropic-ai/claude-agent-sdk-linux-x64": ["@anthropic-ai/claude-agent-sdk-linux-x64@0.3.274", "", { "os": "linux", "cpu": "x64" }, "sha512-QLeK9LzTtTdCjCDRYUVRHj1I3U6AfMvpXYI4Ur1s+vrUcad/C20kShS5/kgug+rERU5CNxNA9v0A052rt47ZXQ=="],
"@anthropic-ai/claude-agent-sdk-linux-x64-musl": ["@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.274", "", { "os": "linux", "cpu": "x64" }, "sha512-a9eIkmgJ5sSywHwWJcComogjIMsfHsurckQ85ljPNTUSFguzl/ZvbTXGiWNRK5rHsoNd3amwQf2fVC/vFyv3eg=="],
"@anthropic-ai/claude-agent-sdk-win32-arm64": ["@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.274", "", { "os": "win32", "cpu": "arm64" }, "sha512-A5wvZAXL3nhuabIXUcRuoYhuPPdEIt3LoA3jUkVLT3c0H+7SH0rv5bylpM9n+apf7lwLCm2TdMZ6x1Ze1Vzekg=="],
"@anthropic-ai/claude-agent-sdk-win32-x64": ["@anthropic-ai/claude-agent-sdk-win32-x64@0.3.274", "", { "os": "win32", "cpu": "x64" }, "sha512-dGcaLfEpV4kwcSijPAIJru+iEiVhHvbBdkyixPyhhf8pniV9B62nUMFFsjNtYL37xIPHxixh2Gsx9/A+Yk4oww=="],
"@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.93.0", "", { "dependencies": { "json-schema-to-ts": "^3.1.1" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["zod"], "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-q9vaSZQVFx6B/gPxetGYfLXSJD5v0sOmh0OpZDq7yCrTSA+Rscvrtyol7JJTW40wEpQB4U1B4JXzxQitbQ3CAA=="],
"@babel/runtime": ["@babel/runtime@7.29.2", "", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="],
"@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="],
"@hono/node-server": ["@hono/node-server@1.19.11", "", { "peerDependencies": { "hono": "^4" } }, "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g=="],
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.29.0", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ=="],
"@types/bun": ["@types/bun@1.2.19", "", { "dependencies": { "bun-types": "1.2.19" } }, "sha512-d9ZCmrH3CJ2uYKXQIUuZ/pUnTqIvLDS0SK7pFmbx8ma+ziH/FRMoAq5bYpRG7y+w1gl+HgyNZbtqgMq4W4e2Lg=="],
"@types/node": ["@types/node@20.19.9", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-cuVNgarYWZqxRJDQHEB58GEONhOK79QVR/qYx4S7kcUObQvUwvFnYxJuuHUKm2aieN9X3yZB4LZsuYNU1Qphsw=="],
"@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="],
"@types/shell-quote": ["@types/shell-quote@1.7.5", "", {}, "sha512-+UE8GAGRPbJVQDdxi16dgadcBfQ+KG2vgZhV1+3A1XmHbmwcdwhCUwIdy+d3pAGrbvgRoVSjeI9vOWyq376Yzw=="],
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
"ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="],
"ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="],
"body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="],
"bun-types": ["bun-types@1.2.19", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-uAOTaZSPuYsWIXRpj7o56Let0g/wjihKCkeRqUBhlLVM/Bt+Fj9xTo+LhC1OV1XDaGkz4hNC80et5xgy+9KTHQ=="],
"bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
"call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
"content-disposition": ["content-disposition@1.0.1", "", {}, "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q=="],
"content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
"cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
"cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],
"cors": ["cors@2.8.6", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw=="],
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
"encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
"escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
"etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
"eventsource": ["eventsource@3.0.7", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="],
"eventsource-parser": ["eventsource-parser@3.0.6", "", {}, "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg=="],
"express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="],
"express-rate-limit": ["express-rate-limit@8.3.1", "", { "dependencies": { "ip-address": "10.1.0" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw=="],
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
"fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="],
"finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="],
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
"fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
"hono": ["hono@4.12.9", "", {}, "sha512-wy3T8Zm2bsEvxKZM5w21VdHDDcwVS1yUFFY6i8UobSsKfFceT7TOwhbhfKsDyx7tYQlmRM5FLpIuYvNFyjctiA=="],
"http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="],
"iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"ip-address": ["ip-address@10.1.0", "", {}, "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q=="],
"ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
"is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="],
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
"jose": ["jose@6.2.2", "", {}, "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ=="],
"json-schema-to-ts": ["json-schema-to-ts@3.1.1", "", { "dependencies": { "@babel/runtime": "^7.18.3", "ts-algebra": "^2.0.0" } }, "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g=="],
"json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
"json-schema-typed": ["json-schema-typed@8.0.2", "", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="],
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
"media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
"merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],
"mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
"mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
"on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
"parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
"path-to-regexp": ["path-to-regexp@8.4.0", "", {}, "sha512-PuseHIvAnz3bjrM2rGJtSgo1zjgxapTLZ7x2pjhzWwlp4SJQgK3f3iZIQwkpEnBaKz6seKBADpM4B4ySkuYypg=="],
"pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="],
"prettier": ["prettier@3.5.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw=="],
"proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
"qs": ["qs@6.15.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ=="],
"range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
"raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="],
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
"router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="],
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
"send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="],
"serve-static": ["serve-static@2.2.1", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="],
"setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
"shell-quote": ["shell-quote@1.8.4", "", {}, "sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ=="],
"side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="],
"side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="],
"side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="],
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
"statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
"ts-algebra": ["ts-algebra@2.0.0", "", {}, "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw=="],
"tunnel": ["tunnel@0.0.6", "", {}, "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="],
"type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="],
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
"undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
"unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
"zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
"zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="],
}
}
+108
View File
@@ -0,0 +1,108 @@
name: Claude Issue Triage Example
description: Run Claude Code for issue triage in GitHub Actions
on:
issues:
types: [opened]
jobs:
triage-issue:
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
issues: write
steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
fetch-depth: 0
- name: Setup GitHub MCP Server
run: |
mkdir -p /tmp/mcp-config
cat > /tmp/mcp-config/mcp-servers.json << 'EOF'
{
"mcpServers": {
"github": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"-e",
"GITHUB_PERSONAL_ACCESS_TOKEN",
"ghcr.io/github/github-mcp-server:sha-23fa0dd"
],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "${{ secrets.GITHUB_TOKEN }}"
}
}
}
}
EOF
- name: Create triage prompt
run: |
mkdir -p /tmp/claude-prompts
cat > /tmp/claude-prompts/triage-prompt.txt << 'EOF'
You're an issue triage assistant for GitHub issues. Your task is to analyze the issue and select appropriate labels from the provided list.
IMPORTANT: Don't post any comments or messages to the issue. Your only action should be to apply labels.
Issue Information:
- REPO: ${GITHUB_REPOSITORY}
- ISSUE_NUMBER: ${{ github.event.issue.number }}
TASK OVERVIEW:
1. First, fetch the list of labels available in this repository by running: `gh label list`. Run exactly this command with nothing else.
2. Next, use the GitHub tools to get context about the issue:
- You have access to these tools:
- mcp__github__get_issue: Use this to retrieve the current issue's details including title, description, and existing labels
- mcp__github__get_issue_comments: Use this to read any discussion or additional context provided in the comments
- mcp__github__update_issue: Use this to apply labels to the issue (do not use this for commenting)
- mcp__github__search_issues: Use this to find similar issues that might provide context for proper categorization and to identify potential duplicate issues
- mcp__github__list_issues: Use this to understand patterns in how other issues are labeled
- Start by using mcp__github__get_issue to get the issue details
3. Analyze the issue content, considering:
- The issue title and description
- The type of issue (bug report, feature request, question, etc.)
- Technical areas mentioned
- Severity or priority indicators
- User impact
- Components affected
4. Select appropriate labels from the available labels list provided above:
- Choose labels that accurately reflect the issue's nature
- Be specific but comprehensive
- Select priority labels if you can determine urgency (high-priority, med-priority, or low-priority)
- Consider platform labels (android, ios) if applicable
- If you find similar issues using mcp__github__search_issues, consider using a "duplicate" label if appropriate. Only do so if the issue is a duplicate of another OPEN issue.
5. Apply the selected labels:
- Use mcp__github__update_issue to apply your selected labels
- DO NOT post any comments explaining your decision
- DO NOT communicate directly with users
- If no labels are clearly applicable, do not apply any labels
IMPORTANT GUIDELINES:
- Be thorough in your analysis
- Only select labels from the provided list above
- DO NOT post any comments to the issue
- Your ONLY action should be to apply labels using mcp__github__update_issue
- It's okay to not add any labels if none are clearly applicable
EOF
env:
GITHUB_REPOSITORY: ${{ github.repository }}
- name: Run Claude Code for Issue Triage
uses: anthropics/claude-code-base-action@beta
with:
prompt_file: /tmp/claude-prompts/triage-prompt.txt
allowed_tools: "Bash(gh label list),mcp__github__get_issue,mcp__github__get_issue_comments,mcp__github__update_issue,mcp__github__search_issues,mcp__github__list_issues"
claude_args: |
--mcp-config /tmp/mcp-config/mcp-servers.json
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
+196
View File
@@ -0,0 +1,196 @@
{
"name": "@anthropic-ai/claude-code-base-action",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@anthropic-ai/claude-code-base-action",
"version": "1.0.0",
"dependencies": {
"@actions/core": "^1.10.1",
"shell-quote": "^1.8.4"
},
"devDependencies": {
"@types/bun": "^1.2.12",
"@types/node": "^20.0.0",
"@types/shell-quote": "^1.7.5",
"prettier": "3.5.3",
"typescript": "^5.8.3"
}
},
"node_modules/@actions/core": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz",
"integrity": "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==",
"license": "MIT",
"dependencies": {
"@actions/exec": "^1.1.1",
"@actions/http-client": "^2.0.1"
}
},
"node_modules/@actions/exec": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz",
"integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==",
"license": "MIT",
"dependencies": {
"@actions/io": "^1.0.1"
}
},
"node_modules/@actions/http-client": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.3.tgz",
"integrity": "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==",
"license": "MIT",
"dependencies": {
"tunnel": "^0.0.6",
"undici": "^5.25.4"
}
},
"node_modules/@actions/io": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz",
"integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==",
"license": "MIT"
},
"node_modules/@fastify/busboy": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz",
"integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==",
"license": "MIT",
"engines": {
"node": ">=14"
}
},
"node_modules/@types/bun": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.3.1.tgz",
"integrity": "sha512-4jNMk2/K9YJtfqwoAa28c8wK+T7nvJFOjxI4h/7sORWcypRNxBpr+TPNaCfVWq70tLCJsqoFwcf0oI0JU/fvMQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"bun-types": "1.3.1"
}
},
"node_modules/@types/node": {
"version": "20.19.23",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.23.tgz",
"integrity": "sha512-yIdlVVVHXpmqRhtyovZAcSy0MiPcYWGkoO4CGe/+jpP0hmNuihm4XhHbADpK++MsiLHP5MVlv+bcgdF99kSiFQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
}
},
"node_modules/@types/react": {
"version": "19.2.2",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz",
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"csstype": "^3.0.2"
}
},
"node_modules/@types/shell-quote": {
"version": "1.7.5",
"resolved": "https://registry.npmjs.org/@types/shell-quote/-/shell-quote-1.7.5.tgz",
"integrity": "sha512-+UE8GAGRPbJVQDdxi16dgadcBfQ+KG2vgZhV1+3A1XmHbmwcdwhCUwIdy+d3pAGrbvgRoVSjeI9vOWyq376Yzw==",
"dev": true,
"license": "MIT"
},
"node_modules/bun-types": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.3.1.tgz",
"integrity": "sha512-NMrcy7smratanWJ2mMXdpatalovtxVggkj11bScuWuiOoXTiKIu2eVS1/7qbyI/4yHedtsn175n4Sm4JcdHLXw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
},
"peerDependencies": {
"@types/react": "^19"
}
},
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"dev": true,
"license": "MIT",
"peer": true
},
"node_modules/prettier": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
"dev": true,
"license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/shell-quote": {
"version": "1.8.4",
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.4.tgz",
"integrity": "sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/tunnel": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==",
"license": "MIT",
"engines": {
"node": ">=0.6.11 <=0.7.0 || >=0.7.3"
}
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/undici": {
"version": "5.29.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz",
"integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==",
"license": "MIT",
"dependencies": {
"@fastify/busboy": "^2.0.0"
},
"engines": {
"node": ">=14.0"
}
},
"node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"dev": true,
"license": "MIT"
}
}
}
+24
View File
@@ -0,0 +1,24 @@
{
"name": "@anthropic-ai/claude-code-base-action",
"version": "1.0.0",
"private": true,
"scripts": {
"format": "prettier --write .",
"format:check": "prettier --check .",
"install-hooks": "bun run scripts/install-hooks.sh",
"test": "bun test",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@actions/core": "^1.10.1",
"@anthropic-ai/claude-agent-sdk": "^0.3.274",
"shell-quote": "^1.8.4"
},
"devDependencies": {
"@types/bun": "^1.2.12",
"@types/node": "^20.0.0",
"@types/shell-quote": "^1.7.5",
"prettier": "3.5.3",
"typescript": "^5.8.3"
}
}
+13
View File
@@ -0,0 +1,13 @@
#!/bin/sh
# Install git hooks
echo "Installing git hooks..."
# Make sure hooks directory exists
mkdir -p .git/hooks
# Install pre-push hook
cp scripts/pre-push .git/hooks/pre-push
chmod +x .git/hooks/pre-push
echo "Git hooks installed successfully!"
+42
View File
@@ -0,0 +1,42 @@
import * as core from "@actions/core";
import { existsSync } from "fs";
import { writeFile } from "fs/promises";
import { join } from "path";
const EXECUTION_FILENAME = "claude-execution-output.json";
export function getExecutionFilePath(): string | undefined {
if (!process.env.RUNNER_TEMP) {
return undefined;
}
return join(process.env.RUNNER_TEMP, EXECUTION_FILENAME);
}
export async function writeExecutionFile(
messages: unknown[],
): Promise<string | undefined> {
const executionFile = getExecutionFilePath();
if (!executionFile) {
core.warning("Failed to write execution file: RUNNER_TEMP is not set");
return undefined;
}
try {
await writeFile(executionFile, JSON.stringify(messages, null, 2));
console.log(`Log saved to ${executionFile}`);
return executionFile;
} catch (error) {
core.warning(`Failed to write execution file: ${error}`);
return undefined;
}
}
export function setExecutionFileOutputIfPresent(): string | undefined {
const executionFile = getExecutionFilePath();
if (!executionFile || !existsSync(executionFile)) {
return undefined;
}
core.setOutput("execution_file", executionFile);
return executionFile;
}
+86
View File
@@ -0,0 +1,86 @@
#!/usr/bin/env bun
import * as core from "@actions/core";
import { preparePrompt } from "./prepare-prompt";
import { runClaude } from "./run-claude";
import { setupClaudeCodeSettings } from "./setup-claude-code-settings";
import { validateEnvironmentVariables } from "./validate-env";
import { installPlugins } from "./install-plugins";
import { setExecutionFileOutputIfPresent } from "./execution-file";
import { setupWorkloadIdentity } from "./workload-identity";
import type { WorkloadIdentityHandle } from "./workload-identity";
async function run() {
let workloadIdentity: WorkloadIdentityHandle | undefined;
try {
// When workload identity federation is configured, fetch the GitHub OIDC
// identity token and expose it to the CLI before validating auth env vars.
workloadIdentity = await setupWorkloadIdentity();
validateEnvironmentVariables();
// The composite action's "Install Claude Code" step writes the binary to
// ~/.local/bin/claude. Pass that path explicitly so the Agent SDK doesn't
// fall back to its bundled platform package, which bun may resolve to the
// wrong libc variant on Linux.
const claudeExecutable =
process.env.INPUT_PATH_TO_CLAUDE_CODE_EXECUTABLE ||
`${process.env.HOME}/.local/bin/claude`;
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,
claudeExecutable,
);
const promptConfig = await preparePrompt({
prompt: process.env.INPUT_PROMPT || "",
promptFile: process.env.INPUT_PROMPT_FILE || "",
});
const result = await runClaude(promptConfig.path, {
claudeArgs: process.env.INPUT_CLAUDE_ARGS,
allowedTools: process.env.INPUT_ALLOWED_TOOLS,
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: process.env.INPUT_APPEND_SYSTEM_PROMPT,
fallbackModel: process.env.INPUT_FALLBACK_MODEL,
model: process.env.ANTHROPIC_MODEL,
pathToClaudeCodeExecutable: claudeExecutable,
showFullOutput: process.env.INPUT_SHOW_FULL_OUTPUT,
});
// Set outputs for the standalone base-action
core.setOutput("conclusion", result.conclusion);
if (result.executionFile) {
core.setOutput("execution_file", result.executionFile);
}
if (result.sessionId) {
core.setOutput("session_id", result.sessionId);
}
if (result.structuredOutput) {
core.setOutput("structured_output", result.structuredOutput);
}
} catch (error) {
setExecutionFileOutputIfPresent();
core.setFailed(`Action failed with error: ${error}`);
core.setOutput("conclusion", "failure");
process.exit(1);
} finally {
// Stop refreshing the workload identity token file (so the process can
// exit) and delete the token material so it doesn't outlive this step
workloadIdentity?.stop();
}
}
if (import.meta.main) {
run();
}
+243
View File
@@ -0,0 +1,243 @@
import { spawn, ChildProcess } from "child_process";
const PLUGIN_NAME_REGEX = /^[@a-zA-Z0-9_\-\/\.]+$/;
const MAX_PLUGIN_NAME_LENGTH = 512;
const PATH_TRAVERSAL_REGEX =
/\.\.\/|\/\.\.|\.\/|\/\.|(?:^|\/)\.\.$|(?:^|\/)\.$|\.\.(?![0-9])/;
const MARKETPLACE_URL_REGEX =
/^https:\/\/[a-zA-Z0-9\-._~:/?#[\]@!$&'()*+,;=%]+\.git$/;
/**
* Checks if a marketplace input is a local path (not a URL)
* @param input - The marketplace input to check
* @returns true if the input is a local path, false if it's a URL
*/
function isLocalPath(input: string): boolean {
// Local paths start with ./, ../, /, or a drive letter (Windows)
return (
input.startsWith("./") ||
input.startsWith("../") ||
input.startsWith("/") ||
/^[a-zA-Z]:[\\\/]/.test(input)
);
}
/**
* Validates a marketplace URL or local path
* @param input - The marketplace URL or local path to validate
* @throws {Error} If the input is invalid
*/
function validateMarketplaceInput(input: string): void {
const normalized = input.trim();
if (!normalized) {
throw new Error("Marketplace URL or path cannot be empty");
}
// Local paths are passed directly to Claude Code which handles them
if (isLocalPath(normalized)) {
return;
}
// Validate as URL
if (!MARKETPLACE_URL_REGEX.test(normalized)) {
throw new Error(`Invalid marketplace URL format: ${input}`);
}
// Additional check for valid URL structure
try {
new URL(normalized);
} catch {
throw new Error(`Invalid marketplace URL: ${input}`);
}
}
/**
* Validates a plugin name for security issues
* @param pluginName - The plugin name to validate
* @throws {Error} If the plugin name is invalid
*/
function validatePluginName(pluginName: string): void {
// Normalize Unicode to prevent homoglyph attacks (e.g., fullwidth dots, Unicode slashes)
const normalized = pluginName.normalize("NFC");
if (normalized.length > MAX_PLUGIN_NAME_LENGTH) {
throw new Error(`Plugin name too long: ${normalized.substring(0, 50)}...`);
}
if (!PLUGIN_NAME_REGEX.test(normalized)) {
throw new Error(`Invalid plugin name format: ${pluginName}`);
}
// Prevent path traversal attacks with single efficient regex check
if (PATH_TRAVERSAL_REGEX.test(normalized)) {
throw new Error(`Invalid plugin name format: ${pluginName}`);
}
}
/**
* Parse a newline-separated list of marketplace URLs or local paths and return an array of validated entries
* @param marketplaces - Newline-separated list of marketplace Git URLs or local paths
* @returns Array of validated marketplace URLs or paths (empty array if none provided)
*/
function parseMarketplaces(marketplaces?: string): string[] {
const trimmed = marketplaces?.trim();
if (!trimmed) {
return [];
}
// Split by newline and process each entry
return trimmed
.split("\n")
.map((entry) => entry.trim())
.filter((entry) => {
if (entry.length === 0) return false;
validateMarketplaceInput(entry);
return true;
});
}
/**
* Parse a newline-separated list of plugin names and return an array of trimmed, non-empty plugin names
* Validates plugin names to prevent command injection and path traversal attacks
* Allows: letters, numbers, @, -, _, /, . (common npm/scoped package characters)
* Disallows: path traversal (../, ./), shell metacharacters, and consecutive dots
* @param plugins - Newline-separated list of plugin names, or undefined/empty to return empty array
* @returns Array of validated plugin names (empty array if none provided)
* @throws {Error} If any plugin name fails validation
*/
function parsePlugins(plugins?: string): string[] {
const trimmedPlugins = plugins?.trim();
if (!trimmedPlugins) {
return [];
}
// Split by newline and process each plugin
return trimmedPlugins
.split("\n")
.map((p) => p.trim())
.filter((p) => {
if (p.length === 0) return false;
validatePluginName(p);
return true;
});
}
/**
* Executes a Claude Code CLI command with proper error handling
* @param claudeExecutable - Path to the Claude executable
* @param args - Command arguments to pass to the executable
* @param errorContext - Context string for error messages (e.g., "Failed to install plugin 'foo'")
* @returns Promise that resolves when the command completes successfully
* @throws {Error} If the command fails to execute
*/
async function executeClaudeCommand(
claudeExecutable: string,
args: string[],
errorContext: string,
): Promise<void> {
return new Promise((resolve, reject) => {
const childProcess: ChildProcess = spawn(claudeExecutable, args, {
stdio: "inherit",
});
childProcess.on("close", (code: number | null) => {
if (code === 0) {
resolve();
} else if (code === null) {
reject(new Error(`${errorContext}: process terminated by signal`));
} else {
reject(new Error(`${errorContext} (exit code: ${code})`));
}
});
childProcess.on("error", (err: Error) => {
reject(new Error(`${errorContext}: ${err.message}`));
});
});
}
/**
* Installs a single Claude Code plugin
* @param pluginName - The name of the plugin to install
* @param claudeExecutable - Path to the Claude executable
* @returns Promise that resolves when the plugin is installed successfully
* @throws {Error} If the plugin installation fails
*/
async function installPlugin(
pluginName: string,
claudeExecutable: string,
): Promise<void> {
console.log(`Installing plugin: ${pluginName}`);
return executeClaudeCommand(
claudeExecutable,
["plugin", "install", pluginName],
`Failed to install plugin '${pluginName}'`,
);
}
/**
* Adds a Claude Code plugin marketplace
* @param claudeExecutable - Path to the Claude executable
* @param marketplace - The marketplace Git URL or local path to add
* @returns Promise that resolves when the marketplace add command completes
* @throws {Error} If the command fails to execute
*/
async function addMarketplace(
claudeExecutable: string,
marketplace: string,
): Promise<void> {
console.log(`Adding marketplace: ${marketplace}`);
return executeClaudeCommand(
claudeExecutable,
["plugin", "marketplace", "add", marketplace],
`Failed to add marketplace '${marketplace}'`,
);
}
/**
* Installs Claude Code plugins from a newline-separated list
* @param marketplacesInput - Newline-separated list of marketplace Git URLs or local paths
* @param pluginsInput - Newline-separated list of plugin names
* @param claudeExecutable - Path to the Claude executable (defaults to "claude")
* @returns Promise that resolves when all plugins are installed
* @throws {Error} If any plugin fails validation or installation (stops on first error)
*/
export async function installPlugins(
marketplacesInput?: string,
pluginsInput?: string,
claudeExecutable?: string,
): Promise<void> {
// Resolve executable path with explicit fallback
const resolvedExecutable = claudeExecutable || "claude";
// Parse and add all marketplaces before installing plugins
const marketplaces = parseMarketplaces(marketplacesInput);
if (marketplaces.length > 0) {
console.log(`Adding ${marketplaces.length} marketplace(s)...`);
for (const marketplace of marketplaces) {
await addMarketplace(resolvedExecutable, marketplace);
console.log(`✓ Successfully added marketplace: ${marketplace}`);
}
} else {
console.log("No marketplaces specified, skipping marketplace setup");
}
const plugins = parsePlugins(pluginsInput);
if (plugins.length > 0) {
console.log(`Installing ${plugins.length} plugin(s)...`);
for (const plugin of plugins) {
await installPlugin(plugin, resolvedExecutable);
console.log(`✓ Successfully installed: ${plugin}`);
}
} else {
console.log("No plugins specified, skipping plugins installation");
}
}
+355
View File
@@ -0,0 +1,355 @@
import { parse as parseShellArgs } from "shell-quote";
import type { ClaudeOptions } from "./run-claude";
import type { Options as SdkOptions } from "@anthropic-ai/claude-agent-sdk";
/**
* Result of parsing ClaudeOptions for SDK usage
*/
export type ParsedSdkOptions = {
sdkOptions: SdkOptions;
showFullOutput: boolean;
hasJsonSchema: boolean;
};
// Flags that should accumulate multiple values instead of overwriting
// Include both camelCase and hyphenated variants for CLI compatibility
const ACCUMULATING_FLAGS = new Set([
"allowedTools",
"allowed-tools",
"disallowedTools",
"disallowed-tools",
"mcp-config",
"add-dir",
]);
// Delimiter used to join accumulated flag values
const ACCUMULATE_DELIMITER = "\x00";
// shell-quote treats ()|&;<> as control operators and splits adjacent text
// around them into separate tokens (returned as `{op}` objects, which we then
// dropped). For CLI args these must be literal characters — e.g. unquoted
// `--allowedTools Bash(gh:*)` was being mangled into bare `Bash`, silently
// widening a scoped permission rule to Bash(*). We escape each metachar to a
// Unicode private-use codepoint before parsing and restore it afterward,
// keeping shell-quote's quote/whitespace handling intact.
const SHELL_META_PAIRS: [string, string][] = [
["(", ""],
[")", ""],
["|", ""],
["&", ""],
[";", ""],
["<", ""],
[">", ""],
];
const SHELL_META_ESCAPE = new Map(SHELL_META_PAIRS);
const SHELL_META_UNESCAPE = new Map(SHELL_META_PAIRS.map(([k, v]) => [v, k]));
const SHELL_META_ESCAPE_RE = /[()|&;<>]/g;
const SHELL_META_UNESCAPE_RE = /[-]/g;
function escapeShellMeta(s: string): string {
return s.replace(SHELL_META_ESCAPE_RE, (c) => SHELL_META_ESCAPE.get(c)!);
}
function unescapeShellMeta(s: string): string {
return s.replace(SHELL_META_UNESCAPE_RE, (c) => SHELL_META_UNESCAPE.get(c)!);
}
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);
}
/**
* Strip comment lines from a shell argument string.
* Lines whose first non-whitespace character is `#` are removed entirely.
* Inline `#` within a line (e.g. inside a quoted value) is left untouched
* because shell-quote handles quoting — we only need to remove full comment lines
* before shell-quote sees them.
*/
function stripShellComments(input: string): string {
return input
.split("\n")
.filter((line) => !line.trim().startsWith("#"))
.join("\n");
}
/**
* Parse claudeArgs string into extraArgs record for SDK pass-through
* The SDK/CLI will handle --mcp-config, --json-schema, etc.
* For allowedTools and disallowedTools, multiple occurrences are accumulated (null-char joined).
* Accumulating flags also consume all consecutive non-flag values
* (e.g., --allowed-tools "Tool1" "Tool2" "Tool3" captures all three).
*/
function parseClaudeArgsToExtraArgs(
claudeArgs?: string,
): Record<string, string | null> {
if (!claudeArgs?.trim()) return {};
const result: Record<string, string | null> = {};
const args = parseShellArgs(escapeShellMeta(stripShellComments(claudeArgs)))
.map((arg) => {
if (typeof arg === "string") return unescapeShellMeta(arg);
// With control metachars escaped above, the only non-string shell-quote
// can still emit is a glob op (bareword containing *, ?, or [). Its
// `pattern` field is the verbatim token text — use it as-is so values
// like `Bash(cmd:*)` and `Read(path/**)` round-trip intact.
if (typeof arg === "object" && arg !== null && "pattern" in arg) {
return unescapeShellMeta((arg as { pattern: string }).pattern);
}
return undefined;
})
.filter((arg): arg is string => typeof arg === "string");
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (arg?.startsWith("--")) {
const flag = arg.slice(2);
const nextArg = args[i + 1];
// Check if next arg is a value (not another flag)
if (nextArg && !nextArg.startsWith("--")) {
// For accumulating flags, consume all consecutive non-flag values
// This handles: --allowed-tools "Tool1" "Tool2" "Tool3"
if (ACCUMULATING_FLAGS.has(flag)) {
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 {
result[flag] = nextArg;
i++; // Skip the value
}
} else {
result[flag] = null; // Boolean flag
}
}
}
return result;
}
/**
* Parse ClaudeOptions into SDK-compatible options
* Uses extraArgs for CLI pass-through instead of duplicating option parsing
*/
export function parseSdkOptions(options: ClaudeOptions): ParsedSdkOptions {
// Determine output verbosity
const isDebugMode = process.env.ACTIONS_STEP_DEBUG === "true";
const showFullOutput = options.showFullOutput === "true" || isDebugMode;
// Parse claudeArgs into extraArgs for CLI pass-through
const extraArgs = parseClaudeArgsToExtraArgs(options.claudeArgs);
// Detect if --json-schema is present (for hasJsonSchema flag)
const hasJsonSchema = "json-schema" in extraArgs;
const modelFromClaudeArgs = extraArgs["model"] || undefined;
delete extraArgs["model"];
const maxTurnsFromClaudeArgs = extraArgs["max-turns"] || undefined;
delete extraArgs["max-turns"];
const additionalDirectories = extraArgs["add-dir"]
? extraArgs["add-dir"]
.split(ACCUMULATE_DELIMITER)
.map((dir) => dir.trim())
.filter(Boolean)
: [];
delete extraArgs["add-dir"];
// Extract and merge allowedTools from all sources:
// 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)
// This prevents duplicate flags being overwritten when claudeArgs contains --allowedTools
const allowedToolsValues = [
extraArgs["allowedTools"],
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
? options.allowedTools.split(",").map((t) => t.trim())
: [];
const mergedAllowedTools = [
...new Set([...extraArgsAllowedTools, ...directAllowedTools]),
];
delete extraArgs["allowedTools"];
delete extraArgs["allowed-tools"];
// Same for disallowedTools - check both camelCase and hyphenated variants
const disallowedToolsValues = [
extraArgs["disallowedTools"],
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
? options.disallowedTools.split(",").map((t) => t.trim())
: [];
const mergedDisallowedTools = [
...new Set([...extraArgsDisallowedTools, ...directDisallowedTools]),
];
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
const env: Record<string, string | undefined> = { ...process.env };
if (process.env.INPUT_ACTION_INPUTS_PRESENT) {
env.GITHUB_ACTION_INPUTS = process.env.INPUT_ACTION_INPUTS_PRESENT;
}
// Set the entrypoint for Claude Code to identify this as the GitHub Action
env.CLAUDE_CODE_ENTRYPOINT = "claude-code-github-action";
// Remove OIDC token request variables so Claude cannot mint new tokens.
// These are only needed by the action itself (via @actions/core.getIDToken()),
// not by the Claude session.
delete env.ACTIONS_ID_TOKEN_REQUEST_URL;
delete env.ACTIONS_ID_TOKEN_REQUEST_TOKEN;
// Remove ALL_INPUTS as it is only needed during initial setup to determine
// input presence (collectActionInputsPresence) and contains serialized workflow inputs.
delete env.ALL_INPUTS;
// Build system prompt option - default to claude_code preset
let systemPrompt: SdkOptions["systemPrompt"];
if (options.systemPrompt) {
systemPrompt = options.systemPrompt;
} else if (options.appendSystemPrompt) {
systemPrompt = {
type: "preset",
preset: "claude_code",
append: options.appendSystemPrompt,
};
} else {
// Default to claude_code preset when no custom prompt is specified
systemPrompt = {
type: "preset",
preset: "claude_code",
};
}
// Build SDK options - use merged tools from both direct options and claudeArgs
const sdkOptions: SdkOptions = {
// Direct options from ClaudeOptions inputs
model: options.model || modelFromClaudeArgs,
maxTurns: options.maxTurns
? parseInt(options.maxTurns, 10)
: maxTurnsFromClaudeArgs
? parseInt(maxTurnsFromClaudeArgs, 10)
: undefined,
allowedTools:
mergedAllowedTools.length > 0 ? mergedAllowedTools : undefined,
disallowedTools:
mergedDisallowedTools.length > 0 ? mergedDisallowedTools : undefined,
systemPrompt,
fallbackModel: options.fallbackModel,
pathToClaudeCodeExecutable: options.pathToClaudeCodeExecutable,
additionalDirectories:
additionalDirectories.length > 0 ? additionalDirectories : undefined,
// Pass through claudeArgs as extraArgs - CLI handles --mcp-config, --json-schema, etc.
// Note: allowedTools and disallowedTools have been removed from extraArgs to prevent duplicates
extraArgs,
env,
// Load settings from sources - prefer user's --setting-sources if provided, otherwise use all sources
// This ensures users can override the default behavior (e.g., --setting-sources user to avoid in-repo configs)
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 {
sdkOptions,
showFullOutput,
hasJsonSchema,
};
}
+82
View File
@@ -0,0 +1,82 @@
import { existsSync, statSync } from "fs";
import { mkdir, writeFile } from "fs/promises";
export type PreparePromptInput = {
prompt: string;
promptFile: string;
};
export type PreparePromptConfig = {
type: "file" | "inline";
path: string;
};
async function validateAndPreparePrompt(
input: PreparePromptInput,
): Promise<PreparePromptConfig> {
// Validate inputs
if (!input.prompt && !input.promptFile) {
throw new Error(
"Neither 'prompt' nor 'prompt_file' was provided. At least one is required.",
);
}
if (input.prompt && input.promptFile) {
throw new Error(
"Both 'prompt' and 'prompt_file' were provided. Please specify only one.",
);
}
// Handle prompt file
if (input.promptFile) {
if (!existsSync(input.promptFile)) {
throw new Error(`Prompt file '${input.promptFile}' does not exist.`);
}
// Validate that the file is not empty
const stats = statSync(input.promptFile);
if (stats.size === 0) {
throw new Error(
"Prompt file is empty. Please provide a non-empty prompt.",
);
}
return {
type: "file",
path: input.promptFile,
};
}
// Handle inline prompt
if (!input.prompt || input.prompt.trim().length === 0) {
throw new Error("Prompt is empty. Please provide a non-empty prompt.");
}
const inlinePath = "/tmp/claude-action/prompt.txt";
return {
type: "inline",
path: inlinePath,
};
}
async function createTemporaryPromptFile(
prompt: string,
promptPath: string,
): Promise<void> {
// Create the directory path
const dirPath = promptPath.substring(0, promptPath.lastIndexOf("/"));
await mkdir(dirPath, { recursive: true });
await writeFile(promptPath, prompt);
}
export async function preparePrompt(
input: PreparePromptInput,
): Promise<PreparePromptConfig> {
const config = await validateAndPreparePrompt(input);
if (config.type === "inline") {
await createTemporaryPromptFile(input.prompt, config.path);
}
return config;
}
+47
View File
@@ -0,0 +1,47 @@
export type RetryOptions = {
maxAttempts?: number;
initialDelayMs?: number;
maxDelayMs?: number;
backoffFactor?: number;
shouldRetry?: (error: Error) => boolean;
};
export async function retryWithBackoff<T>(
operation: () => Promise<T>,
options: RetryOptions = {},
): Promise<T> {
const {
maxAttempts = 3,
initialDelayMs = 5000,
maxDelayMs = 20000,
backoffFactor = 2,
shouldRetry,
} = options;
let delayMs = initialDelayMs;
let lastError: Error | undefined;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
console.log(`Attempt ${attempt} of ${maxAttempts}...`);
return await operation();
} catch (error) {
lastError = error instanceof Error ? error : new Error(String(error));
console.error(`Attempt ${attempt} failed:`, lastError.message);
if (shouldRetry && !shouldRetry(lastError)) {
console.error("Error is not retryable, giving up immediately");
throw lastError;
}
if (attempt < maxAttempts) {
console.log(`Retrying in ${delayMs / 1000} seconds...`);
await new Promise((resolve) => setTimeout(resolve, delayMs));
delayMs = Math.min(delayMs * backoffFactor, maxDelayMs);
}
}
}
console.error(`Operation failed after ${maxAttempts} attempts`);
throw lastError;
}
+301
View File
@@ -0,0 +1,301 @@
import * as core from "@actions/core";
import { readFile, access } from "fs/promises";
import { dirname, join } from "path";
import { query } from "@anthropic-ai/claude-agent-sdk";
import type {
SDKMessage,
SDKResultMessage,
SDKUserMessage,
} from "@anthropic-ai/claude-agent-sdk";
import type { ParsedSdkOptions } from "./parse-sdk-options";
import { writeExecutionFile } from "./execution-file";
export type ClaudeRunResult = {
executionFile?: string;
sessionId?: string;
conclusion: "success" | "failure";
structuredOutput?: string;
};
/** Filename for the user request file, written by prompt generation */
const USER_REQUEST_FILENAME = "claude-user-request.txt";
/**
* Check if a file exists
*/
async function fileExists(path: string): Promise<boolean> {
try {
await access(path);
return true;
} catch {
return false;
}
}
/**
* Creates a prompt configuration for the SDK.
* If a user request file exists alongside the prompt file, returns a multi-block
* SDKUserMessage that enables slash command processing in the CLI.
* Otherwise, returns the prompt as a simple string.
*/
async function createPromptConfig(
promptPath: string,
showFullOutput: boolean,
): Promise<string | AsyncIterable<SDKUserMessage>> {
const promptContent = await readFile(promptPath, "utf-8");
// Check for user request file in the same directory
const userRequestPath = join(dirname(promptPath), USER_REQUEST_FILENAME);
const hasUserRequest = await fileExists(userRequestPath);
if (!hasUserRequest) {
// No user request file - use simple string prompt
return promptContent;
}
// User request file exists - create multi-block message
const userRequest = await readFile(userRequestPath, "utf-8");
if (showFullOutput) {
console.log("Using multi-block message with user request:", userRequest);
} else {
console.log("Using multi-block message with user request (content hidden)");
}
// Create an async generator that yields a single multi-block message
// The context/instructions go first, then the user's actual request last
// This allows the CLI to detect and process slash commands in the user request
async function* createMultiBlockMessage(): AsyncGenerator<SDKUserMessage> {
yield {
type: "user",
session_id: "",
message: {
role: "user",
content: [
{ type: "text", text: promptContent }, // Instructions + GitHub context
{ type: "text", text: userRequest }, // User's request (may be a slash command)
],
},
parent_tool_use_id: null,
};
}
return createMultiBlockMessage();
}
type ModelUsageSummary = Record<
string,
{
contextWindow: number;
maxOutputTokens: number;
}
>;
/**
* Keep resolved model limits visible without exposing token usage or cost details.
*/
function sanitizeModelUsage(
modelUsage: SDKResultMessage["modelUsage"] | undefined,
): ModelUsageSummary | undefined {
if (!modelUsage) {
return undefined;
}
return Object.fromEntries(
Object.entries(modelUsage).map(([model, usage]) => [
model,
{
contextWindow: usage.contextWindow,
maxOutputTokens: usage.maxOutputTokens,
},
]),
);
}
/**
* Sanitizes SDK output to match CLI sanitization behavior
*/
function sanitizeSdkOutput(
message: SDKMessage,
showFullOutput: boolean,
): string | null {
if (showFullOutput) {
return JSON.stringify(message, null, 2);
}
// System initialization - safe to show
if (message.type === "system" && message.subtype === "init") {
return JSON.stringify(
{
type: "system",
subtype: "init",
message: "Claude Code initialized",
model: "model" in message ? message.model : "unknown",
},
null,
2,
);
}
// Result messages - show sanitized summary
if (message.type === "result") {
const resultMsg = message as SDKResultMessage;
return JSON.stringify(
{
type: "result",
subtype: resultMsg.subtype,
is_error: resultMsg.is_error,
duration_ms: resultMsg.duration_ms,
num_turns: resultMsg.num_turns,
total_cost_usd: resultMsg.total_cost_usd,
permission_denials_count: resultMsg.permission_denials?.length ?? 0,
modelUsage: sanitizeModelUsage(resultMsg.modelUsage),
},
null,
2,
);
}
// Suppress other message types in non-full-output mode
return null;
}
/**
* Run Claude using the Agent SDK
*/
export async function runClaudeWithSdk(
promptPath: string,
{ sdkOptions, showFullOutput, hasJsonSchema }: ParsedSdkOptions,
): Promise<ClaudeRunResult> {
// Create prompt configuration - may be a string or multi-block message
const prompt = await createPromptConfig(promptPath, showFullOutput);
if (!showFullOutput) {
console.log(
"Running Claude Code via SDK (full output hidden for security)...",
);
console.log(
"Rerun in debug mode or enable `show_full_output: true` in your workflow file for full output.",
);
}
console.log(`Running Claude with prompt from file: ${promptPath}`);
// Log SDK options without env (which could contain sensitive data)
const { env, extraArgs, ...optionsToLog } = sdkOptions;
console.log("SDK options:", JSON.stringify(optionsToLog, null, 2));
const messages: SDKMessage[] = [];
let resultMessage: SDKResultMessage | undefined;
try {
for await (const message of query({ prompt, options: sdkOptions })) {
messages.push(message);
const sanitized = sanitizeSdkOutput(message, showFullOutput);
if (sanitized) {
console.log(sanitized);
}
if (message.type === "result") {
resultMessage = message as SDKResultMessage;
// The SDK's query() iterator should close itself after the
// result message, but in some workflow contexts (notably
// pull_request-triggered runs) it stays open indefinitely and
// the for-await hangs until the workflow's timeout-minutes
// kills the job. This causes the action to "succeed" inside
// Claude (verdict posted, $cost recorded) but be reported as
// cancelled with no execution-output.json written. Break
// explicitly: by SDK contract no further messages follow a
// result, so the break is safe.
break;
}
}
} catch (error) {
console.error("SDK execution error:", error);
await writeExecutionFile(messages);
throw new Error(`SDK execution error: ${error}`);
}
const result: ClaudeRunResult = {
conclusion: "failure",
};
const executionFile = await writeExecutionFile(messages);
if (executionFile) {
result.executionFile = executionFile;
}
// Extract session_id from system.init message
const initMessage = messages.find(
(m) => m.type === "system" && "subtype" in m && m.subtype === "init",
);
if (initMessage && "session_id" in initMessage && initMessage.session_id) {
result.sessionId = initMessage.session_id as string;
core.info(`Set session_id: ${result.sessionId}`);
}
if (!resultMessage) {
core.error("No result message received from Claude");
throw new Error("No result message received from Claude");
}
if (
resultMessage.subtype === "success" &&
!resultMessage.is_error &&
sdkOptions.maxTurns !== undefined &&
resultMessage.num_turns > sdkOptions.maxTurns
) {
const message = `Claude reported a successful result after ${resultMessage.num_turns} turns, exceeding the configured maximum of ${sdkOptions.maxTurns}`;
core.error(message);
throw new Error(message);
}
// subtype "success" with is_error:true means the run errored without producing
// a real result — treat it as failure so CI does not show a misleading green check.
const isSuccess =
resultMessage.subtype === "success" && !resultMessage.is_error;
result.conclusion = isSuccess ? "success" : "failure";
// Handle structured output
if (hasJsonSchema) {
if (
isSuccess &&
"structured_output" in resultMessage &&
resultMessage.structured_output
) {
result.structuredOutput = JSON.stringify(resultMessage.structured_output);
core.info(
`Set structured_output with ${Object.keys(resultMessage.structured_output as object).length} field(s)`,
);
} else {
core.setFailed(
`--json-schema was provided but Claude did not return structured_output. Result subtype: ${resultMessage.subtype}`,
);
result.conclusion = "failure";
throw new Error(
`--json-schema was provided but Claude did not return structured_output. Result subtype: ${resultMessage.subtype}`,
);
}
}
if (!isSuccess) {
if (resultMessage.subtype === "success" && resultMessage.is_error) {
core.error(
"Claude result reported subtype success with is_error:true (run did not complete successfully)",
);
}
if ("errors" in resultMessage && resultMessage.errors) {
core.error(`Execution failed: ${resultMessage.errors.join(", ")}`);
}
throw new Error(
`Claude execution failed: ${
resultMessage.subtype === "success" && resultMessage.is_error
? "result is_error:true"
: "errors" in resultMessage && resultMessage.errors
? resultMessage.errors.join(", ")
: "unknown error"
}`,
);
}
return result;
}
+25
View File
@@ -0,0 +1,25 @@
import { runClaudeWithSdk } from "./run-claude-sdk";
import type { ClaudeRunResult } from "./run-claude-sdk";
import { parseSdkOptions } from "./parse-sdk-options";
export type ClaudeOptions = {
claudeArgs?: string;
model?: string;
pathToClaudeCodeExecutable?: string;
allowedTools?: string;
disallowedTools?: string;
maxTurns?: string;
mcpConfig?: string;
systemPrompt?: string;
appendSystemPrompt?: string;
fallbackModel?: string;
showFullOutput?: string;
};
export async function runClaude(
promptPath: string,
options: ClaudeOptions,
): Promise<ClaudeRunResult> {
const parsedOptions = parseSdkOptions(options);
return runClaudeWithSdk(promptPath, parsedOptions);
}
@@ -0,0 +1,68 @@
import { $ } from "bun";
import { homedir } from "os";
import { readFile } from "fs/promises";
export async function setupClaudeCodeSettings(
settingsInput?: string,
homeDir?: string,
) {
const home = homeDir ?? homedir();
const settingsPath = `${home}/.claude/settings.json`;
console.log(`Setting up Claude settings at: ${settingsPath}`);
// Ensure .claude directory exists
console.log(`Creating .claude directory...`);
await $`mkdir -p ${home}/.claude`.quiet();
let settings: Record<string, unknown> = {};
try {
const existingSettings = await $`cat ${settingsPath}`.quiet().text();
if (existingSettings.trim()) {
settings = JSON.parse(existingSettings);
console.log(
`Found existing settings:`,
JSON.stringify(settings, null, 2),
);
} else {
console.log(`Settings file exists but is empty`);
}
} catch (e) {
console.log(`No existing settings file found, creating new one`);
}
// Handle settings input (either file path or JSON string)
if (settingsInput && settingsInput.trim()) {
console.log(`Processing settings input...`);
let inputSettings: Record<string, unknown> = {};
try {
// First try to parse as JSON
inputSettings = JSON.parse(settingsInput);
console.log(`Parsed settings input as JSON`);
} catch (e) {
// If not JSON, treat as file path
console.log(
`Settings input is not JSON, treating as file path: ${settingsInput}`,
);
try {
const fileContent = await readFile(settingsInput, "utf-8");
inputSettings = JSON.parse(fileContent);
console.log(`Successfully read and parsed settings from file`);
} catch (fileError) {
console.error(`Failed to read or parse settings file: ${fileError}`);
throw new Error(`Failed to process settings input: ${fileError}`);
}
}
// Merge input settings with existing settings
settings = { ...settings, ...inputSettings };
console.log(`Merged settings with input settings`);
}
// Always set enableAllProjectMcpServers to true
settings.enableAllProjectMcpServers = true;
console.log(`Updated settings with enableAllProjectMcpServers: true`);
await $`echo ${JSON.stringify(settings, null, 2)} > ${settingsPath}`.quiet();
console.log(`Settings saved successfully`);
}
+89
View File
@@ -0,0 +1,89 @@
/**
* Validates the environment variables required for running Claude Code
* based on the selected provider (Anthropic API, AWS Bedrock, Google Vertex AI, or Microsoft Foundry)
*/
export function validateEnvironmentVariables() {
const useBedrock = process.env.CLAUDE_CODE_USE_BEDROCK === "1";
const useVertex = process.env.CLAUDE_CODE_USE_VERTEX === "1";
const useFoundry = process.env.CLAUDE_CODE_USE_FOUNDRY === "1";
const anthropicApiKey = process.env.ANTHROPIC_API_KEY;
const claudeCodeOAuthToken = process.env.CLAUDE_CODE_OAUTH_TOKEN;
const federationRuleId = process.env.ANTHROPIC_FEDERATION_RULE_ID;
const federationOrganizationId = process.env.ANTHROPIC_ORGANIZATION_ID;
const hasWorkloadIdentity = Boolean(
federationRuleId && federationOrganizationId,
);
const hasPartialWorkloadIdentity =
!hasWorkloadIdentity &&
Boolean(federationRuleId || federationOrganizationId);
const errors: string[] = [];
// Check for mutual exclusivity between providers
const activeProviders = [useBedrock, useVertex, useFoundry].filter(Boolean);
if (activeProviders.length > 1) {
errors.push(
"Cannot use multiple providers simultaneously. Please set only one of: CLAUDE_CODE_USE_BEDROCK, CLAUDE_CODE_USE_VERTEX, or CLAUDE_CODE_USE_FOUNDRY.",
);
}
if (!useBedrock && !useVertex && !useFoundry) {
if (!anthropicApiKey && !claudeCodeOAuthToken && !hasWorkloadIdentity) {
if (hasPartialWorkloadIdentity) {
errors.push(
"Workload identity federation requires both ANTHROPIC_FEDERATION_RULE_ID and ANTHROPIC_ORGANIZATION_ID to be set.",
);
} else {
errors.push(
"Either ANTHROPIC_API_KEY, CLAUDE_CODE_OAUTH_TOKEN, or workload identity federation (ANTHROPIC_FEDERATION_RULE_ID and ANTHROPIC_ORGANIZATION_ID) is required when using direct Anthropic API.",
);
}
}
} else if (useBedrock) {
const awsRegion = process.env.AWS_REGION;
const awsAccessKeyId = process.env.AWS_ACCESS_KEY_ID;
const awsSecretAccessKey = process.env.AWS_SECRET_ACCESS_KEY;
const awsBearerToken = process.env.AWS_BEARER_TOKEN_BEDROCK;
// AWS_REGION is always required for Bedrock
if (!awsRegion) {
errors.push("AWS_REGION is required when using AWS Bedrock.");
}
// Either bearer token OR access key credentials must be provided
const hasAccessKeyCredentials = awsAccessKeyId && awsSecretAccessKey;
const hasBearerToken = awsBearerToken;
if (!hasAccessKeyCredentials && !hasBearerToken) {
errors.push(
"Either AWS_BEARER_TOKEN_BEDROCK or both AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are required when using AWS Bedrock.",
);
}
} else if (useVertex) {
const requiredVertexVars = {
ANTHROPIC_VERTEX_PROJECT_ID: process.env.ANTHROPIC_VERTEX_PROJECT_ID,
CLOUD_ML_REGION: process.env.CLOUD_ML_REGION,
};
Object.entries(requiredVertexVars).forEach(([key, value]) => {
if (!value) {
errors.push(`${key} is required when using Google Vertex AI.`);
}
});
} else if (useFoundry) {
const foundryResource = process.env.ANTHROPIC_FOUNDRY_RESOURCE;
const foundryBaseUrl = process.env.ANTHROPIC_FOUNDRY_BASE_URL;
// Either resource name or base URL is required
if (!foundryResource && !foundryBaseUrl) {
errors.push(
"Either ANTHROPIC_FOUNDRY_RESOURCE or ANTHROPIC_FOUNDRY_BASE_URL is required when using Microsoft Foundry.",
);
}
}
if (errors.length > 0) {
const errorMessage = `Environment variable validation failed:\n${errors.map((e) => ` - ${e}`).join("\n")}`;
throw new Error(errorMessage);
}
}
+196
View File
@@ -0,0 +1,196 @@
#!/usr/bin/env bun
/**
* Workload Identity Federation support.
*
* When the federation inputs are configured, the action fetches a GitHub
* Actions OIDC token (JWT), writes it to a file, and points the Claude Code
* CLI at it via ANTHROPIC_IDENTITY_TOKEN_FILE. The CLI exchanges the JWT for
* a short-lived Anthropic access token using the federation rule, so no
* static ANTHROPIC_API_KEY is needed.
*
* GitHub's OIDC tokens are short-lived and the CLI re-reads the token file
* every time it refreshes its Anthropic access token, so the action keeps the
* file fresh in the background for long-running executions.
*/
import * as core from "@actions/core";
import { createHash } from "crypto";
import { mkdirSync, rmSync, writeFileSync } from "fs";
import { join } from "path";
import { retryWithBackoff } from "./retry";
/** How often the GitHub OIDC identity token file is rewritten. */
const REFRESH_INTERVAL_MS = 4 * 60 * 1000;
/**
* Default audience requested on the GitHub OIDC token. Scopes the JWT to the
* Claude API token exchange; override with the anthropic_oidc_audience input
* if your federation rule expects a different audience.
*/
const DEFAULT_OIDC_AUDIENCE = "https://api.anthropic.com";
export type WorkloadIdentityHandle = {
tokenFile: string;
stop: () => void;
};
/**
* Whether the workload identity federation inputs are configured.
* Mirrors the Claude Code CLI's env detection, which requires the federation
* rule ID and organization ID.
*/
export function isWorkloadIdentityConfigured(): boolean {
return Boolean(
process.env.ANTHROPIC_FEDERATION_RULE_ID?.trim() &&
process.env.ANTHROPIC_ORGANIZATION_ID?.trim(),
);
}
async function fetchIdentityToken(audience: string) {
return retryWithBackoff(() => core.getIDToken(audience));
}
/**
* Writes a profile config that switches federation resolution to the
* file-backed path. Resolving federation through a profile (rather than bare
* env vars) enables the SDK's on-disk credentials cache, so the several
* `claude` processes the action spawns (plugin installs, main query) share
* one exchanged access token instead of each re-exchanging the single-use
* GitHub OIDC token, which fails with 401 (`jti_reused`).
*
* The profile is intentionally minimal: the SDK gap-fills the federation
* fields (rule, organization, identity-token file, service account, base URL)
* from the ANTHROPIC_* env vars the action already exports, so the file only
* needs to exist to turn the cache on.
*
* The config dir name embeds a fingerprint of the federation inputs. The
* SDK's cache reuses a token on `expires_at` alone, with no record of the
* config that minted it, and the token's scope is bound at mint time — so a
* later action step in the same job (RUNNER_TEMP is per-job) with different
* federation inputs must land in a different dir or it would silently reuse
* the first step's token.
*
* Sharing the cache is only safe while the action spawns its `claude`
* subprocesses sequentially: the SDK cache is not cross-process serialized,
* and concurrent cache misses would each re-exchange the same single-use
* identity token. Parallelizing the plugin installs would reintroduce the
* `jti_reused` failures.
*/
function writeFederationProfile(baseDir: string): string {
// Every input that changes which credential the exchange mints must be in
// here; service_account_id and scope are sent in the exchange request body.
const fingerprint = createHash("sha256")
.update(
JSON.stringify([
process.env.ANTHROPIC_FEDERATION_RULE_ID?.trim() ?? "",
process.env.ANTHROPIC_ORGANIZATION_ID?.trim() ?? "",
process.env.ANTHROPIC_SERVICE_ACCOUNT_ID?.trim() ?? "",
process.env.ANTHROPIC_WORKSPACE_ID?.trim() ?? "",
process.env.ANTHROPIC_BASE_URL?.trim() ?? "",
process.env.ANTHROPIC_SCOPE?.trim() ?? "",
]),
)
.digest("hex")
.slice(0, 16);
const configDir = join(baseDir, `config-${fingerprint}`);
mkdirSync(join(configDir, "configs"), { recursive: true, mode: 0o700 });
writeFileSync(
join(configDir, "configs", "default.json"),
JSON.stringify(
{ version: "1.0", authentication: { type: "oidc_federation" } },
null,
2,
),
{ mode: 0o600 },
);
return configDir;
}
/**
* Fetches a GitHub Actions OIDC token, writes it to a file in RUNNER_TEMP,
* exports ANTHROPIC_IDENTITY_TOKEN_FILE, and starts a background refresh so
* the file stays valid for long executions.
*
* Returns undefined when federation is not configured or is shadowed by a
* higher-precedence credential. Callers must invoke stop() when execution
* finishes; it also deletes the identity token and any cached exchanged
* credential.
*/
export async function setupWorkloadIdentity(): Promise<
WorkloadIdentityHandle | undefined
> {
if (!isWorkloadIdentityConfigured()) {
return undefined;
}
if (
process.env.ANTHROPIC_API_KEY?.trim() ||
process.env.CLAUDE_CODE_OAUTH_TOKEN?.trim()
) {
core.warning(
"Workload identity federation inputs are set alongside anthropic_api_key or claude_code_oauth_token. The API key/OAuth token takes precedence, so federation will not be used.",
);
return undefined;
}
const audience =
process.env.ANTHROPIC_OIDC_AUDIENCE?.trim() || DEFAULT_OIDC_AUDIENCE;
const tokenDir = join(
process.env.RUNNER_TEMP || "/tmp",
"claude-workload-identity",
);
const tokenFile = join(tokenDir, "identity-token");
const writeIdentityToken = async () => {
const identityToken = await fetchIdentityToken(audience);
core.setSecret(identityToken);
mkdirSync(tokenDir, { recursive: true, mode: 0o700 });
writeFileSync(tokenFile, identityToken, { mode: 0o600 });
};
try {
await writeIdentityToken();
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(
`Failed to fetch a GitHub Actions OIDC token for workload identity federation: ${message}. Did you remember to add \`id-token: write\` to your workflow permissions?`,
);
}
process.env.ANTHROPIC_IDENTITY_TOKEN_FILE = tokenFile;
if (
process.env.ANTHROPIC_CONFIG_DIR?.trim() ||
process.env.ANTHROPIC_PROFILE?.trim()
) {
core.warning(
"ANTHROPIC_CONFIG_DIR or ANTHROPIC_PROFILE is already set, so the action will not write its own federation profile. Credential caching across the spawned Claude processes follows the existing profile configuration.",
);
} else {
process.env.ANTHROPIC_CONFIG_DIR = writeFederationProfile(tokenDir);
process.env.ANTHROPIC_PROFILE = "default";
}
console.log(
`Workload identity federation configured (rule: ${process.env.ANTHROPIC_FEDERATION_RULE_ID}, identity token file: ${tokenFile})`,
);
const refreshInterval = setInterval(() => {
writeIdentityToken().catch((error) => {
core.warning(
`Failed to refresh the GitHub Actions OIDC identity token: ${error instanceof Error ? error.message : String(error)}`,
);
});
}, REFRESH_INTERVAL_MS);
return {
tokenFile,
stop: () => {
clearInterval(refreshInterval);
// RUNNER_TEMP is per-job, not per-step: remove the identity token, the
// profile, and the cached exchanged credential so they don't outlive
// this step.
rmSync(tokenDir, { recursive: true, force: true });
},
};
}
+12
View File
@@ -0,0 +1,12 @@
#!/bin/bash
# Install act if not already installed
if ! command -v act &> /dev/null; then
echo "Installing act..."
brew install act
fi
# Run the test workflow locally
# You'll need to provide your ANTHROPIC_API_KEY
echo "Running action locally with act..."
act push --secret ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" -W .github/workflows/test-base-action.yml --container-architecture linux/amd64
+18
View File
@@ -0,0 +1,18 @@
#!/bin/bash
# Install act if not already installed
if ! command -v act &> /dev/null; then
echo "Installing act..."
brew install act
fi
# Check if ANTHROPIC_API_KEY is set
if [ -z "$ANTHROPIC_API_KEY" ]; then
echo "Error: ANTHROPIC_API_KEY environment variable is not set"
echo "Please export your API key: export ANTHROPIC_API_KEY='your-key-here'"
exit 1
fi
# Run the MCP test workflow locally
echo "Running MCP server test locally with act..."
act push --secret ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" -W .github/workflows/test-mcp-servers.yml --container-architecture linux/amd64
+39
View File
@@ -0,0 +1,39 @@
#!/usr/bin/env bun
import * as core from "@actions/core";
import { afterEach, describe, expect, spyOn, test } from "bun:test";
import { mkdtemp, rm, writeFile } from "fs/promises";
import { tmpdir } from "os";
import { join } from "path";
import { setExecutionFileOutputIfPresent } from "../src/execution-file";
describe("execution file output", () => {
const originalRunnerTemp = process.env.RUNNER_TEMP;
let tempDir: string | undefined;
afterEach(async () => {
if (tempDir) {
await rm(tempDir, { recursive: true, force: true });
tempDir = undefined;
}
process.env.RUNNER_TEMP = originalRunnerTemp;
});
test("sets execution_file output when the default execution file exists", async () => {
const setOutputSpy = spyOn(core, "setOutput").mockImplementation(() => {});
tempDir = await mkdtemp(join(tmpdir(), "claude-execution-file-"));
process.env.RUNNER_TEMP = tempDir;
const executionFile = join(tempDir, "claude-execution-output.json");
await writeFile(executionFile, "[]");
try {
expect(setExecutionFileOutputIfPresent()).toBe(executionFile);
expect(setOutputSpy).toHaveBeenCalledWith(
"execution_file",
executionFile,
);
} finally {
setOutputSpy.mockRestore();
}
});
});
+706
View File
@@ -0,0 +1,706 @@
#!/usr/bin/env bun
import { describe, test, expect, mock, spyOn, afterEach } from "bun:test";
import { installPlugins } from "../src/install-plugins";
import * as childProcess from "child_process";
describe("installPlugins", () => {
let spawnSpy: ReturnType<typeof spyOn> | undefined;
afterEach(() => {
// Restore original spawn after each test
if (spawnSpy) {
spawnSpy.mockRestore();
}
});
function createMockSpawn(
exitCode: number | null = 0,
shouldError: boolean = false,
) {
const mockProcess = {
on: mock((event: string, handler: Function) => {
if (event === "close" && !shouldError) {
// Simulate successful close
setTimeout(() => handler(exitCode), 0);
} else if (event === "error" && shouldError) {
// Simulate error
setTimeout(() => handler(new Error("spawn error")), 0);
}
return mockProcess;
}),
};
spawnSpy = spyOn(childProcess, "spawn").mockImplementation(
() => mockProcess as any,
);
return spawnSpy;
}
test("should not call spawn when no plugins are specified", async () => {
const spy = createMockSpawn();
await installPlugins(undefined, "");
expect(spy).not.toHaveBeenCalled();
});
test("should not call spawn when plugins is undefined", async () => {
const spy = createMockSpawn();
await installPlugins(undefined, undefined);
expect(spy).not.toHaveBeenCalled();
});
test("should not call spawn when plugins is only whitespace", async () => {
const spy = createMockSpawn();
await installPlugins(undefined, " ");
expect(spy).not.toHaveBeenCalled();
});
test("should install a single plugin with default executable", async () => {
const spy = createMockSpawn();
await installPlugins(undefined, "test-plugin");
expect(spy).toHaveBeenCalledTimes(1);
// Only call: install plugin (no marketplace without explicit marketplace input)
expect(spy).toHaveBeenNthCalledWith(
1,
"claude",
["plugin", "install", "test-plugin"],
{ stdio: "inherit" },
);
});
test("should install multiple plugins sequentially", async () => {
const spy = createMockSpawn();
await installPlugins(undefined, "plugin1\nplugin2\nplugin3");
expect(spy).toHaveBeenCalledTimes(3);
// Install plugins (no marketplace without explicit marketplace input)
expect(spy).toHaveBeenNthCalledWith(
1,
"claude",
["plugin", "install", "plugin1"],
{ stdio: "inherit" },
);
expect(spy).toHaveBeenNthCalledWith(
2,
"claude",
["plugin", "install", "plugin2"],
{ stdio: "inherit" },
);
expect(spy).toHaveBeenNthCalledWith(
3,
"claude",
["plugin", "install", "plugin3"],
{ stdio: "inherit" },
);
});
test("should use custom claude executable path when provided", async () => {
const spy = createMockSpawn();
await installPlugins(undefined, "test-plugin", "/custom/path/to/claude");
expect(spy).toHaveBeenCalledTimes(1);
// Only call: install plugin (no marketplace without explicit marketplace input)
expect(spy).toHaveBeenNthCalledWith(
1,
"/custom/path/to/claude",
["plugin", "install", "test-plugin"],
{ stdio: "inherit" },
);
});
test("should trim whitespace from plugin names before installation", async () => {
const spy = createMockSpawn();
await installPlugins(undefined, " plugin1 \n plugin2 ");
expect(spy).toHaveBeenCalledTimes(2);
// Install plugins (no marketplace without explicit marketplace input)
expect(spy).toHaveBeenNthCalledWith(
1,
"claude",
["plugin", "install", "plugin1"],
{ stdio: "inherit" },
);
expect(spy).toHaveBeenNthCalledWith(
2,
"claude",
["plugin", "install", "plugin2"],
{ stdio: "inherit" },
);
});
test("should skip empty entries in plugin list", async () => {
const spy = createMockSpawn();
await installPlugins(undefined, "plugin1\n\nplugin2");
expect(spy).toHaveBeenCalledTimes(2);
// Install plugins (no marketplace without explicit marketplace input)
expect(spy).toHaveBeenNthCalledWith(
1,
"claude",
["plugin", "install", "plugin1"],
{ stdio: "inherit" },
);
expect(spy).toHaveBeenNthCalledWith(
2,
"claude",
["plugin", "install", "plugin2"],
{ stdio: "inherit" },
);
});
test("should handle plugin installation error and throw", async () => {
createMockSpawn(1, false); // Exit code 1
await expect(installPlugins(undefined, "failing-plugin")).rejects.toThrow(
"Failed to install plugin 'failing-plugin' (exit code: 1)",
);
});
test("should handle null exit code (process terminated by signal)", async () => {
createMockSpawn(null, false); // Exit code null (terminated by signal)
await expect(
installPlugins(undefined, "terminated-plugin"),
).rejects.toThrow(
"Failed to install plugin 'terminated-plugin': process terminated by signal",
);
});
test("should stop installation on first error", async () => {
const spy = createMockSpawn(1, false); // Exit code 1
await expect(
installPlugins(undefined, "plugin1\nplugin2\nplugin3"),
).rejects.toThrow("Failed to install plugin 'plugin1' (exit code: 1)");
// Should only try to install first plugin before failing
expect(spy).toHaveBeenCalledTimes(1);
});
test("should handle plugins with special characters in names", async () => {
const spy = createMockSpawn();
await installPlugins(undefined, "org/plugin-name\n@scope/plugin");
expect(spy).toHaveBeenCalledTimes(2);
// Install plugins (no marketplace without explicit marketplace input)
expect(spy).toHaveBeenNthCalledWith(
1,
"claude",
["plugin", "install", "org/plugin-name"],
{ stdio: "inherit" },
);
expect(spy).toHaveBeenNthCalledWith(
2,
"claude",
["plugin", "install", "@scope/plugin"],
{ stdio: "inherit" },
);
});
test("should handle spawn errors", async () => {
createMockSpawn(0, true); // Trigger error event
await expect(installPlugins(undefined, "test-plugin")).rejects.toThrow(
"Failed to install plugin 'test-plugin': spawn error",
);
});
test("should install plugins with custom executable and multiple plugins", async () => {
const spy = createMockSpawn();
await installPlugins(
undefined,
"plugin-a\nplugin-b",
"/usr/local/bin/claude-custom",
);
expect(spy).toHaveBeenCalledTimes(2);
// Install plugins (no marketplace without explicit marketplace input)
expect(spy).toHaveBeenNthCalledWith(
1,
"/usr/local/bin/claude-custom",
["plugin", "install", "plugin-a"],
{ stdio: "inherit" },
);
expect(spy).toHaveBeenNthCalledWith(
2,
"/usr/local/bin/claude-custom",
["plugin", "install", "plugin-b"],
{ stdio: "inherit" },
);
});
test("should reject plugin names with command injection attempts", async () => {
const spy = createMockSpawn();
// Should throw due to invalid characters (semicolon and spaces)
await expect(
installPlugins(undefined, "plugin-name; rm -rf /"),
).rejects.toThrow("Invalid plugin name format");
// Mock should never be called because validation fails first
expect(spy).not.toHaveBeenCalled();
});
test("should reject plugin names with path traversal using ../", async () => {
const spy = createMockSpawn();
await expect(
installPlugins(undefined, "../../../malicious-plugin"),
).rejects.toThrow("Invalid plugin name format");
expect(spy).not.toHaveBeenCalled();
});
test("should reject plugin names with path traversal using ./", async () => {
const spy = createMockSpawn();
await expect(
installPlugins(undefined, "./../../@scope/package"),
).rejects.toThrow("Invalid plugin name format");
expect(spy).not.toHaveBeenCalled();
});
test("should reject plugin names with consecutive dots", async () => {
const spy = createMockSpawn();
await expect(installPlugins(undefined, ".../.../package")).rejects.toThrow(
"Invalid plugin name format",
);
expect(spy).not.toHaveBeenCalled();
});
test("should reject plugin names with hidden path traversal", async () => {
const spy = createMockSpawn();
await expect(installPlugins(undefined, "package/../other")).rejects.toThrow(
"Invalid plugin name format",
);
expect(spy).not.toHaveBeenCalled();
});
test("should accept plugin names with single dots in version numbers", async () => {
const spy = createMockSpawn();
await installPlugins(undefined, "plugin-v1.0.2");
expect(spy).toHaveBeenCalledTimes(1);
// Only call: install plugin (no marketplace without explicit marketplace input)
expect(spy).toHaveBeenNthCalledWith(
1,
"claude",
["plugin", "install", "plugin-v1.0.2"],
{ stdio: "inherit" },
);
});
test("should accept plugin names with multiple dots in semantic versions", async () => {
const spy = createMockSpawn();
await installPlugins(undefined, "@scope/plugin-v1.0.0-beta.1");
expect(spy).toHaveBeenCalledTimes(1);
// Only call: install plugin (no marketplace without explicit marketplace input)
expect(spy).toHaveBeenNthCalledWith(
1,
"claude",
["plugin", "install", "@scope/plugin-v1.0.0-beta.1"],
{ stdio: "inherit" },
);
});
test("should reject Unicode homoglyph path traversal attempts", async () => {
const spy = createMockSpawn();
// Using fullwidth dots (U+FF0E) and fullwidth solidus (U+FF0F)
await expect(installPlugins(undefined, "../malicious")).rejects.toThrow(
"Invalid plugin name format",
);
expect(spy).not.toHaveBeenCalled();
});
test("should reject path traversal at end of path", async () => {
const spy = createMockSpawn();
await expect(installPlugins(undefined, "package/..")).rejects.toThrow(
"Invalid plugin name format",
);
expect(spy).not.toHaveBeenCalled();
});
test("should reject single dot directory reference", async () => {
const spy = createMockSpawn();
await expect(installPlugins(undefined, "package/.")).rejects.toThrow(
"Invalid plugin name format",
);
expect(spy).not.toHaveBeenCalled();
});
test("should reject path traversal in middle of path", async () => {
const spy = createMockSpawn();
await expect(installPlugins(undefined, "package/../other")).rejects.toThrow(
"Invalid plugin name format",
);
expect(spy).not.toHaveBeenCalled();
});
// Marketplace functionality tests
test("should add a single marketplace before installing plugins", async () => {
const spy = createMockSpawn();
await installPlugins(
"https://github.com/user/marketplace.git",
"test-plugin",
);
expect(spy).toHaveBeenCalledTimes(2);
// First call: add marketplace
expect(spy).toHaveBeenNthCalledWith(
1,
"claude",
[
"plugin",
"marketplace",
"add",
"https://github.com/user/marketplace.git",
],
{ stdio: "inherit" },
);
// Second call: install plugin
expect(spy).toHaveBeenNthCalledWith(
2,
"claude",
["plugin", "install", "test-plugin"],
{ stdio: "inherit" },
);
});
test("should add multiple marketplaces with newline separation", async () => {
const spy = createMockSpawn();
await installPlugins(
"https://github.com/user/m1.git\nhttps://github.com/user/m2.git",
"test-plugin",
);
expect(spy).toHaveBeenCalledTimes(3); // 2 marketplaces + 1 plugin
// First two calls: add marketplaces
expect(spy).toHaveBeenNthCalledWith(
1,
"claude",
["plugin", "marketplace", "add", "https://github.com/user/m1.git"],
{ stdio: "inherit" },
);
expect(spy).toHaveBeenNthCalledWith(
2,
"claude",
["plugin", "marketplace", "add", "https://github.com/user/m2.git"],
{ stdio: "inherit" },
);
// Third call: install plugin
expect(spy).toHaveBeenNthCalledWith(
3,
"claude",
["plugin", "install", "test-plugin"],
{ stdio: "inherit" },
);
});
test("should add marketplaces before installing multiple plugins", async () => {
const spy = createMockSpawn();
await installPlugins(
"https://github.com/user/marketplace.git",
"plugin1\nplugin2",
);
expect(spy).toHaveBeenCalledTimes(3); // 1 marketplace + 2 plugins
// First call: add marketplace
expect(spy).toHaveBeenNthCalledWith(
1,
"claude",
[
"plugin",
"marketplace",
"add",
"https://github.com/user/marketplace.git",
],
{ stdio: "inherit" },
);
// Next calls: install plugins
expect(spy).toHaveBeenNthCalledWith(
2,
"claude",
["plugin", "install", "plugin1"],
{ stdio: "inherit" },
);
expect(spy).toHaveBeenNthCalledWith(
3,
"claude",
["plugin", "install", "plugin2"],
{ stdio: "inherit" },
);
});
test("should handle only marketplaces without plugins", async () => {
const spy = createMockSpawn();
await installPlugins("https://github.com/user/marketplace.git", undefined);
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenNthCalledWith(
1,
"claude",
[
"plugin",
"marketplace",
"add",
"https://github.com/user/marketplace.git",
],
{ stdio: "inherit" },
);
});
test("should skip empty marketplace entries", async () => {
const spy = createMockSpawn();
await installPlugins(
"https://github.com/user/m1.git\n\nhttps://github.com/user/m2.git",
"test-plugin",
);
expect(spy).toHaveBeenCalledTimes(3); // 2 marketplaces (skip empty) + 1 plugin
});
test("should trim whitespace from marketplace URLs", async () => {
const spy = createMockSpawn();
await installPlugins(
" https://github.com/user/marketplace.git \n https://github.com/user/m2.git ",
"test-plugin",
);
expect(spy).toHaveBeenCalledTimes(3);
expect(spy).toHaveBeenNthCalledWith(
1,
"claude",
[
"plugin",
"marketplace",
"add",
"https://github.com/user/marketplace.git",
],
{ stdio: "inherit" },
);
expect(spy).toHaveBeenNthCalledWith(
2,
"claude",
["plugin", "marketplace", "add", "https://github.com/user/m2.git"],
{ stdio: "inherit" },
);
});
test("should reject invalid marketplace URL format", async () => {
const spy = createMockSpawn();
await expect(
installPlugins("not-a-valid-url", "test-plugin"),
).rejects.toThrow("Invalid marketplace URL format");
expect(spy).not.toHaveBeenCalled();
});
test("should reject marketplace URL without .git extension", async () => {
const spy = createMockSpawn();
await expect(
installPlugins("https://github.com/user/marketplace", "test-plugin"),
).rejects.toThrow("Invalid marketplace URL format");
expect(spy).not.toHaveBeenCalled();
});
test("should reject marketplace URL with non-https protocol", async () => {
const spy = createMockSpawn();
await expect(
installPlugins("http://github.com/user/marketplace.git", "test-plugin"),
).rejects.toThrow("Invalid marketplace URL format");
expect(spy).not.toHaveBeenCalled();
});
test("should skip whitespace-only marketplace input", async () => {
const spy = createMockSpawn();
await installPlugins(" ", "test-plugin");
// Should skip marketplaces and only install plugin
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenNthCalledWith(
1,
"claude",
["plugin", "install", "test-plugin"],
{ stdio: "inherit" },
);
});
test("should handle marketplace addition error", async () => {
createMockSpawn(1, false); // Exit code 1
await expect(
installPlugins("https://github.com/user/marketplace.git", "test-plugin"),
).rejects.toThrow(
"Failed to add marketplace 'https://github.com/user/marketplace.git' (exit code: 1)",
);
});
test("should stop if marketplace addition fails before installing plugins", async () => {
const spy = createMockSpawn(1, false); // Exit code 1
await expect(
installPlugins(
"https://github.com/user/marketplace.git",
"plugin1\nplugin2",
),
).rejects.toThrow("Failed to add marketplace");
// Should only try to add marketplace, not install any plugins
expect(spy).toHaveBeenCalledTimes(1);
});
test("should use custom executable for marketplace operations", async () => {
const spy = createMockSpawn();
await installPlugins(
"https://github.com/user/marketplace.git",
"test-plugin",
"/custom/path/to/claude",
);
expect(spy).toHaveBeenCalledTimes(2);
expect(spy).toHaveBeenNthCalledWith(
1,
"/custom/path/to/claude",
[
"plugin",
"marketplace",
"add",
"https://github.com/user/marketplace.git",
],
{ stdio: "inherit" },
);
expect(spy).toHaveBeenNthCalledWith(
2,
"/custom/path/to/claude",
["plugin", "install", "test-plugin"],
{ stdio: "inherit" },
);
});
// Local marketplace path tests
test("should accept local marketplace path with ./", async () => {
const spy = createMockSpawn();
await installPlugins("./my-local-marketplace", "test-plugin");
expect(spy).toHaveBeenCalledTimes(2);
expect(spy).toHaveBeenNthCalledWith(
1,
"claude",
["plugin", "marketplace", "add", "./my-local-marketplace"],
{ stdio: "inherit" },
);
expect(spy).toHaveBeenNthCalledWith(
2,
"claude",
["plugin", "install", "test-plugin"],
{ stdio: "inherit" },
);
});
test("should accept local marketplace path with absolute Unix path", async () => {
const spy = createMockSpawn();
await installPlugins("/home/user/my-marketplace", "test-plugin");
expect(spy).toHaveBeenCalledTimes(2);
expect(spy).toHaveBeenNthCalledWith(
1,
"claude",
["plugin", "marketplace", "add", "/home/user/my-marketplace"],
{ stdio: "inherit" },
);
});
test("should accept local marketplace path with Windows absolute path", async () => {
const spy = createMockSpawn();
await installPlugins("C:\\Users\\user\\marketplace", "test-plugin");
expect(spy).toHaveBeenCalledTimes(2);
expect(spy).toHaveBeenNthCalledWith(
1,
"claude",
["plugin", "marketplace", "add", "C:\\Users\\user\\marketplace"],
{ stdio: "inherit" },
);
});
test("should accept mixed local and remote marketplaces", async () => {
const spy = createMockSpawn();
await installPlugins(
"./local-marketplace\nhttps://github.com/user/remote.git",
"test-plugin",
);
expect(spy).toHaveBeenCalledTimes(3);
expect(spy).toHaveBeenNthCalledWith(
1,
"claude",
["plugin", "marketplace", "add", "./local-marketplace"],
{ stdio: "inherit" },
);
expect(spy).toHaveBeenNthCalledWith(
2,
"claude",
["plugin", "marketplace", "add", "https://github.com/user/remote.git"],
{ stdio: "inherit" },
);
});
test("should accept local path with ../ (parent directory)", async () => {
const spy = createMockSpawn();
await installPlugins("../shared-plugins/marketplace", "test-plugin");
expect(spy).toHaveBeenCalledTimes(2);
expect(spy).toHaveBeenNthCalledWith(
1,
"claude",
["plugin", "marketplace", "add", "../shared-plugins/marketplace"],
{ stdio: "inherit" },
);
});
test("should accept local path with nested directories", async () => {
const spy = createMockSpawn();
await installPlugins("./plugins/my-org/my-marketplace", "test-plugin");
expect(spy).toHaveBeenCalledTimes(2);
expect(spy).toHaveBeenNthCalledWith(
1,
"claude",
["plugin", "marketplace", "add", "./plugins/my-org/my-marketplace"],
{ stdio: "inherit" },
);
});
test("should accept local path with dots in directory name", async () => {
const spy = createMockSpawn();
await installPlugins("./my.plugin.marketplace", "test-plugin");
expect(spy).toHaveBeenCalledTimes(2);
expect(spy).toHaveBeenNthCalledWith(
1,
"claude",
["plugin", "marketplace", "add", "./my.plugin.marketplace"],
{ stdio: "inherit" },
);
});
});
+10
View File
@@ -0,0 +1,10 @@
{
"mcpServers": {
"test-server": {
"type": "stdio",
"command": "bun",
"args": ["simple-mcp-server.ts"],
"env": {}
}
}
}
+2
View File
@@ -0,0 +1,2 @@
engine-strict=true
registry=https://registry.npmjs.org/
+186
View File
@@ -0,0 +1,186 @@
{
"lockfileVersion": 1,
"workspaces": {
"": {
"name": "mcp-test",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.11.0",
},
},
},
"packages": {
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.12.0", "", { "dependencies": { "ajv": "^6.12.6", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-m//7RlINx1F3sz3KqwY1WWzVgTcYX52HYk4bJ1hkBXV3zccAEth+jRvG8DBRrdaQuRsPAJOx2MH3zaHNCKL7Zg=="],
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
"ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
"body-parser": ["body-parser@2.2.0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.0", "http-errors": "^2.0.0", "iconv-lite": "^0.6.3", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.0", "type-is": "^2.0.0" } }, "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg=="],
"bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
"call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
"content-disposition": ["content-disposition@1.0.0", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg=="],
"content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
"cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
"cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],
"cors": ["cors@2.8.5", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g=="],
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
"depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
"encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
"escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
"etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
"eventsource": ["eventsource@3.0.7", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="],
"eventsource-parser": ["eventsource-parser@3.0.2", "", {}, "sha512-6RxOBZ/cYgd8usLwsEl+EC09Au/9BcmCKYF2/xbml6DNczf7nv0MQb+7BA2F+li6//I+28VNlQR37XfQtcAJuA=="],
"express": ["express@5.1.0", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA=="],
"express-rate-limit": ["express-rate-limit@7.5.0", "", { "peerDependencies": { "express": "^4.11 || 5 || ^5.0.0-beta.1" } }, "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg=="],
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
"finalhandler": ["finalhandler@2.1.0", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q=="],
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
"fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
"http-errors": ["http-errors@2.0.0", "", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" } }, "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ=="],
"iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
"is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="],
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
"json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
"media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
"merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],
"mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
"mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
"on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
"parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
"path-to-regexp": ["path-to-regexp@8.2.0", "", {}, "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ=="],
"pkce-challenge": ["pkce-challenge@5.0.0", "", {}, "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ=="],
"proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
"qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="],
"range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
"raw-body": ["raw-body@3.0.0", "", { "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", "iconv-lite": "0.6.3", "unpipe": "1.0.0" } }, "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g=="],
"router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="],
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
"send": ["send@1.2.0", "", { "dependencies": { "debug": "^4.3.5", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.0", "mime-types": "^3.0.1", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.1" } }, "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw=="],
"serve-static": ["serve-static@2.2.0", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ=="],
"setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
"side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="],
"side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="],
"side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="],
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
"statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="],
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
"type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="],
"unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
"zod": ["zod@3.25.32", "", {}, "sha512-OSm2xTIRfW8CV5/QKgngwmQW/8aPfGdaQFlrGoErlgg/Epm7cjb6K6VEyExfe65a3VybUOnu381edLb0dfJl0g=="],
"zod-to-json-schema": ["zod-to-json-schema@3.24.5", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g=="],
}
}
+7
View File
@@ -0,0 +1,7 @@
{
"name": "mcp-test",
"version": "1.0.0",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.24.0"
}
}
@@ -0,0 +1,29 @@
#!/usr/bin/env bun
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const server = new McpServer({
name: "test-server",
version: "1.0.0",
});
server.tool("test_tool", "A simple test tool", {}, async () => {
return {
content: [
{
type: "text",
text: "Test tool response",
},
],
};
});
async function runServer() {
const transport = new StdioServerTransport();
await server.connect(transport);
process.on("exit", () => {
server.close();
});
}
runServer().catch(console.error);
+641
View File
@@ -0,0 +1,641 @@
#!/usr/bin/env bun
import { describe, test, expect } from "bun:test";
import { parseSdkOptions } from "../src/parse-sdk-options";
import type { ClaudeOptions } from "../src/run-claude";
describe("parseSdkOptions", () => {
describe("allowedTools merging", () => {
test("should extract allowedTools from claudeArgs", () => {
const options: ClaudeOptions = {
claudeArgs: '--allowedTools "Edit,Read,Write"',
};
const result = parseSdkOptions(options);
expect(result.sdkOptions.allowedTools).toEqual(["Edit", "Read", "Write"]);
expect(result.sdkOptions.extraArgs?.["allowedTools"]).toBeUndefined();
});
test("should extract allowedTools from claudeArgs with MCP tools", () => {
const options: ClaudeOptions = {
claudeArgs:
'--allowedTools "Edit,Read,mcp__github_comment__update_claude_comment"',
};
const result = parseSdkOptions(options);
expect(result.sdkOptions.allowedTools).toEqual([
"Edit",
"Read",
"mcp__github_comment__update_claude_comment",
]);
});
test("should accumulate multiple --allowedTools flags from claudeArgs", () => {
// This simulates tag mode adding its tools, then user adding their own
const options: ClaudeOptions = {
claudeArgs:
'--allowedTools "Edit,Read,mcp__github_comment__update_claude_comment" --model "claude-3" --allowedTools "Bash(npm install),mcp__github__get_issue"',
};
const result = parseSdkOptions(options);
expect(result.sdkOptions.allowedTools).toEqual([
"Edit",
"Read",
"mcp__github_comment__update_claude_comment",
"Bash(npm install)",
"mcp__github__get_issue",
]);
});
test("should merge allowedTools from both claudeArgs and direct options", () => {
const options: ClaudeOptions = {
claudeArgs: '--allowedTools "Edit,Read"',
allowedTools: "Write,Glob",
};
const result = parseSdkOptions(options);
expect(result.sdkOptions.allowedTools).toEqual([
"Edit",
"Read",
"Write",
"Glob",
]);
});
test("should deduplicate allowedTools when merging", () => {
const options: ClaudeOptions = {
claudeArgs: '--allowedTools "Edit,Read"',
allowedTools: "Edit,Write",
};
const result = parseSdkOptions(options);
expect(result.sdkOptions.allowedTools).toEqual(["Edit", "Read", "Write"]);
});
test("should use only direct options when claudeArgs has no allowedTools", () => {
const options: ClaudeOptions = {
claudeArgs: '--model "claude-3-5-sonnet"',
allowedTools: "Edit,Read",
};
const result = parseSdkOptions(options);
expect(result.sdkOptions.allowedTools).toEqual(["Edit", "Read"]);
});
test("should return undefined allowedTools when neither source has it", () => {
const options: ClaudeOptions = {
claudeArgs: '--model "claude-3-5-sonnet"',
};
const result = parseSdkOptions(options);
expect(result.sdkOptions.allowedTools).toBeUndefined();
});
test("should remove allowedTools from extraArgs after extraction", () => {
const options: ClaudeOptions = {
claudeArgs: '--allowedTools "Edit,Read" --model "claude-3-5-sonnet"',
};
const result = parseSdkOptions(options);
expect(result.sdkOptions.extraArgs?.["allowedTools"]).toBeUndefined();
expect(result.sdkOptions.extraArgs?.["model"]).toBeUndefined();
expect(result.sdkOptions.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 preserve unquoted Bash(cmd:*) rules instead of collapsing to bare Bash", () => {
// Regression: shell-quote tokenizes unquoted `(`/`)` as control ops and
// `*` as a glob, which were filtered out — collapsing scoped rules like
// `Bash(gh:*)` into bare `Bash` (= Bash(*), unrestricted shell).
const options: ClaudeOptions = {
claudeArgs: "--allowedTools View,Bash(gh:*),Bash(cat:*)",
};
const result = parseSdkOptions(options);
expect(result.sdkOptions.allowedTools).toEqual([
"View",
"Bash(gh:*)",
"Bash(cat:*)",
]);
expect(result.sdkOptions.allowedTools).not.toContain("Bash");
});
test("should preserve unquoted space-separated Bash(cmd:*) rules", () => {
const options: ClaudeOptions = {
claudeArgs: "--allowed-tools Bash(gh:*) Bash(cat:*) Read(//tmp/**)",
};
const result = parseSdkOptions(options);
expect(result.sdkOptions.allowedTools).toEqual([
"Bash(gh:*)",
"Bash(cat:*)",
"Read(//tmp/**)",
]);
expect(result.sdkOptions.allowedTools).not.toContain("Bash");
});
test("should preserve unquoted Tool(content) rules without glob chars", () => {
const options: ClaudeOptions = {
claudeArgs:
"--allowedTools Read(~/file),WebFetch(domain:example.com),Edit",
};
const result = parseSdkOptions(options);
expect(result.sdkOptions.allowedTools).toEqual([
"Read(~/file)",
"WebFetch(domain:example.com)",
"Edit",
]);
});
test("should still preserve quoted Bash(cmd:*) rules (no regression)", () => {
const options: ClaudeOptions = {
claudeArgs: '--allowedTools "Bash(gh:*),Bash(cat:*)"',
};
const result = parseSdkOptions(options);
expect(result.sdkOptions.allowedTools).toEqual([
"Bash(gh:*)",
"Bash(cat:*)",
]);
});
test("should merge quoted tag-mode tools with unquoted user tools without widening", () => {
// Real-world shape: the action's tag mode wraps its own --allowedTools in
// double quotes, then appends the user's claude_args (typically unquoted
// in workflow YAML). Both halves must round-trip.
const options: ClaudeOptions = {
claudeArgs:
'--permission-mode acceptEdits --allowedTools "Glob,Grep,Read,Bash(git add:*),Bash(git commit:*)" ' +
"--model claude-opus-4-7\n" +
"--allowedTools View,Bash(gh:*),Bash(printf:*),Bash(cat:*)",
};
const result = parseSdkOptions(options);
expect(result.sdkOptions.allowedTools).toEqual([
"Glob",
"Grep",
"Read",
"Bash(git add:*)",
"Bash(git commit:*)",
"View",
"Bash(gh:*)",
"Bash(printf:*)",
"Bash(cat:*)",
]);
expect(result.sdkOptions.allowedTools).not.toContain("Bash");
});
test("should preserve unquoted disallowedTools rules without widening", () => {
// Same bug class on the deny side: a scoped deny collapsing to bare
// `Bash` would block all shell instead of the intended prefix.
const options: ClaudeOptions = {
claudeArgs: "--disallowedTools Bash(rm:*),Bash(sudo:*)",
};
const result = parseSdkOptions(options);
expect(result.sdkOptions.disallowedTools).toEqual([
"Bash(rm:*)",
"Bash(sudo:*)",
]);
expect(result.sdkOptions.disallowedTools).not.toContain("Bash");
});
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", () => {
test("should extract disallowedTools from claudeArgs", () => {
const options: ClaudeOptions = {
claudeArgs: '--disallowedTools "Bash,Write"',
};
const result = parseSdkOptions(options);
expect(result.sdkOptions.disallowedTools).toEqual(["Bash", "Write"]);
expect(result.sdkOptions.extraArgs?.["disallowedTools"]).toBeUndefined();
});
test("should merge disallowedTools from both sources", () => {
const options: ClaudeOptions = {
claudeArgs: '--disallowedTools "Bash"',
disallowedTools: "Write",
};
const result = parseSdkOptions(options);
expect(result.sdkOptions.disallowedTools).toEqual(["Bash", "Write"]);
});
});
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"]).toBeUndefined();
expect(result.sdkOptions.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("add-dir handling", () => {
test("should accumulate multiple add-dir flags into additionalDirectories", () => {
const options: ClaudeOptions = {
claudeArgs: '--add-dir "/path/to/dir-a"\n--add-dir "/path/to/dir-b"',
};
const result = parseSdkOptions(options);
expect(result.sdkOptions.additionalDirectories).toEqual([
"/path/to/dir-a",
"/path/to/dir-b",
]);
expect(result.sdkOptions.extraArgs?.["add-dir"]).toBeUndefined();
});
test("should map a single add-dir flag to additionalDirectories", () => {
const options: ClaudeOptions = {
claudeArgs: '--add-dir "/path/to/dir"',
};
const result = parseSdkOptions(options);
expect(result.sdkOptions.additionalDirectories).toEqual(["/path/to/dir"]);
expect(result.sdkOptions.extraArgs?.["add-dir"]).toBeUndefined();
});
test("should preserve other extraArgs when extracting add-dir", () => {
const options: ClaudeOptions = {
claudeArgs: '--model "claude-3-5-sonnet" --add-dir "/path/to/dir"',
};
const result = parseSdkOptions(options);
expect(result.sdkOptions.additionalDirectories).toEqual(["/path/to/dir"]);
expect(result.sdkOptions.extraArgs?.["model"]).toBeUndefined();
expect(result.sdkOptions.model).toBe("claude-3-5-sonnet");
expect(result.sdkOptions.extraArgs?.["add-dir"]).toBeUndefined();
});
});
describe("other extraArgs passthrough", () => {
test("should pass through json-schema in extraArgs", () => {
const options: ClaudeOptions = {
claudeArgs: `--json-schema '{"type":"object"}'`,
};
const result = parseSdkOptions(options);
expect(result.sdkOptions.extraArgs?.["json-schema"]).toBe(
'{"type":"object"}',
);
expect(result.hasJsonSchema).toBe(true);
});
});
describe("shell comment stripping", () => {
test("should parse flags before and after a comment line", () => {
const options: ClaudeOptions = {
claudeArgs: "--model 'claude-haiku'\n# comment\n--allowed-tools 'Edit'",
};
const result = parseSdkOptions(options);
expect(result.sdkOptions.extraArgs?.["model"]).toBeUndefined();
expect(result.sdkOptions.model).toBe("claude-haiku");
expect(result.sdkOptions.allowedTools).toEqual(["Edit"]);
});
test("should parse flags correctly when no comments are present", () => {
const options: ClaudeOptions = {
claudeArgs: "--model 'claude-haiku'",
};
const result = parseSdkOptions(options);
expect(result.sdkOptions.extraArgs?.["model"]).toBeUndefined();
expect(result.sdkOptions.model).toBe("claude-haiku");
});
test("should not strip inline # that appears inside a quoted value", () => {
const options: ClaudeOptions = {
claudeArgs: "--model 'claude-haiku' --prompt 'use color #ff0000'",
};
const result = parseSdkOptions(options);
expect(result.sdkOptions.extraArgs?.["model"]).toBeUndefined();
expect(result.sdkOptions.model).toBe("claude-haiku");
expect(result.sdkOptions.extraArgs?.["prompt"]).toBe("use color #ff0000");
});
});
describe("model handling", () => {
test("should map --model from claudeArgs to sdkOptions.model", () => {
const options: ClaudeOptions = {
claudeArgs: "--model claude-haiku-4-5-20251001",
};
const result = parseSdkOptions(options);
expect(result.sdkOptions.model).toBe("claude-haiku-4-5-20251001");
expect(result.sdkOptions.extraArgs?.["model"]).toBeUndefined();
});
test("should prefer direct model option over --model from claudeArgs", () => {
const options: ClaudeOptions = {
model: "claude-sonnet-4-6",
claudeArgs: "--model claude-haiku-4-5-20251001",
};
const result = parseSdkOptions(options);
expect(result.sdkOptions.model).toBe("claude-sonnet-4-6");
expect(result.sdkOptions.extraArgs?.["model"]).toBeUndefined();
});
});
describe("max turns handling", () => {
test("should map --max-turns from claudeArgs to sdkOptions.maxTurns", () => {
const options: ClaudeOptions = {
claudeArgs: "--max-turns 60",
};
const result = parseSdkOptions(options);
expect(result.sdkOptions.maxTurns).toBe(60);
expect(result.sdkOptions.extraArgs?.["max-turns"]).toBeUndefined();
});
test("should prefer the direct maxTurns option", () => {
const options: ClaudeOptions = {
maxTurns: "25",
claudeArgs: "--max-turns 60",
};
const result = parseSdkOptions(options);
expect(result.sdkOptions.maxTurns).toBe(25);
expect(result.sdkOptions.extraArgs?.["max-turns"]).toBeUndefined();
});
});
describe("environment variables passthrough", () => {
test("should include OTEL environment variables in sdkOptions.env", () => {
// Set up test environment variables
const originalEnv = { ...process.env };
process.env.CLAUDE_CODE_ENABLE_TELEMETRY = "1";
process.env.OTEL_METRICS_EXPORTER = "otlp";
process.env.OTEL_LOGS_EXPORTER = "otlp";
process.env.OTEL_EXPORTER_OTLP_PROTOCOL = "http/json";
process.env.OTEL_EXPORTER_OTLP_ENDPOINT = "https://example.com";
process.env.OTEL_EXPORTER_OTLP_HEADERS =
"Authorization=Bearer test-token";
process.env.OTEL_METRIC_EXPORT_INTERVAL = "10000";
process.env.OTEL_LOGS_EXPORT_INTERVAL = "5000";
process.env.OTEL_RESOURCE_ATTRIBUTES = "department=test";
try {
const options: ClaudeOptions = {};
const result = parseSdkOptions(options);
// Verify OTEL env vars are passed through to sdkOptions.env
expect(result.sdkOptions.env?.CLAUDE_CODE_ENABLE_TELEMETRY).toBe("1");
expect(result.sdkOptions.env?.OTEL_METRICS_EXPORTER).toBe("otlp");
expect(result.sdkOptions.env?.OTEL_LOGS_EXPORTER).toBe("otlp");
expect(result.sdkOptions.env?.OTEL_EXPORTER_OTLP_PROTOCOL).toBe(
"http/json",
);
expect(result.sdkOptions.env?.OTEL_EXPORTER_OTLP_ENDPOINT).toBe(
"https://example.com",
);
expect(result.sdkOptions.env?.OTEL_EXPORTER_OTLP_HEADERS).toBe(
"Authorization=Bearer test-token",
);
expect(result.sdkOptions.env?.OTEL_METRIC_EXPORT_INTERVAL).toBe(
"10000",
);
expect(result.sdkOptions.env?.OTEL_LOGS_EXPORT_INTERVAL).toBe("5000");
expect(result.sdkOptions.env?.OTEL_RESOURCE_ATTRIBUTES).toBe(
"department=test",
);
} finally {
// Restore original environment
process.env = originalEnv;
}
});
test("should set CLAUDE_CODE_ENTRYPOINT in sdkOptions.env", () => {
const options: ClaudeOptions = {};
const result = parseSdkOptions(options);
expect(result.sdkOptions.env?.CLAUDE_CODE_ENTRYPOINT).toBe(
"claude-code-github-action",
);
});
test("should strip ACTIONS_ID_TOKEN_REQUEST_URL and ACTIONS_ID_TOKEN_REQUEST_TOKEN from env", () => {
const originalEnv = { ...process.env };
process.env.ACTIONS_ID_TOKEN_REQUEST_URL =
"https://token.actions.githubusercontent.com";
process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN = "secret-token-value";
try {
const options: ClaudeOptions = {};
const result = parseSdkOptions(options);
expect(
result.sdkOptions.env?.ACTIONS_ID_TOKEN_REQUEST_URL,
).toBeUndefined();
expect(
result.sdkOptions.env?.ACTIONS_ID_TOKEN_REQUEST_TOKEN,
).toBeUndefined();
} finally {
process.env = originalEnv;
}
});
test("should strip ALL_INPUTS from env", () => {
const originalEnv = { ...process.env };
process.env.ALL_INPUTS = JSON.stringify({
anthropic_api_key: "sk-ant-test-key",
github_token: "ghp_test_token",
});
try {
const options: ClaudeOptions = {};
const result = parseSdkOptions(options);
expect(result.sdkOptions.env?.ALL_INPUTS).toBeUndefined();
} finally {
process.env = originalEnv;
}
});
});
});
+67
View File
@@ -0,0 +1,67 @@
import { describe, expect, test } from "bun:test";
import { parse as parseShellArgs } from "shell-quote";
describe("shell-quote parseShellArgs", () => {
test("should handle empty input", () => {
expect(parseShellArgs("")).toEqual([]);
expect(parseShellArgs(" ")).toEqual([]);
});
test("should parse simple arguments", () => {
expect(parseShellArgs("--max-turns 3")).toEqual(["--max-turns", "3"]);
expect(parseShellArgs("-a -b -c")).toEqual(["-a", "-b", "-c"]);
});
test("should handle double quotes", () => {
expect(parseShellArgs('--config "/path/to/config.json"')).toEqual([
"--config",
"/path/to/config.json",
]);
expect(parseShellArgs('"arg with spaces"')).toEqual(["arg with spaces"]);
});
test("should handle single quotes", () => {
expect(parseShellArgs("--config '/path/to/config.json'")).toEqual([
"--config",
"/path/to/config.json",
]);
expect(parseShellArgs("'arg with spaces'")).toEqual(["arg with spaces"]);
});
test("should handle escaped characters", () => {
expect(parseShellArgs("arg\\ with\\ spaces")).toEqual(["arg with spaces"]);
expect(parseShellArgs('arg\\"with\\"quotes')).toEqual(['arg"with"quotes']);
});
test("should handle mixed quotes", () => {
expect(parseShellArgs(`--msg "It's a test"`)).toEqual([
"--msg",
"It's a test",
]);
expect(parseShellArgs(`--msg 'He said "hello"'`)).toEqual([
"--msg",
'He said "hello"',
]);
});
test("should handle complex real-world example", () => {
const input = `--max-turns 3 --mcp-config "/Users/john/config.json" --model claude-3-5-sonnet-latest --system-prompt 'You are helpful'`;
expect(parseShellArgs(input)).toEqual([
"--max-turns",
"3",
"--mcp-config",
"/Users/john/config.json",
"--model",
"claude-3-5-sonnet-latest",
"--system-prompt",
"You are helpful",
]);
});
test("should filter out non-string results", () => {
// shell-quote can return objects for operators like | > < etc
const result = parseShellArgs("echo hello");
const filtered = result.filter((arg) => typeof arg === "string");
expect(filtered).toEqual(["echo", "hello"]);
});
});
+114
View File
@@ -0,0 +1,114 @@
#!/usr/bin/env bun
import { describe, test, expect, beforeEach, afterEach } from "bun:test";
import { preparePrompt, type PreparePromptInput } from "../src/prepare-prompt";
import { unlink, writeFile, readFile, stat } from "fs/promises";
describe("preparePrompt integration tests", () => {
beforeEach(async () => {
try {
await unlink("/tmp/claude-action/prompt.txt");
} catch {
// Ignore if file doesn't exist
}
});
afterEach(async () => {
try {
await unlink("/tmp/claude-action/prompt.txt");
} catch {
// Ignore if file doesn't exist
}
});
test("should create temporary prompt file when only prompt is provided", async () => {
const input: PreparePromptInput = {
prompt: "This is a test prompt",
promptFile: "",
};
const config = await preparePrompt(input);
expect(config.path).toBe("/tmp/claude-action/prompt.txt");
expect(config.type).toBe("inline");
const fileContent = await readFile(config.path, "utf-8");
expect(fileContent).toBe("This is a test prompt");
const fileStat = await stat(config.path);
expect(fileStat.size).toBeGreaterThan(0);
});
test("should use existing file when promptFile is provided", async () => {
const testFilePath = "/tmp/test-prompt.txt";
await writeFile(testFilePath, "Prompt from file");
const input: PreparePromptInput = {
prompt: "",
promptFile: testFilePath,
};
const config = await preparePrompt(input);
expect(config.path).toBe(testFilePath);
expect(config.type).toBe("file");
await unlink(testFilePath);
});
test("should fail when neither prompt nor promptFile is provided", async () => {
const input: PreparePromptInput = {
prompt: "",
promptFile: "",
};
await expect(preparePrompt(input)).rejects.toThrow(
"Neither 'prompt' nor 'prompt_file' was provided",
);
});
test("should fail when promptFile points to non-existent file", async () => {
const input: PreparePromptInput = {
prompt: "",
promptFile: "/tmp/non-existent-file.txt",
};
await expect(preparePrompt(input)).rejects.toThrow(
"Prompt file '/tmp/non-existent-file.txt' does not exist.",
);
});
test("should fail when prompt is empty", async () => {
const emptyFilePath = "/tmp/empty-prompt.txt";
await writeFile(emptyFilePath, "");
const input: PreparePromptInput = {
prompt: "",
promptFile: emptyFilePath,
};
await expect(preparePrompt(input)).rejects.toThrow("Prompt file is empty");
try {
await unlink(emptyFilePath);
} catch {
// Ignore cleanup errors
}
});
test("should fail when both prompt and promptFile are provided", async () => {
const testFilePath = "/tmp/test-prompt.txt";
await writeFile(testFilePath, "Prompt from file");
const input: PreparePromptInput = {
prompt: "This should cause an error",
promptFile: testFilePath,
};
await expect(preparePrompt(input)).rejects.toThrow(
"Both 'prompt' and 'prompt_file' were provided. Please specify only one.",
);
await unlink(testFilePath);
});
});
+50
View File
@@ -0,0 +1,50 @@
import { readFileSync } from "node:fs";
import { describe, expect, test } from "bun:test";
const actionMetadata = readFileSync(
new URL("../action.yml", import.meta.url),
"utf8",
);
const readme = readFileSync(new URL("../README.md", import.meta.url), "utf8");
describe("base action README", () => {
test("should document every input declared in the action metadata", () => {
const inputMetadata = actionMetadata.match(
/^inputs:\n([\s\S]*?)^outputs:/m,
)?.[1];
const inputReference = readme.match(
/^## Inputs\n([\s\S]*?)^## Outputs/m,
)?.[1];
expect(inputMetadata).toBeDefined();
expect(inputReference).toBeDefined();
const declaredInputs = [
...(inputMetadata?.matchAll(/^ ([a-z0-9_]+):$/gm) ?? []),
].map((match) => match[1]);
const documentedInputs = [
...(inputReference?.matchAll(/^\| `([^`]+)`/gm) ?? []),
].map((match) => match[1]);
expect(documentedInputs).toEqual(declaredInputs);
});
test("should not use removed legacy inputs in workflow examples", () => {
const removedInputs = [
"allowed_tools",
"disallowed_tools",
"max_turns",
"mcp_config",
"system_prompt",
"append_system_prompt",
"claude_env",
"model",
"anthropic_model",
"fallback_model",
];
for (const input of removedInputs) {
expect(readme).not.toMatch(new RegExp(`^\\s+${input}:`, "m"));
}
});
});
+120
View File
@@ -0,0 +1,120 @@
import { describe, it, expect, beforeEach, afterEach, mock } from "bun:test";
import { retryWithBackoff } from "../src/retry";
describe("retryWithBackoff", () => {
let originalConsoleLog: typeof console.log;
let originalConsoleError: typeof console.error;
beforeEach(() => {
originalConsoleLog = console.log;
originalConsoleError = console.error;
console.log = mock(() => {});
console.error = mock(() => {});
});
afterEach(() => {
console.log = originalConsoleLog;
console.error = originalConsoleError;
});
it("returns the result on first success", async () => {
const result = await retryWithBackoff(() => Promise.resolve("ok"), {
maxAttempts: 3,
initialDelayMs: 1,
});
expect(result).toBe("ok");
});
it("retries on failure and succeeds", async () => {
let attempt = 0;
const result = await retryWithBackoff(
() => {
attempt++;
if (attempt < 3) throw new Error("transient");
return Promise.resolve("recovered");
},
{ maxAttempts: 3, initialDelayMs: 1 },
);
expect(result).toBe("recovered");
expect(attempt).toBe(3);
});
it("throws after exhausting all attempts", async () => {
await expect(
retryWithBackoff(() => Promise.reject(new Error("permanent")), {
maxAttempts: 2,
initialDelayMs: 1,
}),
).rejects.toThrow("permanent");
});
it("stops retrying immediately when shouldRetry returns false", async () => {
class NonRetryableError extends Error {
constructor() {
super("non-retryable");
this.name = "NonRetryableError";
}
}
let attempts = 0;
await expect(
retryWithBackoff(
() => {
attempts++;
throw new NonRetryableError();
},
{
maxAttempts: 3,
initialDelayMs: 1,
shouldRetry: (error) => !(error instanceof NonRetryableError),
},
),
).rejects.toThrow("non-retryable");
expect(attempts).toBe(1);
});
it("continues retrying when shouldRetry returns true", async () => {
let attempts = 0;
await expect(
retryWithBackoff(
() => {
attempts++;
throw new Error("retryable");
},
{
maxAttempts: 3,
initialDelayMs: 1,
shouldRetry: () => true,
},
),
).rejects.toThrow("retryable");
expect(attempts).toBe(3);
});
it("preserves the original error when shouldRetry aborts", async () => {
class SpecificError extends Error {
code = 401;
constructor() {
super("unauthorized");
this.name = "SpecificError";
}
}
try {
await retryWithBackoff(
() => {
throw new SpecificError();
},
{
maxAttempts: 3,
initialDelayMs: 1,
shouldRetry: (error) => !(error instanceof SpecificError),
},
);
expect.unreachable("should have thrown");
} catch (error) {
expect(error).toBeInstanceOf(SpecificError);
expect((error as SpecificError).code).toBe(401);
}
});
});
+287
View File
@@ -0,0 +1,287 @@
#!/usr/bin/env bun
import { afterEach, describe, expect, mock, spyOn, test } from "bun:test";
import { mkdtemp, readFile, rm, writeFile } from "fs/promises";
import { tmpdir } from "os";
import { join } from "path";
describe("runClaudeWithSdk", () => {
const originalRunnerTemp = process.env.RUNNER_TEMP;
let tempDir: string | undefined;
afterEach(async () => {
if (tempDir) {
await rm(tempDir, { recursive: true, force: true });
tempDir = undefined;
}
process.env.RUNNER_TEMP = originalRunnerTemp;
});
test("writes the execution file when the SDK throws after yielding messages", async () => {
const consoleErrorSpy = spyOn(console, "error").mockImplementation(
() => {},
);
const consoleLogSpy = spyOn(console, "log").mockImplementation(() => {});
tempDir = await mkdtemp(join(tmpdir(), "claude-sdk-"));
process.env.RUNNER_TEMP = tempDir;
const promptPath = join(tempDir, "prompt.txt");
await writeFile(promptPath, "test prompt");
const initMessage = {
type: "system",
subtype: "init",
session_id: "session-123",
model: "claude-sonnet-4-6",
};
mock.module("@anthropic-ai/claude-agent-sdk", () => ({
query: async function* () {
yield initMessage;
throw new Error("Claude Code returned error_max_turns");
},
}));
try {
const { runClaudeWithSdk } = await import("../src/run-claude-sdk");
await expect(
runClaudeWithSdk(promptPath, {
sdkOptions: {},
showFullOutput: false,
hasJsonSchema: false,
}),
).rejects.toThrow("SDK execution error");
const executionFile = join(tempDir, "claude-execution-output.json");
await expect(readFile(executionFile, "utf-8")).resolves.toBe(
JSON.stringify([initMessage], null, 2),
);
} finally {
consoleErrorSpy.mockRestore();
consoleLogSpy.mockRestore();
}
});
test("logs resolved model limits without exposing token usage", async () => {
const consoleLogSpy = spyOn(console, "log").mockImplementation(() => {});
tempDir = await mkdtemp(join(tmpdir(), "claude-sdk-"));
process.env.RUNNER_TEMP = tempDir;
const promptPath = join(tempDir, "prompt.txt");
await writeFile(promptPath, "test prompt");
const initMessage = {
type: "system",
subtype: "init",
session_id: "session-123",
model: "claude-opus-5",
};
const resultMessage = {
type: "result",
subtype: "success",
is_error: false,
duration_ms: 434,
num_turns: 1,
total_cost_usd: 1.23,
permission_denials: [],
modelUsage: {
"claude-opus-5": {
inputTokens: 96209,
outputTokens: 55324,
cacheReadInputTokens: 1135701,
cacheCreationInputTokens: 149043,
webSearchRequests: 0,
costUSD: 1.23,
contextWindow: 200000,
maxOutputTokens: 64000,
},
},
};
mock.module("@anthropic-ai/claude-agent-sdk", () => ({
query: async function* () {
yield initMessage;
yield resultMessage;
},
}));
try {
const { runClaudeWithSdk } = await import("../src/run-claude-sdk");
await expect(
runClaudeWithSdk(promptPath, {
sdkOptions: {},
showFullOutput: false,
hasJsonSchema: false,
}),
).resolves.toMatchObject({ conclusion: "success" });
const sanitizedResult = consoleLogSpy.mock.calls
.map(([message]) => message)
.find(
(message) =>
typeof message === "string" && message.includes('"type": "result"'),
);
expect(sanitizedResult).toBeDefined();
if (typeof sanitizedResult !== "string") {
throw new Error("Sanitized result output was not logged");
}
expect(JSON.parse(sanitizedResult)).toEqual({
type: "result",
subtype: "success",
is_error: false,
duration_ms: 434,
num_turns: 1,
total_cost_usd: 1.23,
permission_denials_count: 0,
modelUsage: {
"claude-opus-5": {
contextWindow: 200000,
maxOutputTokens: 64000,
},
},
});
expect(sanitizedResult).not.toContain("inputTokens");
expect(sanitizedResult).not.toContain("costUSD");
} finally {
consoleLogSpy.mockRestore();
}
});
test("fails when result subtype is success but is_error is true", async () => {
const consoleErrorSpy = spyOn(console, "error").mockImplementation(
() => {},
);
const consoleLogSpy = spyOn(console, "log").mockImplementation(() => {});
const coreErrorSpy = spyOn(
await import("@actions/core"),
"error",
).mockImplementation(() => {});
tempDir = await mkdtemp(join(tmpdir(), "claude-sdk-"));
process.env.RUNNER_TEMP = tempDir;
const promptPath = join(tempDir, "prompt.txt");
await writeFile(promptPath, "test prompt");
const initMessage = {
type: "system",
subtype: "init",
session_id: "session-123",
model: "claude-sonnet-5",
};
const errorResultMessage = {
type: "result",
subtype: "success",
is_error: true,
duration_ms: 434,
num_turns: 1,
total_cost_usd: 0,
permission_denials: [],
};
mock.module("@anthropic-ai/claude-agent-sdk", () => ({
query: async function* () {
yield initMessage;
yield errorResultMessage;
},
}));
try {
const { runClaudeWithSdk } = await import("../src/run-claude-sdk");
await expect(
runClaudeWithSdk(promptPath, {
sdkOptions: {},
showFullOutput: false,
hasJsonSchema: false,
}),
).rejects.toThrow("result is_error:true");
const executionFile = join(tempDir, "claude-execution-output.json");
await expect(readFile(executionFile, "utf-8")).resolves.toBe(
JSON.stringify([initMessage, errorResultMessage], null, 2),
);
expect(coreErrorSpy).toHaveBeenCalledWith(
"Claude result reported subtype success with is_error:true (run did not complete successfully)",
);
} finally {
consoleErrorSpy.mockRestore();
consoleLogSpy.mockRestore();
coreErrorSpy.mockRestore();
}
});
test("fails closed when a successful result exceeds maxTurns", async () => {
const consoleErrorSpy = spyOn(console, "error").mockImplementation(
() => {},
);
const consoleLogSpy = spyOn(console, "log").mockImplementation(() => {});
const coreErrorSpy = spyOn(
await import("@actions/core"),
"error",
).mockImplementation(() => {});
tempDir = await mkdtemp(join(tmpdir(), "claude-sdk-"));
process.env.RUNNER_TEMP = tempDir;
const promptPath = join(tempDir, "prompt.txt");
await writeFile(promptPath, "test prompt");
const initMessage = {
type: "system",
subtype: "init",
session_id: "session-123",
model: "claude-opus-4-7",
};
const successResultMessage = {
type: "result",
subtype: "success",
is_error: false,
duration_ms: 960000,
num_turns: 73,
total_cost_usd: 0,
permission_denials: [],
};
mock.module("@anthropic-ai/claude-agent-sdk", () => ({
query: async function* () {
yield initMessage;
yield successResultMessage;
},
}));
try {
const { runClaudeWithSdk } = await import("../src/run-claude-sdk");
await expect(
runClaudeWithSdk(promptPath, {
sdkOptions: { maxTurns: 60 },
showFullOutput: false,
hasJsonSchema: false,
}),
).rejects.toThrow(
"Claude reported a successful result after 73 turns, exceeding the configured maximum of 60",
);
const executionFile = join(tempDir, "claude-execution-output.json");
await expect(readFile(executionFile, "utf-8")).resolves.toBe(
JSON.stringify([initMessage, successResultMessage], null, 2),
);
expect(coreErrorSpy).toHaveBeenCalledWith(
"Claude reported a successful result after 73 turns, exceeding the configured maximum of 60",
);
} finally {
consoleErrorSpy.mockRestore();
consoleLogSpy.mockRestore();
coreErrorSpy.mockRestore();
}
});
});
@@ -0,0 +1,150 @@
#!/usr/bin/env bun
import { describe, test, expect, beforeEach, afterEach } from "bun:test";
import { setupClaudeCodeSettings } from "../src/setup-claude-code-settings";
import { tmpdir } from "os";
import { mkdir, writeFile, readFile, rm } from "fs/promises";
import { join } from "path";
const testHomeDir = join(
tmpdir(),
"claude-code-test-home",
Date.now().toString(),
);
const settingsPath = join(testHomeDir, ".claude", "settings.json");
const testSettingsDir = join(testHomeDir, ".claude-test");
const testSettingsPath = join(testSettingsDir, "test-settings.json");
describe("setupClaudeCodeSettings", () => {
beforeEach(async () => {
// Create test home directory and test settings directory
await mkdir(testHomeDir, { recursive: true });
await mkdir(testSettingsDir, { recursive: true });
});
afterEach(async () => {
// Clean up test home directory
await rm(testHomeDir, { recursive: true, force: true });
});
test("should always set enableAllProjectMcpServers to true when no input", async () => {
await setupClaudeCodeSettings(undefined, testHomeDir);
const settingsContent = await readFile(settingsPath, "utf-8");
const settings = JSON.parse(settingsContent);
expect(settings.enableAllProjectMcpServers).toBe(true);
});
test("should merge settings from JSON string input", async () => {
const inputSettings = JSON.stringify({
model: "claude-sonnet-4-20250514",
env: { API_KEY: "test-key" },
});
await setupClaudeCodeSettings(inputSettings, testHomeDir);
const settingsContent = await readFile(settingsPath, "utf-8");
const settings = JSON.parse(settingsContent);
expect(settings.enableAllProjectMcpServers).toBe(true);
expect(settings.model).toBe("claude-sonnet-4-20250514");
expect(settings.env).toEqual({ API_KEY: "test-key" });
});
test("should merge settings from file path input", async () => {
const testSettings = {
hooks: {
PreToolUse: [
{
matcher: "Bash",
hooks: [{ type: "command", command: "echo test" }],
},
],
},
permissions: {
allow: ["Bash", "Read"],
},
};
await writeFile(testSettingsPath, JSON.stringify(testSettings, null, 2));
await setupClaudeCodeSettings(testSettingsPath, testHomeDir);
const settingsContent = await readFile(settingsPath, "utf-8");
const settings = JSON.parse(settingsContent);
expect(settings.enableAllProjectMcpServers).toBe(true);
expect(settings.hooks).toEqual(testSettings.hooks);
expect(settings.permissions).toEqual(testSettings.permissions);
});
test("should override enableAllProjectMcpServers even if false in input", async () => {
const inputSettings = JSON.stringify({
enableAllProjectMcpServers: false,
model: "test-model",
});
await setupClaudeCodeSettings(inputSettings, testHomeDir);
const settingsContent = await readFile(settingsPath, "utf-8");
const settings = JSON.parse(settingsContent);
expect(settings.enableAllProjectMcpServers).toBe(true);
expect(settings.model).toBe("test-model");
});
test("should throw error for invalid JSON string", async () => {
expect(() =>
setupClaudeCodeSettings("{ invalid json", testHomeDir),
).toThrow();
});
test("should throw error for non-existent file path", async () => {
expect(() =>
setupClaudeCodeSettings("/non/existent/file.json", testHomeDir),
).toThrow();
});
test("should handle empty string input", async () => {
await setupClaudeCodeSettings("", testHomeDir);
const settingsContent = await readFile(settingsPath, "utf-8");
const settings = JSON.parse(settingsContent);
expect(settings.enableAllProjectMcpServers).toBe(true);
});
test("should handle whitespace-only input", async () => {
await setupClaudeCodeSettings(" \n\t ", testHomeDir);
const settingsContent = await readFile(settingsPath, "utf-8");
const settings = JSON.parse(settingsContent);
expect(settings.enableAllProjectMcpServers).toBe(true);
});
test("should merge with existing settings", async () => {
// First, create some existing settings
await setupClaudeCodeSettings(
JSON.stringify({ existingKey: "existingValue" }),
testHomeDir,
);
// Then, add new settings
const newSettings = JSON.stringify({
newKey: "newValue",
model: "claude-opus-4-1-20250805",
});
await setupClaudeCodeSettings(newSettings, testHomeDir);
const settingsContent = await readFile(settingsPath, "utf-8");
const settings = JSON.parse(settingsContent);
expect(settings.enableAllProjectMcpServers).toBe(true);
expect(settings.existingKey).toBe("existingValue");
expect(settings.newKey).toBe("newValue");
expect(settings.model).toBe("claude-opus-4-1-20250805");
});
});
+363
View File
@@ -0,0 +1,363 @@
#!/usr/bin/env bun
import { describe, test, expect, beforeEach, afterEach } from "bun:test";
import { validateEnvironmentVariables } from "../src/validate-env";
describe("validateEnvironmentVariables", () => {
let originalEnv: NodeJS.ProcessEnv;
beforeEach(() => {
// Save the original environment
originalEnv = { ...process.env };
// Clear relevant environment variables
delete process.env.ANTHROPIC_API_KEY;
delete process.env.ANTHROPIC_FEDERATION_RULE_ID;
delete process.env.ANTHROPIC_ORGANIZATION_ID;
delete process.env.CLAUDE_CODE_USE_BEDROCK;
delete process.env.CLAUDE_CODE_USE_VERTEX;
delete process.env.CLAUDE_CODE_USE_FOUNDRY;
delete process.env.AWS_REGION;
delete process.env.AWS_ACCESS_KEY_ID;
delete process.env.AWS_SECRET_ACCESS_KEY;
delete process.env.AWS_SESSION_TOKEN;
delete process.env.AWS_BEARER_TOKEN_BEDROCK;
delete process.env.ANTHROPIC_BEDROCK_BASE_URL;
delete process.env.ANTHROPIC_VERTEX_PROJECT_ID;
delete process.env.CLOUD_ML_REGION;
delete process.env.GOOGLE_APPLICATION_CREDENTIALS;
delete process.env.ANTHROPIC_VERTEX_BASE_URL;
delete process.env.ANTHROPIC_FOUNDRY_RESOURCE;
delete process.env.ANTHROPIC_FOUNDRY_BASE_URL;
});
afterEach(() => {
// Restore the original environment
process.env = originalEnv;
});
describe("Direct Anthropic API", () => {
test("should pass when ANTHROPIC_API_KEY is provided", () => {
process.env.ANTHROPIC_API_KEY = "test-api-key";
expect(() => validateEnvironmentVariables()).not.toThrow();
});
test("should fail when ANTHROPIC_API_KEY is missing", () => {
expect(() => validateEnvironmentVariables()).toThrow(
"Either ANTHROPIC_API_KEY, CLAUDE_CODE_OAUTH_TOKEN, or workload identity federation (ANTHROPIC_FEDERATION_RULE_ID and ANTHROPIC_ORGANIZATION_ID) is required when using direct Anthropic API.",
);
});
test("should pass when workload identity federation variables are provided", () => {
process.env.ANTHROPIC_FEDERATION_RULE_ID = "fdrl_test";
process.env.ANTHROPIC_ORGANIZATION_ID =
"00000000-0000-0000-0000-000000000000";
expect(() => validateEnvironmentVariables()).not.toThrow();
});
test("should fail when only ANTHROPIC_FEDERATION_RULE_ID is provided", () => {
process.env.ANTHROPIC_FEDERATION_RULE_ID = "fdrl_test";
expect(() => validateEnvironmentVariables()).toThrow(
"Workload identity federation requires both ANTHROPIC_FEDERATION_RULE_ID and ANTHROPIC_ORGANIZATION_ID to be set.",
);
});
test("should fail when only ANTHROPIC_ORGANIZATION_ID is provided", () => {
process.env.ANTHROPIC_ORGANIZATION_ID =
"00000000-0000-0000-0000-000000000000";
expect(() => validateEnvironmentVariables()).toThrow(
"Workload identity federation requires both ANTHROPIC_FEDERATION_RULE_ID and ANTHROPIC_ORGANIZATION_ID to be set.",
);
});
});
describe("AWS Bedrock", () => {
test("should pass when all required Bedrock variables are provided", () => {
process.env.CLAUDE_CODE_USE_BEDROCK = "1";
process.env.AWS_REGION = "us-east-1";
process.env.AWS_ACCESS_KEY_ID = "test-access-key";
process.env.AWS_SECRET_ACCESS_KEY = "test-secret-key";
expect(() => validateEnvironmentVariables()).not.toThrow();
});
test("should pass with optional Bedrock variables", () => {
process.env.CLAUDE_CODE_USE_BEDROCK = "1";
process.env.AWS_REGION = "us-east-1";
process.env.AWS_ACCESS_KEY_ID = "test-access-key";
process.env.AWS_SECRET_ACCESS_KEY = "test-secret-key";
process.env.AWS_SESSION_TOKEN = "test-session-token";
process.env.ANTHROPIC_BEDROCK_BASE_URL = "https://test.url";
expect(() => validateEnvironmentVariables()).not.toThrow();
});
test("should construct Bedrock base URL from AWS_REGION when ANTHROPIC_BEDROCK_BASE_URL is not provided", () => {
// This test verifies our action.yml change, which constructs:
// ANTHROPIC_BEDROCK_BASE_URL: ${{ env.ANTHROPIC_BEDROCK_BASE_URL || (env.AWS_REGION && format('https://bedrock-runtime.{0}.amazonaws.com', env.AWS_REGION)) }}
process.env.CLAUDE_CODE_USE_BEDROCK = "1";
process.env.AWS_REGION = "us-west-2";
process.env.AWS_ACCESS_KEY_ID = "test-access-key";
process.env.AWS_SECRET_ACCESS_KEY = "test-secret-key";
// ANTHROPIC_BEDROCK_BASE_URL is intentionally not set
// The actual URL construction happens in the composite action in action.yml
// This test is a placeholder to document the behavior
expect(() => validateEnvironmentVariables()).not.toThrow();
// In the actual action, ANTHROPIC_BEDROCK_BASE_URL would be:
// https://bedrock-runtime.us-west-2.amazonaws.com
});
test("should fail when AWS_REGION is missing", () => {
process.env.CLAUDE_CODE_USE_BEDROCK = "1";
process.env.AWS_ACCESS_KEY_ID = "test-access-key";
process.env.AWS_SECRET_ACCESS_KEY = "test-secret-key";
expect(() => validateEnvironmentVariables()).toThrow(
"AWS_REGION is required when using AWS Bedrock.",
);
});
test("should fail when only AWS_SECRET_ACCESS_KEY is provided without bearer token", () => {
process.env.CLAUDE_CODE_USE_BEDROCK = "1";
process.env.AWS_REGION = "us-east-1";
process.env.AWS_SECRET_ACCESS_KEY = "test-secret-key";
expect(() => validateEnvironmentVariables()).toThrow(
"Either AWS_BEARER_TOKEN_BEDROCK or both AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are required when using AWS Bedrock.",
);
});
test("should fail when only AWS_ACCESS_KEY_ID is provided without bearer token", () => {
process.env.CLAUDE_CODE_USE_BEDROCK = "1";
process.env.AWS_REGION = "us-east-1";
process.env.AWS_ACCESS_KEY_ID = "test-access-key";
expect(() => validateEnvironmentVariables()).toThrow(
"Either AWS_BEARER_TOKEN_BEDROCK or both AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are required when using AWS Bedrock.",
);
});
test("should pass when AWS_BEARER_TOKEN_BEDROCK is provided instead of access keys", () => {
process.env.CLAUDE_CODE_USE_BEDROCK = "1";
process.env.AWS_REGION = "us-east-1";
process.env.AWS_BEARER_TOKEN_BEDROCK = "test-bearer-token";
expect(() => validateEnvironmentVariables()).not.toThrow();
});
test("should pass when both bearer token and access keys are provided", () => {
process.env.CLAUDE_CODE_USE_BEDROCK = "1";
process.env.AWS_REGION = "us-east-1";
process.env.AWS_BEARER_TOKEN_BEDROCK = "test-bearer-token";
process.env.AWS_ACCESS_KEY_ID = "test-access-key";
process.env.AWS_SECRET_ACCESS_KEY = "test-secret-key";
expect(() => validateEnvironmentVariables()).not.toThrow();
});
test("should fail when no authentication method is provided", () => {
process.env.CLAUDE_CODE_USE_BEDROCK = "1";
process.env.AWS_REGION = "us-east-1";
expect(() => validateEnvironmentVariables()).toThrow(
"Either AWS_BEARER_TOKEN_BEDROCK or both AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are required when using AWS Bedrock.",
);
});
test("should report missing region and authentication", () => {
process.env.CLAUDE_CODE_USE_BEDROCK = "1";
expect(() => validateEnvironmentVariables()).toThrow(
/AWS_REGION is required when using AWS Bedrock.*Either AWS_BEARER_TOKEN_BEDROCK or both AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are required when using AWS Bedrock/s,
);
});
});
describe("Google Vertex AI", () => {
test("should pass when all required Vertex variables are provided", () => {
process.env.CLAUDE_CODE_USE_VERTEX = "1";
process.env.ANTHROPIC_VERTEX_PROJECT_ID = "test-project";
process.env.CLOUD_ML_REGION = "us-central1";
expect(() => validateEnvironmentVariables()).not.toThrow();
});
test("should pass with optional Vertex variables", () => {
process.env.CLAUDE_CODE_USE_VERTEX = "1";
process.env.ANTHROPIC_VERTEX_PROJECT_ID = "test-project";
process.env.CLOUD_ML_REGION = "us-central1";
process.env.GOOGLE_APPLICATION_CREDENTIALS = "/path/to/creds.json";
process.env.ANTHROPIC_VERTEX_BASE_URL = "https://test.url";
expect(() => validateEnvironmentVariables()).not.toThrow();
});
test("should fail when ANTHROPIC_VERTEX_PROJECT_ID is missing", () => {
process.env.CLAUDE_CODE_USE_VERTEX = "1";
process.env.CLOUD_ML_REGION = "us-central1";
expect(() => validateEnvironmentVariables()).toThrow(
"ANTHROPIC_VERTEX_PROJECT_ID is required when using Google Vertex AI.",
);
});
test("should fail when CLOUD_ML_REGION is missing", () => {
process.env.CLAUDE_CODE_USE_VERTEX = "1";
process.env.ANTHROPIC_VERTEX_PROJECT_ID = "test-project";
expect(() => validateEnvironmentVariables()).toThrow(
"CLOUD_ML_REGION is required when using Google Vertex AI.",
);
});
test("should report all missing Vertex variables", () => {
process.env.CLAUDE_CODE_USE_VERTEX = "1";
expect(() => validateEnvironmentVariables()).toThrow(
/ANTHROPIC_VERTEX_PROJECT_ID is required when using Google Vertex AI.*CLOUD_ML_REGION is required when using Google Vertex AI/s,
);
});
});
describe("Microsoft Foundry", () => {
test("should pass when ANTHROPIC_FOUNDRY_RESOURCE is provided", () => {
process.env.CLAUDE_CODE_USE_FOUNDRY = "1";
process.env.ANTHROPIC_FOUNDRY_RESOURCE = "test-resource";
expect(() => validateEnvironmentVariables()).not.toThrow();
});
test("should pass when ANTHROPIC_FOUNDRY_BASE_URL is provided", () => {
process.env.CLAUDE_CODE_USE_FOUNDRY = "1";
process.env.ANTHROPIC_FOUNDRY_BASE_URL =
"https://test-resource.services.ai.azure.com";
expect(() => validateEnvironmentVariables()).not.toThrow();
});
test("should pass when both resource and base URL are provided", () => {
process.env.CLAUDE_CODE_USE_FOUNDRY = "1";
process.env.ANTHROPIC_FOUNDRY_RESOURCE = "test-resource";
process.env.ANTHROPIC_FOUNDRY_BASE_URL =
"https://custom.services.ai.azure.com";
expect(() => validateEnvironmentVariables()).not.toThrow();
});
test("should construct Foundry base URL from resource name when ANTHROPIC_FOUNDRY_BASE_URL is not provided", () => {
// This test verifies our action.yml change, which constructs:
// ANTHROPIC_FOUNDRY_BASE_URL: ${{ env.ANTHROPIC_FOUNDRY_BASE_URL || (env.ANTHROPIC_FOUNDRY_RESOURCE && format('https://{0}.services.ai.azure.com', env.ANTHROPIC_FOUNDRY_RESOURCE)) }}
process.env.CLAUDE_CODE_USE_FOUNDRY = "1";
process.env.ANTHROPIC_FOUNDRY_RESOURCE = "my-foundry-resource";
// ANTHROPIC_FOUNDRY_BASE_URL is intentionally not set
// The actual URL construction happens in the composite action in action.yml
// This test is a placeholder to document the behavior
expect(() => validateEnvironmentVariables()).not.toThrow();
// In the actual action, ANTHROPIC_FOUNDRY_BASE_URL would be:
// https://my-foundry-resource.services.ai.azure.com
});
test("should fail when neither ANTHROPIC_FOUNDRY_RESOURCE nor ANTHROPIC_FOUNDRY_BASE_URL is provided", () => {
process.env.CLAUDE_CODE_USE_FOUNDRY = "1";
expect(() => validateEnvironmentVariables()).toThrow(
"Either ANTHROPIC_FOUNDRY_RESOURCE or ANTHROPIC_FOUNDRY_BASE_URL is required when using Microsoft Foundry.",
);
});
});
describe("Multiple providers", () => {
test("should fail when both Bedrock and Vertex are enabled", () => {
process.env.CLAUDE_CODE_USE_BEDROCK = "1";
process.env.CLAUDE_CODE_USE_VERTEX = "1";
// Provide all required vars to isolate the mutual exclusion error
process.env.AWS_REGION = "us-east-1";
process.env.AWS_ACCESS_KEY_ID = "test-access-key";
process.env.AWS_SECRET_ACCESS_KEY = "test-secret-key";
process.env.ANTHROPIC_VERTEX_PROJECT_ID = "test-project";
process.env.CLOUD_ML_REGION = "us-central1";
expect(() => validateEnvironmentVariables()).toThrow(
"Cannot use multiple providers simultaneously. Please set only one of: CLAUDE_CODE_USE_BEDROCK, CLAUDE_CODE_USE_VERTEX, or CLAUDE_CODE_USE_FOUNDRY.",
);
});
test("should fail when both Bedrock and Foundry are enabled", () => {
process.env.CLAUDE_CODE_USE_BEDROCK = "1";
process.env.CLAUDE_CODE_USE_FOUNDRY = "1";
// Provide all required vars to isolate the mutual exclusion error
process.env.AWS_REGION = "us-east-1";
process.env.AWS_ACCESS_KEY_ID = "test-access-key";
process.env.AWS_SECRET_ACCESS_KEY = "test-secret-key";
process.env.ANTHROPIC_FOUNDRY_RESOURCE = "test-resource";
expect(() => validateEnvironmentVariables()).toThrow(
"Cannot use multiple providers simultaneously. Please set only one of: CLAUDE_CODE_USE_BEDROCK, CLAUDE_CODE_USE_VERTEX, or CLAUDE_CODE_USE_FOUNDRY.",
);
});
test("should fail when both Vertex and Foundry are enabled", () => {
process.env.CLAUDE_CODE_USE_VERTEX = "1";
process.env.CLAUDE_CODE_USE_FOUNDRY = "1";
// Provide all required vars to isolate the mutual exclusion error
process.env.ANTHROPIC_VERTEX_PROJECT_ID = "test-project";
process.env.CLOUD_ML_REGION = "us-central1";
process.env.ANTHROPIC_FOUNDRY_RESOURCE = "test-resource";
expect(() => validateEnvironmentVariables()).toThrow(
"Cannot use multiple providers simultaneously. Please set only one of: CLAUDE_CODE_USE_BEDROCK, CLAUDE_CODE_USE_VERTEX, or CLAUDE_CODE_USE_FOUNDRY.",
);
});
test("should fail when all three providers are enabled", () => {
process.env.CLAUDE_CODE_USE_BEDROCK = "1";
process.env.CLAUDE_CODE_USE_VERTEX = "1";
process.env.CLAUDE_CODE_USE_FOUNDRY = "1";
// Provide all required vars to isolate the mutual exclusion error
process.env.AWS_REGION = "us-east-1";
process.env.AWS_ACCESS_KEY_ID = "test-access-key";
process.env.AWS_SECRET_ACCESS_KEY = "test-secret-key";
process.env.ANTHROPIC_VERTEX_PROJECT_ID = "test-project";
process.env.CLOUD_ML_REGION = "us-central1";
process.env.ANTHROPIC_FOUNDRY_RESOURCE = "test-resource";
expect(() => validateEnvironmentVariables()).toThrow(
"Cannot use multiple providers simultaneously. Please set only one of: CLAUDE_CODE_USE_BEDROCK, CLAUDE_CODE_USE_VERTEX, or CLAUDE_CODE_USE_FOUNDRY.",
);
});
});
describe("Error message formatting", () => {
test("should format error message properly with multiple errors", () => {
process.env.CLAUDE_CODE_USE_BEDROCK = "1";
// Missing all required Bedrock vars
let error: Error | undefined;
try {
validateEnvironmentVariables();
} catch (e) {
error = e as Error;
}
expect(error).toBeDefined();
expect(error!.message).toMatch(
/^Environment variable validation failed:/,
);
expect(error!.message).toContain(
" - AWS_REGION is required when using AWS Bedrock.",
);
expect(error!.message).toContain(
" - Either AWS_BEARER_TOKEN_BEDROCK or both AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are required when using AWS Bedrock.",
);
});
});
});
+258
View File
@@ -0,0 +1,258 @@
#!/usr/bin/env bun
import { describe, test, expect, beforeEach, afterEach, spyOn } from "bun:test";
import * as core from "@actions/core";
import {
existsSync,
mkdtempSync,
readdirSync,
readFileSync,
rmSync,
statSync,
} from "fs";
import { tmpdir } from "os";
import { join } from "path";
import {
isWorkloadIdentityConfigured,
setupWorkloadIdentity,
} from "../src/workload-identity";
describe("workload identity federation", () => {
let originalEnv: NodeJS.ProcessEnv;
let tempDir: string;
let getIDTokenSpy: ReturnType<typeof spyOn>;
let warningSpy: ReturnType<typeof spyOn>;
let setSecretSpy: ReturnType<typeof spyOn>;
beforeEach(() => {
originalEnv = { ...process.env };
tempDir = mkdtempSync(join(tmpdir(), "wif-test-"));
process.env.RUNNER_TEMP = tempDir;
delete process.env.ANTHROPIC_API_KEY;
delete process.env.CLAUDE_CODE_OAUTH_TOKEN;
delete process.env.ANTHROPIC_FEDERATION_RULE_ID;
delete process.env.ANTHROPIC_ORGANIZATION_ID;
delete process.env.ANTHROPIC_OIDC_AUDIENCE;
delete process.env.ANTHROPIC_IDENTITY_TOKEN_FILE;
delete process.env.ANTHROPIC_SERVICE_ACCOUNT_ID;
delete process.env.ANTHROPIC_WORKSPACE_ID;
delete process.env.ANTHROPIC_BASE_URL;
delete process.env.ANTHROPIC_SCOPE;
delete process.env.ANTHROPIC_CONFIG_DIR;
delete process.env.ANTHROPIC_PROFILE;
getIDTokenSpy = spyOn(core, "getIDToken").mockResolvedValue(
"test-identity-token",
);
warningSpy = spyOn(core, "warning").mockImplementation(() => {});
setSecretSpy = spyOn(core, "setSecret").mockImplementation(() => {});
});
afterEach(() => {
process.env = originalEnv;
getIDTokenSpy.mockRestore();
warningSpy.mockRestore();
setSecretSpy.mockRestore();
rmSync(tempDir, { recursive: true, force: true });
});
describe("isWorkloadIdentityConfigured", () => {
test("returns false when no federation variables are set", () => {
expect(isWorkloadIdentityConfigured()).toBe(false);
});
test("returns false when only one federation variable is set", () => {
process.env.ANTHROPIC_FEDERATION_RULE_ID = "fdrl_test";
expect(isWorkloadIdentityConfigured()).toBe(false);
});
test("returns true when rule ID and organization ID are set", () => {
process.env.ANTHROPIC_FEDERATION_RULE_ID = "fdrl_test";
process.env.ANTHROPIC_ORGANIZATION_ID =
"00000000-0000-0000-0000-000000000000";
expect(isWorkloadIdentityConfigured()).toBe(true);
});
});
describe("setupWorkloadIdentity", () => {
test("returns undefined when federation is not configured", async () => {
const handle = await setupWorkloadIdentity();
expect(handle).toBeUndefined();
expect(getIDTokenSpy).not.toHaveBeenCalled();
});
test("returns undefined and warns when an API key is also set", async () => {
process.env.ANTHROPIC_FEDERATION_RULE_ID = "fdrl_test";
process.env.ANTHROPIC_ORGANIZATION_ID =
"00000000-0000-0000-0000-000000000000";
process.env.ANTHROPIC_API_KEY = "sk-ant-test";
const handle = await setupWorkloadIdentity();
expect(handle).toBeUndefined();
expect(warningSpy).toHaveBeenCalled();
expect(getIDTokenSpy).not.toHaveBeenCalled();
expect(process.env.ANTHROPIC_IDENTITY_TOKEN_FILE).toBeUndefined();
});
test("writes the identity token file and exports its path", async () => {
process.env.ANTHROPIC_FEDERATION_RULE_ID = "fdrl_test";
process.env.ANTHROPIC_ORGANIZATION_ID =
"00000000-0000-0000-0000-000000000000";
const handle = await setupWorkloadIdentity();
try {
expect(handle).toBeDefined();
expect(handle!.tokenFile).toBe(
join(tempDir, "claude-workload-identity", "identity-token"),
);
expect(process.env.ANTHROPIC_IDENTITY_TOKEN_FILE).toBe(
handle!.tokenFile,
);
expect(existsSync(handle!.tokenFile)).toBe(true);
expect(readFileSync(handle!.tokenFile, "utf-8")).toBe(
"test-identity-token",
);
expect(statSync(handle!.tokenFile).mode & 0o777).toBe(0o600);
expect(setSecretSpy).toHaveBeenCalledWith("test-identity-token");
// Default audience scopes the JWT to the Claude API token exchange
expect(getIDTokenSpy).toHaveBeenCalledWith("https://api.anthropic.com");
} finally {
handle?.stop();
}
});
test("requests the configured audience", async () => {
process.env.ANTHROPIC_FEDERATION_RULE_ID = "fdrl_test";
process.env.ANTHROPIC_ORGANIZATION_ID =
"00000000-0000-0000-0000-000000000000";
process.env.ANTHROPIC_OIDC_AUDIENCE = "https://example.com/custom";
const handle = await setupWorkloadIdentity();
try {
expect(getIDTokenSpy).toHaveBeenCalledWith(
"https://example.com/custom",
);
} finally {
handle?.stop();
}
});
test("writes a minimal federation profile and selects it", async () => {
process.env.ANTHROPIC_FEDERATION_RULE_ID = "fdrl_test";
process.env.ANTHROPIC_ORGANIZATION_ID =
"00000000-0000-0000-0000-000000000000";
process.env.ANTHROPIC_SERVICE_ACCOUNT_ID = "svac_test";
process.env.ANTHROPIC_WORKSPACE_ID = "wrkspc_test";
const handle = await setupWorkloadIdentity();
try {
const configDir = process.env.ANTHROPIC_CONFIG_DIR;
expect(configDir).toBeDefined();
expect(
configDir!.startsWith(
join(tempDir, "claude-workload-identity", "config-"),
),
).toBe(true);
expect(process.env.ANTHROPIC_PROFILE).toBe("default");
const profilePath = join(configDir!, "configs", "default.json");
expect(statSync(profilePath).mode & 0o777).toBe(0o600);
// Minimal on purpose: the SDK gap-fills the federation fields from
// the ANTHROPIC_* env vars the action exports.
expect(JSON.parse(readFileSync(profilePath, "utf-8"))).toEqual({
version: "1.0",
authentication: { type: "oidc_federation" },
});
} finally {
handle?.stop();
}
});
test("derives the config dir from the federation inputs", async () => {
process.env.ANTHROPIC_FEDERATION_RULE_ID = "fdrl_test";
process.env.ANTHROPIC_ORGANIZATION_ID =
"00000000-0000-0000-0000-000000000000";
process.env.ANTHROPIC_WORKSPACE_ID = "wrkspc_a";
(await setupWorkloadIdentity())?.stop();
const firstConfigDir = process.env.ANTHROPIC_CONFIG_DIR;
expect(firstConfigDir).toBeDefined();
// A later step in the same job with a different workspace must not
// share the first step's credentials cache.
delete process.env.ANTHROPIC_CONFIG_DIR;
delete process.env.ANTHROPIC_PROFILE;
process.env.ANTHROPIC_WORKSPACE_ID = "wrkspc_b";
(await setupWorkloadIdentity())?.stop();
const secondConfigDir = process.env.ANTHROPIC_CONFIG_DIR;
expect(secondConfigDir).toBeDefined();
expect(secondConfigDir).not.toBe(firstConfigDir);
// Same inputs land in the same dir, so an unchanged config can still
// reuse a cached token.
delete process.env.ANTHROPIC_CONFIG_DIR;
delete process.env.ANTHROPIC_PROFILE;
(await setupWorkloadIdentity())?.stop();
expect(process.env.ANTHROPIC_CONFIG_DIR).toBe(secondConfigDir!);
});
test("does not overwrite an operator-set ANTHROPIC_PROFILE", async () => {
process.env.ANTHROPIC_FEDERATION_RULE_ID = "fdrl_test";
process.env.ANTHROPIC_ORGANIZATION_ID =
"00000000-0000-0000-0000-000000000000";
process.env.ANTHROPIC_PROFILE = "operator";
const handle = await setupWorkloadIdentity();
try {
expect(process.env.ANTHROPIC_PROFILE).toBe("operator");
expect(process.env.ANTHROPIC_CONFIG_DIR).toBeUndefined();
expect(warningSpy).toHaveBeenCalled();
const entries = readdirSync(join(tempDir, "claude-workload-identity"));
expect(entries.filter((e) => e.startsWith("config-"))).toEqual([]);
// The identity token file is still provisioned for the operator's
// profile (or the env-var fallback) to consume.
expect(process.env.ANTHROPIC_IDENTITY_TOKEN_FILE).toBe(
handle!.tokenFile,
);
} finally {
handle?.stop();
}
});
test("does not overwrite an operator-set ANTHROPIC_CONFIG_DIR", async () => {
process.env.ANTHROPIC_FEDERATION_RULE_ID = "fdrl_test";
process.env.ANTHROPIC_ORGANIZATION_ID =
"00000000-0000-0000-0000-000000000000";
const operatorConfigDir = join(tempDir, "operator-config");
process.env.ANTHROPIC_CONFIG_DIR = operatorConfigDir;
const handle = await setupWorkloadIdentity();
try {
expect(process.env.ANTHROPIC_CONFIG_DIR).toBe(operatorConfigDir);
expect(process.env.ANTHROPIC_PROFILE).toBeUndefined();
expect(warningSpy).toHaveBeenCalled();
} finally {
handle?.stop();
}
});
test("stop removes the identity token and credential cache", async () => {
process.env.ANTHROPIC_FEDERATION_RULE_ID = "fdrl_test";
process.env.ANTHROPIC_ORGANIZATION_ID =
"00000000-0000-0000-0000-000000000000";
const handle = await setupWorkloadIdentity();
const tokenDir = join(tempDir, "claude-workload-identity");
expect(existsSync(handle!.tokenFile)).toBe(true);
expect(existsSync(process.env.ANTHROPIC_CONFIG_DIR!)).toBe(true);
handle!.stop();
expect(existsSync(tokenDir)).toBe(false);
});
});
});
+30
View File
@@ -0,0 +1,30 @@
{
"compilerOptions": {
// Environment setup & latest features
"lib": ["ESNext"],
"target": "ESNext",
"module": "ESNext",
"moduleDetection": "force",
"jsx": "react-jsx",
"allowJs": true,
// Bundler mode (Bun-specific)
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,
// Best practices
"strict": true,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
// Some stricter flags
"noUnusedLocals": true,
"noUnusedParameters": true,
"noPropertyAccessFromIndexSignature": false
},
"include": ["src/**/*", "test/**/*"],
"exclude": ["node_modules", "test/mcp-test"]
}
+66 -20
View File
@@ -1,22 +1,26 @@
{
"lockfileVersion": 1,
"configVersion": 0,
"workspaces": {
"": {
"name": "claude-pr-action",
"name": "@anthropic-ai/claude-code-action",
"dependencies": {
"@actions/core": "^1.10.1",
"@actions/github": "^6.0.1",
"@anthropic-ai/claude-agent-sdk": "^0.3.274",
"@modelcontextprotocol/sdk": "^1.11.0",
"@octokit/graphql": "^8.2.2",
"@octokit/rest": "^21.1.1",
"@octokit/webhooks-types": "^7.6.1",
"node-fetch": "^3.3.2",
"shell-quote": "^1.8.4",
"zod": "^3.24.4",
},
"devDependencies": {
"@types/bun": "1.2.11",
"@types/node": "^20.0.0",
"@types/node-fetch": "^2.6.12",
"@types/shell-quote": "^1.7.5",
"prettier": "3.5.3",
"typescript": "^5.8.3",
},
@@ -33,19 +37,41 @@
"@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.3.274", "", { "optionalDependencies": { "@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.274", "@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.274", "@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.274", "@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.274", "@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.274", "@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.274", "@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.274", "@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.274" }, "peerDependencies": { "@anthropic-ai/sdk": ">=0.93.0", "@modelcontextprotocol/sdk": "^1.29.0", "zod": "^4.0.0" } }, "sha512-kFmWMsh/BEd4jKkOxUeihr0xMkaMdOxONhNNkZ2pGmP0ElHkArvFvuZxqJTPvREnhCsPZByv5f0EzyAb/hkRZw=="],
"@anthropic-ai/claude-agent-sdk-darwin-arm64": ["@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.274", "", { "os": "darwin", "cpu": "arm64" }, "sha512-B0uAdUIbUhWuybOT5FkhbdsALGNduyaHYo+bEggKBSQS3RFRjgNbX/ah1+IwkmTn4UKDN7AX86DkTEjpj6TSWQ=="],
"@anthropic-ai/claude-agent-sdk-darwin-x64": ["@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.274", "", { "os": "darwin", "cpu": "x64" }, "sha512-l5pp3Z+z2mti0H3y1O0MRbTg/r+MDm0hly4WpoKmZyIZSOrDQR7jhCqS6Ihj3Vvd7CMP9NZ8t6F0oXVBDCM6YQ=="],
"@anthropic-ai/claude-agent-sdk-linux-arm64": ["@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.274", "", { "os": "linux", "cpu": "arm64" }, "sha512-kYsmSN6zieDwVS3QZBBay9fxNp+h4t4OXleuk6xWhrUDEQRfRQEFlD7JVWWWjJIiHILnniycOxarwo4VpqTf1Q=="],
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl": ["@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.274", "", { "os": "linux", "cpu": "arm64" }, "sha512-pNjZQRF3f1O5JwnoifuAD7tSRfk9LSwwBzEPiUHB4sJgatF6hzqyDgTN05hcDx28xg44j0jpKpyVwsjveI/s4g=="],
"@anthropic-ai/claude-agent-sdk-linux-x64": ["@anthropic-ai/claude-agent-sdk-linux-x64@0.3.274", "", { "os": "linux", "cpu": "x64" }, "sha512-QLeK9LzTtTdCjCDRYUVRHj1I3U6AfMvpXYI4Ur1s+vrUcad/C20kShS5/kgug+rERU5CNxNA9v0A052rt47ZXQ=="],
"@anthropic-ai/claude-agent-sdk-linux-x64-musl": ["@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.274", "", { "os": "linux", "cpu": "x64" }, "sha512-a9eIkmgJ5sSywHwWJcComogjIMsfHsurckQ85ljPNTUSFguzl/ZvbTXGiWNRK5rHsoNd3amwQf2fVC/vFyv3eg=="],
"@anthropic-ai/claude-agent-sdk-win32-arm64": ["@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.274", "", { "os": "win32", "cpu": "arm64" }, "sha512-A5wvZAXL3nhuabIXUcRuoYhuPPdEIt3LoA3jUkVLT3c0H+7SH0rv5bylpM9n+apf7lwLCm2TdMZ6x1Ze1Vzekg=="],
"@anthropic-ai/claude-agent-sdk-win32-x64": ["@anthropic-ai/claude-agent-sdk-win32-x64@0.3.274", "", { "os": "win32", "cpu": "x64" }, "sha512-dGcaLfEpV4kwcSijPAIJru+iEiVhHvbBdkyixPyhhf8pniV9B62nUMFFsjNtYL37xIPHxixh2Gsx9/A+Yk4oww=="],
"@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.93.0", "", { "dependencies": { "json-schema-to-ts": "^3.1.1" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["zod"], "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-q9vaSZQVFx6B/gPxetGYfLXSJD5v0sOmh0OpZDq7yCrTSA+Rscvrtyol7JJTW40wEpQB4U1B4JXzxQitbQ3CAA=="],
"@babel/runtime": ["@babel/runtime@7.29.2", "", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="],
"@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="],
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.11.0", "", { "dependencies": { "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.3", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-k/1pb70eD638anoi0e8wUGAlbMJXyvdV4p62Ko+EZ7eBe1xMx8Uhak1R5DgfoofsK5IBBnRwsYGTaLZl+6/+RQ=="],
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.16.0", "", { "dependencies": { "ajv": "^6.12.6", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-8ofX7gkZcLj9H9rSd50mCgm3SSF8C7XoclxJuLoV0Cz3rEQ1tv9MZRYYvJtm9n1BiEQQMzSmE/w2AEkNacLYfg=="],
"@octokit/auth-token": ["@octokit/auth-token@4.0.0", "", {}, "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA=="],
"@octokit/core": ["@octokit/core@5.2.1", "", { "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", "@octokit/request": "^8.4.1", "@octokit/request-error": "^5.1.1", "@octokit/types": "^13.0.0", "before-after-hook": "^2.2.0", "universal-user-agent": "^6.0.0" } }, "sha512-dKYCMuPO1bmrpuogcjQ8z7ICCH3FP6WmxpwC03yjzGfZhj9fTJg6+bS1+UAplekbN2C+M61UNllGOOoAfGCrdQ=="],
"@octokit/core": ["@octokit/core@5.2.2", "", { "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", "@octokit/request": "^8.4.1", "@octokit/request-error": "^5.1.1", "@octokit/types": "^13.0.0", "before-after-hook": "^2.2.0", "universal-user-agent": "^6.0.0" } }, "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg=="],
"@octokit/endpoint": ["@octokit/endpoint@9.0.6", "", { "dependencies": { "@octokit/types": "^13.1.0", "universal-user-agent": "^6.0.0" } }, "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw=="],
"@octokit/graphql": ["@octokit/graphql@8.2.2", "", { "dependencies": { "@octokit/request": "^9.2.3", "@octokit/types": "^14.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-Yi8hcoqsrXGdt0yObxbebHXFOiUA+2v3n53epuOg1QUgOB6c4XzvisBNVXJSl8RYA5KrDuSL2yq9Qmqe5N0ryA=="],
"@octokit/openapi-types": ["@octokit/openapi-types@25.0.0", "", {}, "sha512-FZvktFu7HfOIJf2BScLKIEYjDsw6RKc7rBJCdvCTfKsVnx2GEB/Nbzjr29DUdb7vQhlzS/j8qDzdditP0OC6aw=="],
"@octokit/openapi-types": ["@octokit/openapi-types@25.1.0", "", {}, "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA=="],
"@octokit/plugin-paginate-rest": ["@octokit/plugin-paginate-rest@9.2.2", "", { "dependencies": { "@octokit/types": "^12.6.0" }, "peerDependencies": { "@octokit/core": "5" } }, "sha512-u3KYkGF7GcZnSD/3UP0S7K5XUFT2FkOQdcfXZGZQPGv3lm4F2Xbf71lvjldr8c1H3nNbF+33cLEkWYbokGWqiQ=="],
@@ -59,18 +85,22 @@
"@octokit/rest": ["@octokit/rest@21.1.1", "", { "dependencies": { "@octokit/core": "^6.1.4", "@octokit/plugin-paginate-rest": "^11.4.2", "@octokit/plugin-request-log": "^5.3.1", "@octokit/plugin-rest-endpoint-methods": "^13.3.0" } }, "sha512-sTQV7va0IUVZcntzy1q3QqPm/r8rWtDCqpRAmb8eXXnKkjoQEtFe3Nt5GTVsHft+R6jJoHeSiVLcgcvhtue/rg=="],
"@octokit/types": ["@octokit/types@14.0.0", "", { "dependencies": { "@octokit/openapi-types": "^25.0.0" } }, "sha512-VVmZP0lEhbo2O1pdq63gZFiGCKkm8PPp8AUOijlwPO6hojEVjspA0MWKP7E4hbvGxzFKNqKr6p0IYtOH/Wf/zA=="],
"@octokit/types": ["@octokit/types@14.1.0", "", { "dependencies": { "@octokit/openapi-types": "^25.1.0" } }, "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g=="],
"@octokit/webhooks-types": ["@octokit/webhooks-types@7.6.1", "", {}, "sha512-S8u2cJzklBC0FgTwWVLaM8tMrDuDMVE4xiTK4EYXM9GntyvrdbSoxqDQa+Fh57CCNApyIpyeqPhhFEmHPfrXgw=="],
"@types/bun": ["@types/bun@1.2.11", "", { "dependencies": { "bun-types": "1.2.11" } }, "sha512-ZLbbI91EmmGwlWTRWuV6J19IUiUC5YQ3TCEuSHI3usIP75kuoA8/0PVF+LTrbEnVc8JIhpElWOxv1ocI1fJBbw=="],
"@types/node": ["@types/node@20.17.44", "", { "dependencies": { "undici-types": "~6.19.2" } }, "sha512-50sE4Ibb4BgUMxHrcJQSAU0Fu7fLcTdwcXwRzEF7wnVMWvImFLg2Rxc7SW0vpvaJm4wvhoWEZaQiPpBpocZiUA=="],
"@types/node": ["@types/node@20.19.9", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-cuVNgarYWZqxRJDQHEB58GEONhOK79QVR/qYx4S7kcUObQvUwvFnYxJuuHUKm2aieN9X3yZB4LZsuYNU1Qphsw=="],
"@types/node-fetch": ["@types/node-fetch@2.6.12", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.0" } }, "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA=="],
"@types/shell-quote": ["@types/shell-quote@1.7.5", "", {}, "sha512-+UE8GAGRPbJVQDdxi16dgadcBfQ+KG2vgZhV1+3A1XmHbmwcdwhCUwIdy+d3pAGrbvgRoVSjeI9vOWyq376Yzw=="],
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
"ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
"before-after-hook": ["before-after-hook@2.2.3", "", {}, "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ=="],
@@ -101,7 +131,7 @@
"data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="],
"debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="],
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
"delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
@@ -127,21 +157,25 @@
"etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
"eventsource": ["eventsource@3.0.6", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-l19WpE2m9hSuyP06+FbuUUf1G+R0SFLrtQfbRb9PRr+oimOfxQhgGCbVaXg5IvZyyTThJsxh6L/srkMiCeBPDA=="],
"eventsource": ["eventsource@3.0.7", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="],
"eventsource-parser": ["eventsource-parser@3.0.1", "", {}, "sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA=="],
"eventsource-parser": ["eventsource-parser@3.0.3", "", {}, "sha512-nVpZkTMM9rF6AQ9gPJpFsNAMt48wIzB5TQgiTLdHiuO8XEDhUgZEhqKlZWXbIzo9VmJ/HvysHqEaVeD5v9TPvA=="],
"express": ["express@5.1.0", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA=="],
"express-rate-limit": ["express-rate-limit@7.5.0", "", { "peerDependencies": { "express": "^4.11 || 5 || ^5.0.0-beta.1" } }, "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg=="],
"express-rate-limit": ["express-rate-limit@7.5.1", "", { "peerDependencies": { "express": ">= 4.11" } }, "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw=="],
"fast-content-type-parse": ["fast-content-type-parse@2.0.1", "", {}, "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q=="],
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
"fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="],
"finalhandler": ["finalhandler@2.1.0", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q=="],
"form-data": ["form-data@4.0.2", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "mime-types": "^2.1.12" } }, "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w=="],
"form-data": ["form-data@4.0.4", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow=="],
"formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="],
@@ -175,6 +209,10 @@
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
"json-schema-to-ts": ["json-schema-to-ts@3.1.1", "", { "dependencies": { "@babel/runtime": "^7.18.3", "ts-algebra": "^2.0.0" } }, "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g=="],
"json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
"media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
@@ -213,6 +251,8 @@
"proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
"qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="],
"range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
@@ -235,6 +275,8 @@
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
"shell-quote": ["shell-quote@1.8.4", "", {}, "sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ=="],
"side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="],
"side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="],
@@ -247,6 +289,8 @@
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
"ts-algebra": ["ts-algebra@2.0.0", "", {}, "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw=="],
"tunnel": ["tunnel@0.0.6", "", {}, "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="],
"type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="],
@@ -255,12 +299,14 @@
"undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
"undici-types": ["undici-types@6.19.8", "", {}, "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="],
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
"universal-user-agent": ["universal-user-agent@7.0.2", "", {}, "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q=="],
"universal-user-agent": ["universal-user-agent@7.0.3", "", {}, "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A=="],
"unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
"web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="],
@@ -269,9 +315,9 @@
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
"zod": ["zod@3.24.4", "", {}, "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg=="],
"zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
"zod-to-json-schema": ["zod-to-json-schema@3.24.5", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g=="],
"zod-to-json-schema": ["zod-to-json-schema@3.24.6", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg=="],
"@octokit/core/@octokit/graphql": ["@octokit/graphql@7.1.1", "", { "dependencies": { "@octokit/request": "^8.4.1", "@octokit/types": "^13.0.0", "universal-user-agent": "^6.0.0" } }, "sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g=="],
@@ -283,11 +329,11 @@
"@octokit/endpoint/universal-user-agent": ["universal-user-agent@6.0.1", "", {}, "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ=="],
"@octokit/graphql/@octokit/request": ["@octokit/request@9.2.3", "", { "dependencies": { "@octokit/endpoint": "^10.1.4", "@octokit/request-error": "^6.1.8", "@octokit/types": "^14.0.0", "fast-content-type-parse": "^2.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-Ma+pZU8PXLOEYzsWf0cn/gY+ME57Wq8f49WTXA8FMHp2Ps9djKw//xYJ1je8Hm0pR2lU9FUGeJRWOtxq6olt4w=="],
"@octokit/graphql/@octokit/request": ["@octokit/request@9.2.4", "", { "dependencies": { "@octokit/endpoint": "^10.1.4", "@octokit/request-error": "^6.1.8", "@octokit/types": "^14.0.0", "fast-content-type-parse": "^2.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-q8ybdytBmxa6KogWlNa818r0k1wlqzNC+yNkcQDECHvQo8Vmstrg18JwqJHdJdUiHD2sjlwBgSm9kHkOKe2iyA=="],
"@octokit/plugin-paginate-rest/@octokit/types": ["@octokit/types@12.6.0", "", { "dependencies": { "@octokit/openapi-types": "^20.0.0" } }, "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw=="],
"@octokit/plugin-request-log/@octokit/core": ["@octokit/core@6.1.5", "", { "dependencies": { "@octokit/auth-token": "^5.0.0", "@octokit/graphql": "^8.2.2", "@octokit/request": "^9.2.3", "@octokit/request-error": "^6.1.8", "@octokit/types": "^14.0.0", "before-after-hook": "^3.0.2", "universal-user-agent": "^7.0.0" } }, "sha512-vvmsN0r7rguA+FySiCsbaTTobSftpIDIpPW81trAmsv9TGxg3YCujAxRYp/Uy8xmDgYCzzgulG62H7KYUFmeIg=="],
"@octokit/plugin-request-log/@octokit/core": ["@octokit/core@6.1.6", "", { "dependencies": { "@octokit/auth-token": "^5.0.0", "@octokit/graphql": "^8.2.2", "@octokit/request": "^9.2.3", "@octokit/request-error": "^6.1.8", "@octokit/types": "^14.0.0", "before-after-hook": "^3.0.2", "universal-user-agent": "^7.0.0" } }, "sha512-kIU8SLQkYWGp3pVKiYzA5OSaNF5EE03P/R8zEmmrG6XwOg5oBjXyQVVIauQ0dgau4zYhpZEhJrvIYt6oM+zZZA=="],
"@octokit/plugin-rest-endpoint-methods/@octokit/types": ["@octokit/types@12.6.0", "", { "dependencies": { "@octokit/openapi-types": "^20.0.0" } }, "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw=="],
@@ -297,7 +343,7 @@
"@octokit/request-error/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="],
"@octokit/rest/@octokit/core": ["@octokit/core@6.1.5", "", { "dependencies": { "@octokit/auth-token": "^5.0.0", "@octokit/graphql": "^8.2.2", "@octokit/request": "^9.2.3", "@octokit/request-error": "^6.1.8", "@octokit/types": "^14.0.0", "before-after-hook": "^3.0.2", "universal-user-agent": "^7.0.0" } }, "sha512-vvmsN0r7rguA+FySiCsbaTTobSftpIDIpPW81trAmsv9TGxg3YCujAxRYp/Uy8xmDgYCzzgulG62H7KYUFmeIg=="],
"@octokit/rest/@octokit/core": ["@octokit/core@6.1.6", "", { "dependencies": { "@octokit/auth-token": "^5.0.0", "@octokit/graphql": "^8.2.2", "@octokit/request": "^9.2.3", "@octokit/request-error": "^6.1.8", "@octokit/types": "^14.0.0", "before-after-hook": "^3.0.2", "universal-user-agent": "^7.0.0" } }, "sha512-kIU8SLQkYWGp3pVKiYzA5OSaNF5EE03P/R8zEmmrG6XwOg5oBjXyQVVIauQ0dgau4zYhpZEhJrvIYt6oM+zZZA=="],
"@octokit/rest/@octokit/plugin-paginate-rest": ["@octokit/plugin-paginate-rest@11.6.0", "", { "dependencies": { "@octokit/types": "^13.10.0" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-n5KPteiF7pWKgBIBJSk8qzoZWcUkza2O6A0za97pMGVrGfPdltxrfmfF5GucHYvHGZD8BdaZmmHGz5cX/3gdpw=="],
@@ -323,7 +369,7 @@
"@octokit/plugin-request-log/@octokit/core/@octokit/auth-token": ["@octokit/auth-token@5.1.2", "", {}, "sha512-JcQDsBdg49Yky2w2ld20IHAlwr8d/d8N6NiOXbtuoPCqzbsiJgF633mVUw3x4mo0H5ypataQIX7SFu3yy44Mpw=="],
"@octokit/plugin-request-log/@octokit/core/@octokit/request": ["@octokit/request@9.2.3", "", { "dependencies": { "@octokit/endpoint": "^10.1.4", "@octokit/request-error": "^6.1.8", "@octokit/types": "^14.0.0", "fast-content-type-parse": "^2.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-Ma+pZU8PXLOEYzsWf0cn/gY+ME57Wq8f49WTXA8FMHp2Ps9djKw//xYJ1je8Hm0pR2lU9FUGeJRWOtxq6olt4w=="],
"@octokit/plugin-request-log/@octokit/core/@octokit/request": ["@octokit/request@9.2.4", "", { "dependencies": { "@octokit/endpoint": "^10.1.4", "@octokit/request-error": "^6.1.8", "@octokit/types": "^14.0.0", "fast-content-type-parse": "^2.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-q8ybdytBmxa6KogWlNa818r0k1wlqzNC+yNkcQDECHvQo8Vmstrg18JwqJHdJdUiHD2sjlwBgSm9kHkOKe2iyA=="],
"@octokit/plugin-request-log/@octokit/core/@octokit/request-error": ["@octokit/request-error@6.1.8", "", { "dependencies": { "@octokit/types": "^14.0.0" } }, "sha512-WEi/R0Jmq+IJKydWlKDmryPcmdYSVjL3ekaiEL1L9eo1sUnqMJ+grqmC9cjk7CA7+b2/T397tO5d8YLOH3qYpQ=="],
@@ -337,7 +383,7 @@
"@octokit/rest/@octokit/core/@octokit/auth-token": ["@octokit/auth-token@5.1.2", "", {}, "sha512-JcQDsBdg49Yky2w2ld20IHAlwr8d/d8N6NiOXbtuoPCqzbsiJgF633mVUw3x4mo0H5ypataQIX7SFu3yy44Mpw=="],
"@octokit/rest/@octokit/core/@octokit/request": ["@octokit/request@9.2.3", "", { "dependencies": { "@octokit/endpoint": "^10.1.4", "@octokit/request-error": "^6.1.8", "@octokit/types": "^14.0.0", "fast-content-type-parse": "^2.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-Ma+pZU8PXLOEYzsWf0cn/gY+ME57Wq8f49WTXA8FMHp2Ps9djKw//xYJ1je8Hm0pR2lU9FUGeJRWOtxq6olt4w=="],
"@octokit/rest/@octokit/core/@octokit/request": ["@octokit/request@9.2.4", "", { "dependencies": { "@octokit/endpoint": "^10.1.4", "@octokit/request-error": "^6.1.8", "@octokit/types": "^14.0.0", "fast-content-type-parse": "^2.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-q8ybdytBmxa6KogWlNa818r0k1wlqzNC+yNkcQDECHvQo8Vmstrg18JwqJHdJdUiHD2sjlwBgSm9kHkOKe2iyA=="],
"@octokit/rest/@octokit/core/@octokit/request-error": ["@octokit/request-error@6.1.8", "", { "dependencies": { "@octokit/types": "^14.0.0" } }, "sha512-WEi/R0Jmq+IJKydWlKDmryPcmdYSVjL3ekaiEL1L9eo1sUnqMJ+grqmC9cjk7CA7+b2/T397tO5d8YLOH3qYpQ=="],
+2
View File
@@ -0,0 +1,2 @@
# Intentionally minimal. action.yml pins --config to this file so bun resolves
# its runtime config from the action directory rather than the workspace.
+33
View File
@@ -0,0 +1,33 @@
# Capabilities and Limitations
## What Claude Can Do
- **Respond in a Single Comment**: Claude operates by updating a single initial comment with progress and results
- **Answer Questions**: Analyze code and provide explanations
- **Implement Code Changes**: Make simple to moderate code changes based on requests
- **Prepare Pull Requests**: Creates commits on a branch and links back to a prefilled PR creation page
- **Perform Code Reviews**: Analyze PR changes and provide detailed feedback
- **Smart Branch Handling**:
- When triggered on an **issue**: Always creates a new branch for the work
- When triggered on an **open PR**: Always pushes directly to the existing PR branch
- When triggered on a **closed PR**: Creates a new branch since the original is no longer active
- **View GitHub Actions Results**: Can access workflow runs, job logs, and test results on the PR where it's tagged when `actions: read` permission is configured (see [Additional Permissions for CI/CD Integration](./configuration.md#additional-permissions-for-cicd-integration))
## What Claude Cannot Do
- **Submit PR Reviews**: Claude cannot submit formal GitHub PR reviews
- **Approve PRs**: For security reasons, Claude cannot approve pull requests
- **Post Multiple Comments**: Claude only acts by updating its initial comment
- **Execute Commands Outside Its Context**: Claude only has access to the repository and PR/issue context it's triggered in
- **Run Arbitrary Bash Commands**: By default, Claude cannot execute Bash commands unless explicitly allowed using the `allowed_tools` configuration
- **Perform Branch Operations**: Cannot merge branches, rebase, or perform other git operations beyond pushing commits
## How It Works
1. **Trigger Detection**: Listens for comments containing the trigger phrase (default: `@claude`) or issue assignment to a specific user
2. **Context Gathering**: Analyzes the PR/issue, comments, code changes
3. **Smart Responses**: Either answers questions or implements changes
4. **Branch Management**: Creates new PRs for human authors, pushes directly for Claude's own PRs
5. **Communication**: Posts updates at every step to keep you informed
This action is built on top of [`anthropics/claude-code-base-action`](https://github.com/anthropics/claude-code-base-action).
+141
View File
@@ -0,0 +1,141 @@
# Cloud Providers
You can authenticate with Claude using any of these four methods:
1. Direct Anthropic API (default)
2. Amazon Bedrock with OIDC authentication
3. Google Vertex AI 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#using-with-amazon-bedrock-and-google-cloud).
**Note**:
- Bedrock, Vertex, and Microsoft Foundry use OIDC authentication exclusively
- AWS Bedrock automatically uses cross-region inference profiles for certain models
- For cross-region inference profile models, you need to request and be granted access to the Claude models in all regions that the inference profile uses
## Model Configuration
Use provider-specific model names based on your chosen provider:
```yaml
# For direct Anthropic API (default)
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
# ... other inputs
# For Amazon Bedrock with OIDC
- uses: anthropics/claude-code-action@v1
with:
use_bedrock: "true"
claude_args: |
--model anthropic.claude-4-0-sonnet-20250805-v1:0
# ... other inputs
# For Google Vertex AI with OIDC
- uses: anthropics/claude-code-action@v1
with:
use_vertex: "true"
claude_args: |
--model claude-4-0-sonnet@20250805
# ... other inputs
# For Microsoft Foundry with OIDC
- uses: anthropics/claude-code-action@v1
with:
use_foundry: "true"
claude_args: |
--model claude-sonnet-4-5
# ... other inputs
```
## OIDC Authentication for Cloud Providers
AWS Bedrock, GCP Vertex AI, and Microsoft Foundry all support OIDC authentication.
```yaml
# For AWS Bedrock with OIDC
- name: Configure AWS Credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
aws-region: us-west-2
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
- uses: anthropics/claude-code-action@v1
with:
use_bedrock: "true"
claude_args: |
--model anthropic.claude-4-0-sonnet-20250805-v1:0
# ... other inputs
permissions:
id-token: write # Required for OIDC
```
```yaml
# For GCP Vertex AI with OIDC
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@v2
with:
workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }}
service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }}
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
- uses: anthropics/claude-code-action@v1
with:
use_vertex: "true"
claude_args: |
--model claude-4-0-sonnet@20250805
# ... other inputs
permissions:
id-token: write # Required for OIDC
```
```yaml
# For Microsoft Foundry with OIDC
- name: Authenticate to Azure
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
- uses: anthropics/claude-code-action@v1
with:
use_foundry: "true"
claude_args: |
--model claude-sonnet-4-5
# ... other inputs
env:
ANTHROPIC_FOUNDRY_BASE_URL: https://my-resource.services.ai.azure.com
permissions:
id-token: write # Required for OIDC
```
## Microsoft Foundry Setup
For detailed setup instructions for Microsoft Foundry, see the [official documentation](https://docs.anthropic.com/en/docs/claude-code/microsoft-foundry).
+401
View File
@@ -0,0 +1,401 @@
# Advanced Configuration
## Using Custom MCP Configuration
You can add custom MCP (Model Context Protocol) servers to extend Claude's capabilities using the `--mcp-config` flag in `claude_args`. These servers merge with the built-in GitHub MCP servers.
### Basic Example: Adding a Sequential Thinking Server
```yaml
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
claude_args: |
--mcp-config '{"mcpServers": {"sequential-thinking": {"command": "npx", "args": ["-y", "@modelcontextprotocol/server-sequential-thinking"]}}}'
--allowedTools mcp__sequential-thinking__sequentialthinking
# ... other inputs
```
### Passing Secrets to MCP Servers
For MCP servers that require sensitive information like API keys or tokens, you can create a configuration file with GitHub Secrets:
```yaml
- name: Create MCP Config
run: |
cat > /tmp/mcp-config.json << 'EOF'
{
"mcpServers": {
"custom-api-server": {
"command": "npx",
"args": ["-y", "@example/api-server"],
"env": {
"API_KEY": "${{ secrets.CUSTOM_API_KEY }}",
"BASE_URL": "https://api.example.com"
}
}
}
}
EOF
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
claude_args: |
--mcp-config /tmp/mcp-config.json
# ... other inputs
```
### Using Python MCP Servers with uv
For Python-based MCP servers managed with `uv`, you need to specify the directory containing your server:
```yaml
- name: Create MCP Config for Python Server
run: |
cat > /tmp/mcp-config.json << 'EOF'
{
"mcpServers": {
"my-python-server": {
"type": "stdio",
"command": "uv",
"args": [
"--directory",
"${{ github.workspace }}/path/to/server/",
"run",
"server_file.py"
]
}
}
}
EOF
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
claude_args: |
--mcp-config /tmp/mcp-config.json
--allowedTools my-python-server__<tool_name> # Replace <tool_name> with your server's tool names
# ... other inputs
```
For example, if your Python MCP server is at `mcp_servers/weather.py`, you would use:
```yaml
"args":
["--directory", "${{ github.workspace }}/mcp_servers/", "run", "weather.py"]
```
### Multiple MCP Servers
You can add multiple MCP servers by using multiple `--mcp-config` flags:
```yaml
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
claude_args: |
--mcp-config /tmp/config1.json
--mcp-config /tmp/config2.json
--mcp-config '{"mcpServers": {"inline-server": {"command": "npx", "args": ["@example/server"]}}}'
# ... other inputs
```
**Important**:
- Always use GitHub Secrets (`${{ secrets.SECRET_NAME }}`) for sensitive values like API keys, tokens, or passwords. Never hardcode secrets directly in the workflow file.
- Your custom servers will override any built-in servers with the same name.
- The `claude_args` supports multiple `--mcp-config` flags that will be merged together.
## Additional Permissions for CI/CD Integration
The `additional_permissions` input allows Claude to access GitHub Actions workflow information when you grant the necessary permissions. This is particularly useful for analyzing CI/CD failures and debugging workflow issues.
### Enabling GitHub Actions Access
To allow Claude to view workflow run results, job logs, and CI status:
1. **Grant the necessary permission to your GitHub token**:
- When using the default `GITHUB_TOKEN`, add the `actions: read` permission to your workflow:
```yaml
permissions:
contents: write
pull-requests: write
issues: write
actions: read # Add this line
```
2. **Configure the action with additional permissions**:
```yaml
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
additional_permissions: |
actions: read
# ... other inputs
```
3. **Claude will automatically get access to CI/CD tools**:
When you enable `actions: read`, Claude can use the following MCP tools:
- `mcp__github_ci__get_ci_status` - View workflow run statuses
- `mcp__github_ci__get_workflow_run_details` - Get detailed workflow information
- `mcp__github_ci__download_job_log` - Download and analyze job logs
### Example: Debugging Failed CI Runs
```yaml
name: Claude CI Helper
on:
issue_comment:
types: [created]
permissions:
contents: write
pull-requests: write
issues: write
actions: read # Required for CI access
jobs:
claude-ci-helper:
runs-on: ubuntu-latest
steps:
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
additional_permissions: |
actions: read
# Now Claude can respond to "@claude why did the CI fail?"
```
**Important Notes**:
- The GitHub token must have the corresponding permission in your workflow
- If the permission is missing, Claude will warn you and suggest adding it
- The following additional permissions can be requested beyond the defaults:
- `actions: read`
- `checks: read`
- `discussions: read` or `discussions: write`
- `workflows: read` or `workflows: write`
- Standard permissions (`contents: write`, `pull_requests: write`, `issues: write`) are always included and do not need to be specified
## Custom Environment Variables
You can pass custom environment variables to Claude Code execution using the `settings` input. This is useful for CI/test setups that require specific environment variables:
```yaml
- uses: anthropics/claude-code-action@v1
with:
settings: |
{
"env": {
"NODE_ENV": "test",
"CI": "true",
"DATABASE_URL": "postgres://test:test@localhost:5432/test_db"
}
}
# ... other inputs
```
These environment variables will be available to Claude Code during execution, allowing it to run tests, build processes, or other commands that depend on specific environment configurations.
## Limiting Conversation Turns
You can limit the number of back-and-forth exchanges Claude can have during task execution using the `claude_args` input. This is useful for:
- Controlling costs by preventing runaway conversations
- Setting time boundaries for automated workflows
- Ensuring predictable behavior in CI/CD pipelines
```yaml
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
claude_args: |
--max-turns 5 # Limit to 5 conversation turns
# ... other inputs
```
When the turn limit is reached, Claude will stop execution gracefully. Choose a value that gives Claude enough turns to complete typical tasks while preventing excessive usage.
## Custom Tools
By default, Claude only has access to:
- File operations (reading, committing, editing files, read-only git commands)
- Comment management (creating/updating comments)
- Basic GitHub operations
Claude does **not** have access to execute arbitrary Bash commands by default. If you want Claude to run specific commands (e.g., npm install, npm test), you must explicitly allow them using the `claude_args` configuration:
**Note**: If your repository has a `.mcp.json` file in the root directory, Claude will automatically detect and use the MCP server tools defined there. However, these tools still need to be explicitly allowed.
```yaml
- uses: anthropics/claude-code-action@v1
with:
claude_args: |
--allowedTools "Bash(npm install),Bash(npm run test),Edit,Replace,NotebookEditCell"
--disallowedTools "TaskOutput,KillTask"
# ... other inputs
```
**Note**: The base GitHub tools are always included. Use `--allowedTools` to add additional tools (including specific Bash commands), and `--disallowedTools` to prevent specific tools from being used.
## Custom Model
Specify a Claude model using `claude_args`:
```yaml
- uses: anthropics/claude-code-action@v1
with:
claude_args: |
--model claude-4-0-sonnet-20250805
# ... other inputs
```
For provider-specific models:
```yaml
# AWS Bedrock
- uses: anthropics/claude-code-action@v1
with:
use_bedrock: "true"
claude_args: |
--model anthropic.claude-4-0-sonnet-20250805-v1:0
# ... other inputs
# Google Vertex AI
- uses: anthropics/claude-code-action@v1
with:
use_vertex: "true"
claude_args: |
--model claude-4-0-sonnet@20250805
# ... other inputs
```
### 1M context models through an API gateway
When `ANTHROPIC_BASE_URL` points to an Anthropic-compatible API gateway,
Claude Code may not be able to verify that the gateway supports a model's native
1M context window and can budget the session at 200K instead. Append the
`[1m]` selector to explicitly use the 1M context window for supported models,
including Claude Opus 5 and Claude Sonnet 5:
```yaml
- uses: anthropics/claude-code-action@v1
with:
claude_args: |
--model "claude-opus-5[1m]"
# ... other inputs
```
Use the same selector when setting a model through `ANTHROPIC_MODEL` or another
Claude Code model environment variable. The selector is resolved by Claude Code
before requests are sent to the provider. The action's sanitized result output
includes each model's resolved
`contextWindow` and `maxOutputTokens` under `modelUsage`, so these limits are
visible without enabling `show_full_output`.
## Claude Code Settings
You can provide Claude Code settings to customize behavior such as model selection, environment variables, permissions, and hooks. Settings can be provided either as a JSON string or a path to a settings file.
### Option 1: Settings File
```yaml
- uses: anthropics/claude-code-action@v1
with:
settings: "path/to/settings.json"
# ... other inputs
```
### Option 2: Inline Settings
```yaml
- uses: anthropics/claude-code-action@v1
with:
settings: |
{
"model": "claude-opus-4-1-20250805",
"env": {
"DEBUG": "true",
"API_URL": "https://api.example.com"
},
"permissions": {
"allow": ["Bash", "Read"],
"deny": ["WebFetch"]
},
"hooks": {
"PreToolUse": [{
"matcher": "Bash",
"hooks": [{
"type": "command",
"command": "echo Running bash command..."
}]
}]
}
}
# ... other inputs
```
The settings support all Claude Code settings options including:
- `model`: Override the default model
- `env`: Environment variables for the session
- `permissions`: Tool usage permissions
- `hooks`: Pre/post tool execution hooks
- And more...
For a complete list of available settings and their descriptions, see the [Claude Code settings documentation](https://docs.anthropic.com/en/docs/claude-code/settings).
**Notes**:
- The `enableAllProjectMcpServers` setting is always set to `true` by this action to ensure MCP servers work correctly.
- The `claude_args` input provides direct access to Claude Code CLI arguments and takes precedence over settings.
- We recommend using `claude_args` for simple configurations and `settings` for complex configurations with hooks and environment variables.
## Migration from Deprecated Inputs
Many individual input parameters have been consolidated into `claude_args` or `settings`. Here's how to migrate:
| Old Input | New Approach |
| --------------------- | --------------------------------------------------------------- |
| `allowed_tools` | Use `claude_args: "--allowedTools Tool1,Tool2"` |
| `disallowed_tools` | Use `claude_args: "--disallowedTools Tool1,Tool2"` |
| `max_turns` | Use `claude_args: "--max-turns 10"` |
| `model` | Use `claude_args: "--model claude-4-0-sonnet-20250805"` |
| `claude_env` | Use `settings` with `"env"` object |
| `custom_instructions` | Use `claude_args: "--append-system-prompt 'Your instructions'"` |
| `mcp_config` | Use `claude_args: "--mcp-config '{...}'"` |
| `direct_prompt` | Use `prompt` input instead |
| `override_prompt` | Use `prompt` with GitHub context variables |
## Custom Executables for Specialized Environments
For specialized environments like Nix, custom container setups, or other package management systems where the default installation doesn't work, you can provide your own executables:
### Custom Claude Code Executable
Use `path_to_claude_code_executable` to provide your own Claude Code binary instead of using the automatically installed version:
```yaml
- uses: anthropics/claude-code-action@v1
with:
path_to_claude_code_executable: "/path/to/custom/claude"
# ... other inputs
```
### Custom Bun Executable
Use `path_to_bun_executable` to provide your own Bun runtime instead of the default installation:
```yaml
- uses: anthropics/claude-code-action@v1
with:
path_to_bun_executable: "/path/to/custom/bun"
# ... other inputs
```
**Important**: Using incompatible versions may cause the action to fail. Ensure your custom executables are compatible with the action's requirements.
+744
View File
@@ -0,0 +1,744 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Create Claude Code GitHub App</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
:root {
/* Claude Brand Colors */
--primary-dark: #0e0e0e;
--primary-light: #d4a27f;
--background-light: rgb(253, 253, 247);
--background-dark: rgb(9, 9, 11);
--text-primary: #1a1a1a;
--text-secondary: #525252;
--text-tertiary: #737373;
--border-color: rgba(0, 0, 0, 0.08);
--hover-bg: rgba(0, 0, 0, 0.02);
--success: #2ea44f;
--warning: #e3b341;
--card-shadow:
0 1px 3px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.04);
--card-shadow-hover:
0 4px 6px rgba(0, 0, 0, 0.07), 0 2px 4px rgba(0, 0, 0, 0.05);
}
body {
font-family:
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
"Helvetica Neue", Arial, sans-serif;
background: var(--background-light);
color: var(--text-primary);
line-height: 1.6;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.container {
max-width: 960px;
margin: 0 auto;
padding: 40px 24px;
}
/* Header */
header {
text-align: center;
margin-bottom: 48px;
}
h1 {
font-size: 36px;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 12px;
letter-spacing: -0.02em;
}
.subtitle {
font-size: 18px;
color: var(--text-secondary);
max-width: 640px;
margin: 0 auto;
line-height: 1.5;
}
/* Cards */
.card {
background: white;
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 32px;
margin-bottom: 24px;
box-shadow: var(--card-shadow);
transition: all 0.2s ease;
}
.card:hover {
box-shadow: var(--card-shadow-hover);
}
.card-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 20px;
}
.card-icon {
font-size: 24px;
line-height: 1;
}
h2 {
font-size: 20px;
font-weight: 600;
color: var(--text-primary);
margin: 0;
letter-spacing: -0.01em;
}
.card-description {
color: var(--text-secondary);
margin-bottom: 24px;
font-size: 15px;
line-height: 1.6;
}
/* Buttons */
.button-group {
display: flex;
flex-direction: column;
gap: 16px;
}
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 12px 24px;
font-size: 15px;
font-weight: 500;
border-radius: 8px;
border: none;
cursor: pointer;
transition: all 0.2s ease;
text-decoration: none;
font-family: inherit;
width: 100%;
}
.btn-primary {
background: var(--primary-dark);
color: white;
}
.btn-primary:hover {
background: #1a1a1a;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.btn-secondary {
background: var(--primary-light);
color: var(--primary-dark);
}
.btn-secondary:hover {
background: #c99a70;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(212, 162, 127, 0.3);
}
.btn-outline {
background: white;
color: var(--text-primary);
border: 1px solid var(--border-color);
}
.btn-outline:hover {
background: var(--hover-bg);
border-color: var(--text-secondary);
}
.btn:active {
transform: translateY(0);
}
.btn.copied {
background: var(--success);
color: white;
}
/* Form */
.form-row {
display: flex;
gap: 12px;
align-items: flex-end;
}
.form-group {
flex: 1;
}
label {
display: block;
font-size: 14px;
font-weight: 500;
color: var(--text-primary);
margin-bottom: 6px;
}
input[type="text"] {
width: 100%;
padding: 10px 14px;
font-size: 15px;
border: 1px solid var(--border-color);
border-radius: 8px;
font-family: inherit;
transition: all 0.2s ease;
background: white;
}
input[type="text"]:focus {
outline: none;
border-color: var(--primary-dark);
box-shadow: 0 0 0 3px rgba(14, 14, 14, 0.1);
}
/* Code Block */
.code-container {
position: relative;
background: #fafafa;
border: 1px solid var(--border-color);
border-radius: 8px;
margin: 20px 0;
}
.code-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
border-bottom: 1px solid var(--border-color);
}
.code-label {
font-size: 13px;
font-weight: 500;
color: var(--text-secondary);
}
.copy-btn {
padding: 6px 12px;
font-size: 13px;
font-weight: 500;
background: white;
color: var(--text-primary);
border: 1px solid var(--border-color);
border-radius: 6px;
cursor: pointer;
transition: all 0.2s ease;
}
.copy-btn:hover {
background: var(--hover-bg);
}
.copy-btn.copied {
background: var(--success);
color: white;
border-color: var(--success);
}
.code-block {
padding: 16px;
overflow-x: auto;
font-family:
"SF Mono", Monaco, "Cascadia Code", "Roboto Mono", Consolas,
"Courier New", monospace;
font-size: 13px;
line-height: 1.6;
color: var(--text-primary);
white-space: pre;
}
/* Permissions List */
.permissions-grid {
display: grid;
gap: 12px;
margin-top: 16px;
}
.permission-item {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 14px;
background: #fafafa;
border-radius: 8px;
font-size: 14px;
}
.permission-icon {
color: var(--success);
font-size: 16px;
line-height: 1;
}
.permission-name {
font-weight: 500;
color: var(--text-primary);
}
.permission-value {
margin-left: auto;
color: var(--text-secondary);
font-size: 13px;
}
/* Steps */
.steps {
margin: 24px 0;
}
.step {
display: flex;
gap: 16px;
margin-bottom: 20px;
}
.step-number {
flex-shrink: 0;
width: 28px;
height: 28px;
background: var(--primary-dark);
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
font-weight: 600;
}
.step-content {
flex: 1;
padding-top: 2px;
}
.step-content p {
color: var(--text-secondary);
font-size: 15px;
line-height: 1.6;
}
.step-content strong {
color: var(--text-primary);
font-weight: 500;
}
/* Alert Box */
.alert {
display: flex;
gap: 12px;
padding: 16px;
background: #fffbf0;
border: 1px solid #f5e7c3;
border-radius: 8px;
margin-top: 32px;
}
.alert-icon {
font-size: 18px;
line-height: 1;
flex-shrink: 0;
}
.alert-content {
flex: 1;
font-size: 14px;
line-height: 1.6;
}
.alert-content strong {
color: var(--text-primary);
font-weight: 600;
}
/* Responsive */
@media (min-width: 640px) {
.button-group {
flex-direction: row;
}
.btn {
width: auto;
}
.permissions-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 640px) {
h1 {
font-size: 28px;
}
.subtitle {
font-size: 16px;
}
.card {
padding: 24px 20px;
}
.container {
padding: 24px 16px;
}
}
/* Hidden form elements */
.hidden-form {
display: none;
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>Create Your Custom GitHub App</h1>
<p class="subtitle">
Set up a custom GitHub App for Claude Code Action with all required
permissions automatically configured.
</p>
</header>
<!-- Quick Setup Card -->
<div class="card">
<div class="card-header">
<span class="card-icon">🚀</span>
<h2>Quick Setup</h2>
</div>
<p class="card-description">
Create your GitHub App with one click. All permissions will be
automatically configured for Claude Code Action.
</p>
<div class="button-group">
<!-- Personal Account Button -->
<form
action="https://github.com/settings/apps/new"
method="post"
class="hidden-form"
id="personal-form"
>
<input type="hidden" name="manifest" id="personal-manifest" />
</form>
<button
type="button"
class="btn btn-primary"
onclick="submitPersonalForm()"
>
<span>👤</span>
<span>Create for Personal Account</span>
</button>
<!-- Organization Form -->
<form id="org-form" method="post" class="hidden-form">
<input type="hidden" name="manifest" id="org-manifest" />
</form>
</div>
<!-- Organization Input -->
<div
style="
margin-top: 24px;
padding-top: 24px;
border-top: 1px solid var(--border-color);
"
>
<label for="org-name" style="margin-bottom: 8px"
>Or create for an organization:</label
>
<div class="form-row">
<div class="form-group">
<input
type="text"
id="org-name"
placeholder="Enter organization name (e.g., my-org)"
/>
</div>
<button
type="button"
class="btn btn-secondary"
onclick="submitOrgForm()"
style="flex-shrink: 0"
>
<span>🏢</span>
<span>Create for Org</span>
</button>
</div>
</div>
</div>
<!-- Permissions Card -->
<div class="card">
<div class="card-header">
<span class="card-icon"></span>
<h2>Configured Permissions</h2>
</div>
<p class="card-description">
Your GitHub App will be created with these permissions:
</p>
<div class="permissions-grid">
<div class="permission-item">
<span class="permission-icon"></span>
<span class="permission-name">Contents</span>
<span class="permission-value">Read & Write</span>
</div>
<div class="permission-item">
<span class="permission-icon"></span>
<span class="permission-name">Issues</span>
<span class="permission-value">Read & Write</span>
</div>
<div class="permission-item">
<span class="permission-icon"></span>
<span class="permission-name">Pull Requests</span>
<span class="permission-value">Read & Write</span>
</div>
<div class="permission-item">
<span class="permission-icon"></span>
<span class="permission-name">Actions</span>
<span class="permission-value">Read</span>
</div>
<div class="permission-item">
<span class="permission-icon"></span>
<span class="permission-name">Metadata</span>
<span class="permission-value">Read</span>
</div>
</div>
</div>
<!-- Next Steps Card -->
<div class="card">
<div class="card-header">
<span class="card-icon">📋</span>
<h2>Next Steps</h2>
</div>
<p class="card-description">
After creating your app, complete these steps:
</p>
<div class="steps">
<div class="step">
<div class="step-number">1</div>
<div class="step-content">
<p>
<strong>Generate a private key:</strong> In your app settings,
scroll to "Private keys" and click "Generate a private key"
</p>
</div>
</div>
<div class="step">
<div class="step-number">2</div>
<div class="step-content">
<p>
<strong>Install the app:</strong> Click "Install App" and select
the repositories where you want to use Claude
</p>
</div>
</div>
<div class="step">
<div class="step-number">3</div>
<div class="step-content">
<p>
<strong>Configure your workflow:</strong> Add your app's ID and
private key to your repository secrets
</p>
</div>
</div>
</div>
</div>
<!-- Manual Setup Card -->
<div class="card">
<div class="card-header">
<span class="card-icon">⚙️</span>
<h2>Manual Setup</h2>
</div>
<p class="card-description">
If the buttons above don't work, you can manually create the app by
copying the manifest JSON below:
</p>
<div class="code-container">
<div class="code-header">
<span class="code-label">github-app-manifest.json</span>
<button class="copy-btn" onclick="copyManifest()">Copy</button>
</div>
<div class="code-block" id="manifest-json"></div>
</div>
<div class="steps">
<div class="step">
<div class="step-number">1</div>
<div class="step-content">
<p>Copy the manifest JSON above</p>
</div>
</div>
<div class="step">
<div class="step-number">2</div>
<div class="step-content">
<p>
Go to
<a
href="https://github.com/settings/apps/new"
target="_blank"
style="color: var(--primary-dark); text-decoration: underline"
>GitHub App Settings</a
>
</p>
</div>
</div>
<div class="step">
<div class="step-number">3</div>
<div class="step-content">
<p>Look for "Create from manifest" option and paste the JSON</p>
</div>
</div>
</div>
</div>
<!-- Warning Alert -->
<div class="alert">
<span class="alert-icon">⚠️</span>
<div class="alert-content">
<strong>Important:</strong> Keep your private key secure! Never commit
it to your repository. Always use GitHub secrets to store sensitive
credentials.
</div>
</div>
</div>
<script>
// Manifest configuration
const manifest = {
name: "Claude Code Custom App",
description:
"Custom GitHub App for Claude Code Action - AI-powered coding assistant for GitHub workflows",
url: "https://github.com/anthropics/claude-code-action",
hook_attributes: {
url: "https://example.com/github/webhook",
active: false,
},
redirect_url: "https://github.com/settings/apps/new",
callback_urls: [],
setup_url:
"https://github.com/anthropics/claude-code-action/blob/main/docs/setup.md",
public: false,
default_permissions: {
contents: "write",
issues: "write",
pull_requests: "write",
actions: "read",
metadata: "read",
},
default_events: [
"issue_comment",
"issues",
"pull_request",
"pull_request_review",
"pull_request_review_comment",
],
};
// Populate manifest fields
const manifestJson = JSON.stringify(manifest);
const manifestJsonPretty = JSON.stringify(manifest, null, 2);
document.getElementById("personal-manifest").value = manifestJson;
document.getElementById("org-manifest").value = manifestJson;
// Display formatted JSON
const manifestDisplay = document.getElementById("manifest-json");
manifestDisplay.textContent = manifestJsonPretty;
// Submit personal form
function submitPersonalForm() {
document.getElementById("personal-form").submit();
}
// Submit organization form
function submitOrgForm() {
const orgName = document.getElementById("org-name").value.trim();
if (!orgName) {
alert("Please enter an organization name");
document.getElementById("org-name").focus();
return;
}
const form = document.getElementById("org-form");
form.action = `https://github.com/organizations/${orgName}/settings/apps/new`;
form.submit();
}
// Allow Enter key to submit org form
document
.getElementById("org-name")
.addEventListener("keypress", function (e) {
if (e.key === "Enter") {
e.preventDefault();
submitOrgForm();
}
});
// Copy manifest to clipboard
function copyManifest() {
navigator.clipboard
.writeText(manifestJsonPretty)
.then(() => {
const button = document.querySelector(".copy-btn");
const originalText = button.textContent;
button.textContent = "Copied!";
button.classList.add("copied");
setTimeout(() => {
button.textContent = originalText;
button.classList.remove("copied");
}, 2000);
})
.catch(() => {
// Fallback for older browsers
const textArea = document.createElement("textarea");
textArea.value = manifestJsonPretty;
textArea.style.position = "fixed";
textArea.style.opacity = "0";
document.body.appendChild(textArea);
textArea.select();
try {
document.execCommand("copy");
const button = document.querySelector(".copy-btn");
const originalText = button.textContent;
button.textContent = "Copied!";
button.classList.add("copied");
setTimeout(() => {
button.textContent = originalText;
button.classList.remove("copied");
}, 2000);
} catch (err) {
alert("Failed to copy. Please copy manually.");
}
document.body.removeChild(textArea);
});
}
</script>
</body>
</html>
+122
View File
@@ -0,0 +1,122 @@
# Custom Automations
These examples show how to configure Claude to act automatically based on GitHub events. When you provide a `prompt` input, the action automatically runs in agent mode without requiring manual @mentions. Without a `prompt`, it runs in interactive mode, responding to @claude mentions.
## Mode Detection & Tracking Comments
The action automatically detects which mode to use based on your configuration:
- **Interactive Mode** (no `prompt` input): Responds to @claude mentions, creates tracking comments with progress indicators
- **Automation Mode** (with `prompt` input): Executes immediately, **does not create tracking comments**
> **Note**: In v1, automation mode intentionally does not create tracking comments by default to reduce noise in automated workflows. If you need progress tracking, use the `track_progress: true` input parameter.
## Supported GitHub Events
This action supports the following GitHub events ([learn more GitHub event triggers](https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows)):
- `pull_request` or `pull_request_target` - When PRs are opened or synchronized
- `issue_comment` - When comments are created on issues or PRs
- `pull_request_comment` - When comments are made on PR diffs
- `issues` - When issues are opened or assigned
- `pull_request_review` - When PR reviews are submitted
- `pull_request_review_comment` - When comments are made on PR reviews
- `repository_dispatch` - Custom events triggered via API
- `workflow_dispatch` - Manual workflow triggers (coming soon)
## Automated Documentation Updates
Automatically update documentation when specific files change (see [`examples/pr-review-filtered-paths.yml`](../examples/pr-review-filtered-paths.yml)):
```yaml
on:
pull_request:
paths:
- "src/api/**/*.ts"
steps:
- uses: anthropics/claude-code-action@v1
with:
prompt: |
Update the API documentation in README.md to reflect
the changes made to the API endpoints in this PR.
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
```
When API files are modified, the action automatically detects that a `prompt` is provided and runs in agent mode. Claude updates your README with the latest endpoint documentation and pushes the changes back to the PR, keeping your docs in sync with your code.
## Author-Specific Code Reviews
Automatically review PRs from specific authors or external contributors (see [`examples/pr-review-filtered-authors.yml`](../examples/pr-review-filtered-authors.yml)):
```yaml
on:
pull_request:
types: [opened, synchronize]
jobs:
review-by-author:
if: |
github.event.pull_request.user.login == 'developer1' ||
github.event.pull_request.user.login == 'external-contributor'
steps:
- uses: anthropics/claude-code-action@v1
with:
prompt: |
Please provide a thorough review of this pull request.
Pay extra attention to coding standards, security practices,
and test coverage since this is from an external contributor.
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
```
Perfect for automatically reviewing PRs from new team members, external contributors, or specific developers who need extra guidance. The action automatically runs in agent mode when a `prompt` is provided.
## Custom Prompt Templates
Use the `prompt` input with GitHub context variables for dynamic automation:
```yaml
- uses: anthropics/claude-code-action@v1
with:
prompt: |
Analyze PR #${{ github.event.pull_request.number }} in ${{ github.repository }} for security vulnerabilities.
Focus on:
- SQL injection risks
- XSS vulnerabilities
- Authentication bypasses
- Exposed secrets or credentials
Provide severity ratings (Critical/High/Medium/Low) for any issues found.
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
```
You can access any GitHub context variable using the standard GitHub Actions syntax:
- `${{ github.repository }}` - The repository name
- `${{ github.event.pull_request.number }}` - PR number
- `${{ github.event.issue.number }}` - Issue number
- `${{ github.event.pull_request.title }}` - PR title
- `${{ github.event.pull_request.body }}` - PR description
- `${{ github.event.comment.body }}` - Comment text
- `${{ github.actor }}` - User who triggered the workflow
- `${{ github.base_ref }}` - Base branch for PRs
- `${{ github.head_ref }}` - Head branch for PRs
## Advanced Configuration with claude_args
For more control over Claude's behavior, use the `claude_args` input to pass CLI arguments directly:
```yaml
- uses: anthropics/claude-code-action@v1
with:
prompt: "Review this PR for performance issues"
claude_args: |
--max-turns 15
--model claude-4-0-sonnet-20250805
--allowedTools Edit,Read,Write,Bash
--system-prompt "You are a performance optimization expert. Focus on identifying bottlenecks and suggesting improvements."
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
```
This provides full access to Claude Code CLI capabilities while maintaining the simplified action interface.
+63
View File
@@ -0,0 +1,63 @@
# Experimental Features
**Note:** Experimental features are considered unstable and not supported for production use. They may change or be removed at any time.
## Automatic Mode Detection
The action intelligently detects the appropriate execution mode based on your workflow context, eliminating the need for manual mode configuration.
### Interactive Mode (Tag Mode)
Activated when Claude detects @mentions, issue assignments, or labels—without an explicit `prompt`.
- **Triggers**: `@claude` mentions in comments, issue assignment to claude user, label application
- **Features**: Creates tracking comments with progress checkboxes, full implementation capabilities
- **Use case**: Interactive code assistance, Q&A, and implementation requests
```yaml
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
# No prompt needed - responds to @claude mentions
```
### Automation Mode (Agent Mode)
Automatically activated when you provide a `prompt` input.
- **Triggers**: Any GitHub event when `prompt` input is provided
- **Features**: Direct execution without requiring @claude mentions, streamlined for automation
- **Use case**: Automated PR reviews, scheduled tasks, workflow automation
```yaml
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
prompt: |
Check for outdated dependencies and create an issue if any are found.
# Automatically runs in agent mode when prompt is provided
```
### How It Works
The action uses this logic to determine the mode:
1. **If `prompt` is provided** → Runs in **agent mode** for automation
2. **If no `prompt` but @claude is mentioned** → Runs in **tag mode** for interaction
3. **If neither** → No action is taken
This automatic detection ensures your workflows are simpler and more intuitive, without needing to understand or configure different modes.
### Advanced Mode Control
For specialized use cases, you can fine-tune behavior using `claude_args`:
```yaml
- uses: anthropics/claude-code-action@v1
with:
prompt: "Review this PR"
claude_args: |
--max-turns 20
--system-prompt "You are a code review specialist"
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
```
+267
View File
@@ -0,0 +1,267 @@
# Frequently Asked Questions (FAQ)
This FAQ addresses common questions and gotchas when using the Claude Code GitHub Action.
## Triggering and Authentication
### Why doesn't tagging @claude from my automated workflow work?
The `github-actions` user cannot trigger subsequent GitHub Actions workflows. This is a GitHub security feature to prevent infinite loops. To make this work, you need to use a Personal Access Token (PAT) instead, which will act as a regular user, or use a separate app token of your own. When posting a comment on an issue or PR from your workflow, use your PAT instead of the `GITHUB_TOKEN` generated in your workflow.
### Why does Claude say I don't have permission to trigger it?
Only users with **write permissions** to the repository can trigger Claude. This is a security feature to prevent unauthorized use. Make sure the user commenting has at least write access to the repository.
### Why can't I assign @claude to an issue on my repository?
If you're in a public repository, you should be able to assign to Claude without issue. If it's a private organization repository, you can only assign to users in your own organization, which Claude isn't. In this case, you'll need to make a custom user in that case.
### Why am I getting OIDC authentication errors?
If you're using the default GitHub App authentication, you must add the `id-token: write` permission to your workflow:
```yaml
permissions:
contents: read
id-token: write # Required for OIDC authentication
```
The OIDC token is required in order for the Claude GitHub app to function. If you wish to not use the GitHub app, you can instead provide a `github_token` input to the action for Claude to operate with. See the [Claude Code permissions documentation][perms] for more.
### Why am I getting '403 Resource not accessible by integration' errors?
This error occurs when the action tries to fetch the authenticated user information using a GitHub App installation token. GitHub App tokens have limited access and cannot access the `/user` endpoint, which causes this 403 error.
**Solution**: The action now includes `bot_id` and `bot_name` inputs that default to Claude's bot credentials. This avoids the need to fetch user information from the API.
For the default claude[bot]:
```yaml
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
# bot_id and bot_name have sensible defaults, no need to specify
```
For custom bots, specify both:
```yaml
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
bot_id: "12345678" # Your bot's GitHub user ID
bot_name: "my-bot" # Your bot's username
```
This issue typically only affects agent/automation mode workflows. Interactive workflows (with @claude mentions) don't encounter this issue as they use the comment author's information.
## Claude's Capabilities and Limitations
### Why won't Claude update workflow files when I ask it to?
The GitHub App for Claude doesn't have workflow write access for security reasons. This prevents Claude from modifying CI/CD configurations that could potentially create unintended consequences. This is something we may reconsider in the future.
### Why won't Claude rebase my branch?
Claude only creates and pushes commits. It does not merge branches, rebase, force push, or perform other destructive git operations. Specifically, Claude is configured to:
- Never push to branches other than where it was invoked (either its own branch or the PR branch)
- Never force push or perform destructive operations
This restriction is enforced in Claude's system prompt, so it applies even if you grant the underlying git tools (for example `--allowedTools "Bash(git rebase:*)"`). In that case Claude will still decline rebase requests and explain the limitation rather than running the command.
If you need to rebase, do it yourself locally — or with the Claude Code CLI outside of this action — and push the result.
### Why won't Claude create a pull request?
Claude doesn't create PRs by default. Instead, it pushes commits to a branch and provides a link to a pre-filled PR submission page. This approach ensures your repository's branch protection rules are still adhered to and gives you final control over PR creation.
### Can Claude see my GitHub Actions CI results?
Yes! Claude can access GitHub Actions workflow runs, job logs, and test results on the PR where it's tagged. To enable this:
1. Add `actions: read` permission to your workflow:
```yaml
permissions:
contents: write
pull-requests: write
issues: write
actions: read
```
2. Configure the action with additional permissions:
```yaml
- uses: anthropics/claude-code-action@v1
with:
additional_permissions: |
actions: read
```
Claude will then be able to analyze CI failures and help debug workflow issues. For running tests locally before commits, you can still instruct Claude to do so in your request.
### Why does Claude only update one comment instead of creating new ones?
Claude is configured to update a single comment to avoid cluttering PR/issue discussions. All of Claude's responses, including progress updates and final results, will appear in the same comment with checkboxes showing task progress.
## Branch and Commit Behavior
### Why did Claude create a new branch when commenting on a closed PR?
Claude's branch behavior depends on the context:
- **Open PRs**: Pushes directly to the existing PR branch
- **Closed/Merged PRs**: Creates a new branch (cannot push to closed PR branches)
- **Issues**: Always creates a new branch with a timestamp
### Why are my commits shallow/missing history?
For performance, Claude uses shallow clones:
- PRs: `--depth=20` (last 20 commits)
- New branches: `--depth=1` (single commit)
If you need full history, you can configure this in your workflow before calling Claude in the `actions/checkout` step.
```
- uses: actions/checkout@v6
depth: 0 # will fetch full repo history
```
## Configuration and Tools
### How does automatic mode detection work?
The action intelligently detects whether to run in interactive mode or automation mode:
- **With `prompt` input**: Runs in automation mode - executes immediately without waiting for @claude mentions
- **Without `prompt` input**: Runs in interactive mode - waits for @claude mentions in comments
This automatic detection eliminates the need to manually configure modes.
Example:
```yaml
# Automation mode - runs automatically
prompt: "Review this PR for security vulnerabilities"
# Interactive mode - waits for @claude mention
# (no prompt provided)
```
### What happened to `direct_prompt` and `custom_instructions`?
**These inputs are deprecated in v1.0:**
- **`direct_prompt`** → Use `prompt` instead
- **`custom_instructions`** → Use `claude_args` with `--append-system-prompt` (appends to the default system prompt, matching v0 behavior; `--system-prompt` replaces it entirely)
Migration examples:
```yaml
# Old (v0.x)
direct_prompt: "Review this PR"
custom_instructions: "Focus on security"
# New (v1.0)
prompt: "Review this PR"
claude_args: |
--append-system-prompt "Focus on security"
```
### Why doesn't Claude execute my bash commands?
The Bash tool is **disabled by default** for security. To enable individual bash commands using `claude_args`:
```yaml
claude_args: |
--allowedTools "Bash(npm:*),Bash(git:*)" # Allows only npm and git commands
```
### Can Claude work across multiple repositories?
No, Claude's GitHub app token is sandboxed to the current repository only. It cannot push to any other repositories. It can, however, read public repositories, but to get access to this, you must configure it with tools to do so.
### Why aren't comments posted as claude[bot]?
Comments appear as claude[bot] when the action uses its built-in authentication. However, if you provide a `github_token` in your workflow, the action will use that token's authentication instead, causing comments to appear under a different username.
**Solution**: Remove `github_token` from your workflow file unless you're using a custom GitHub App.
**Note**: The `use_sticky_comment` feature only works with claude[bot] authentication. If you're using a custom `github_token`, sticky comments won't update properly since they expect the claude[bot] username.
## MCP Servers and Extended Functionality
### What MCP servers are available by default?
Claude Code Action automatically configures two MCP servers:
1. **GitHub MCP server**: For GitHub API operations
2. **File operations server**: For advanced file manipulation
However, tools from these servers still need to be explicitly allowed via `claude_args` with `--allowedTools`.
## Troubleshooting
### How can I debug what Claude is doing?
Check the GitHub Action log for Claude's run for the full execution trace.
### Why can't I trigger Claude with `@claude-mention` or `claude!`?
The trigger uses word boundaries, so `@claude` must be a complete word. Variations like `@claude-bot`, `@claude!`, or `claude@mention` won't work unless you customize the `trigger_phrase`.
### How can I use custom executables in specialized environments?
For specialized environments like Nix, NixOS, or custom container setups where you need to provide your own executables:
**Using a custom Claude Code executable:**
```yaml
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
path_to_claude_code_executable: "/path/to/custom/claude"
# ... other inputs
```
**Using a custom Bun executable:**
```yaml
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
path_to_bun_executable: "/path/to/custom/bun"
# ... other inputs
```
**Common use cases:**
- Nix/NixOS environments where packages are managed differently
- Docker containers with pre-installed executables
- Custom build environments with specific version requirements
- Debugging specific issues with particular versions
**Important notes:**
- Using an older Claude Code version may cause problems if the action uses newer features
- Using an incompatible Bun version may cause runtime errors
- The action will skip automatic installation when custom paths are provided
- Ensure the custom executables are available in your GitHub Actions environment
## Best Practices
1. **Always specify permissions explicitly** in your workflow file
2. **Use GitHub Secrets** for API keys - never hardcode them
3. **Be specific with tool permissions** - only enable what's necessary via `claude_args`
4. **Test in a separate branch** before using on important PRs
5. **Monitor Claude's token usage** to avoid hitting API limits
6. **Review Claude's changes** carefully before merging
## Getting Help
If you encounter issues not covered here:
1. Check the [GitHub Issues](https://github.com/anthropics/claude-code-action/issues)
2. Review the [example workflows](https://github.com/anthropics/claude-code-action#examples)
[perms]: https://docs.anthropic.com/en/docs/claude-code/settings#permissions
+357
View File
@@ -0,0 +1,357 @@
# Migration Guide: v0.x to v1.0
This guide helps you migrate from Claude Code Action v0.x to v1.0. The new version introduces intelligent mode detection and simplified configuration while maintaining backward compatibility for most use cases.
## Overview of Changes
### 🎯 Key Improvements in v1.0
1. **Automatic Mode Detection** - No more manual `mode` configuration
2. **Simplified Configuration** - Unified `prompt` and `claude_args` inputs
3. **Better SDK Alignment** - Closer integration with Claude Code CLI
### ⚠️ Breaking Changes
The following inputs have been deprecated and replaced:
| Deprecated Input | Replacement | Notes |
| --------------------- | ------------------------------------- | ----------------------------------------------------------------------------------- |
| `mode` | Auto-detected | Action automatically chooses based on context |
| `direct_prompt` | `prompt` | Direct drop-in replacement |
| `override_prompt` | `prompt` | Use GitHub context variables instead |
| `custom_instructions` | `claude_args: --append-system-prompt` | Appends to the default prompt (v0 behavior); `--system-prompt` replaces it entirely |
| `max_turns` | `claude_args: --max-turns` | Use CLI format |
| `model` | `claude_args: --model` | Specify via CLI |
| `allowed_tools` | `claude_args: --allowedTools` | Use CLI format |
| `disallowed_tools` | `claude_args: --disallowedTools` | Use CLI format |
| `claude_env` | `settings` with env object | Use settings JSON |
| `mcp_config` | `claude_args: --mcp-config` | Pass MCP config via CLI arguments |
| `timeout_minutes` | Use GitHub Actions `timeout-minutes` | Configure at job level instead of input level |
## Migration Examples
### Basic Interactive Workflow (@claude mentions)
**Before (v0.x):**
```yaml
- uses: anthropics/claude-code-action@beta
with:
mode: "tag"
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
custom_instructions: "Follow our coding standards"
max_turns: "10"
allowed_tools: "Edit,Read,Write"
```
**After (v1.0):**
```yaml
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
claude_args: |
--max-turns 10
--append-system-prompt "Follow our coding standards"
--allowedTools Edit,Read,Write
```
### Automation Workflow
**Before (v0.x):**
```yaml
- uses: anthropics/claude-code-action@beta
with:
mode: "agent"
direct_prompt: "Review this PR for security issues"
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
model: "claude-3-5-sonnet-20241022"
allowed_tools: "Edit,Read,Write"
```
**After (v1.0):**
```yaml
- uses: anthropics/claude-code-action@v1
with:
prompt: |
REPO: ${{ github.repository }}
PR NUMBER: ${{ github.event.pull_request.number }}
Review this PR for security issues
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
claude_args: |
--model claude-4-0-sonnet-20250805
--allowedTools Edit,Read,Write
```
> **⚠️ Important**: For PR reviews, always include the repository and PR context in your prompt. This ensures Claude knows which PR to review.
### Automation with Progress Tracking (New in v1.0)
**Missing the tracking comments from v0.x agent mode?** The new `track_progress` input brings them back!
In v1.0, automation mode (with `prompt` input) doesn't create tracking comments by default to reduce noise. However, if you need progress visibility, you can use the `track_progress` feature:
**Before (v0.x with tracking):**
```yaml
- uses: anthropics/claude-code-action@beta
with:
mode: "agent"
direct_prompt: "Review this PR for security issues"
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
```
**After (v1.0 with tracking):**
```yaml
- uses: anthropics/claude-code-action@v1
with:
track_progress: true # Forces tag mode with tracking comments
prompt: |
REPO: ${{ github.repository }}
PR NUMBER: ${{ github.event.pull_request.number }}
Review this PR for security issues
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
```
#### Benefits of `track_progress`
1. **Preserves GitHub Context**: Automatically includes all PR/issue details, comments, and attachments
2. **Brings Back Tracking Comments**: Creates progress indicators just like v0.x agent mode
3. **Works with Custom Prompts**: Your `prompt` is injected as custom instructions while maintaining context
#### Supported Events for `track_progress`
The `track_progress` input only works with these GitHub events:
**Pull Request Events:**
- `opened` - New PR created
- `synchronize` - PR updated with new commits
- `ready_for_review` - Draft PR marked as ready
- `reopened` - Previously closed PR reopened
**Issue Events:**
- `opened` - New issue created
- `edited` - Issue title or body modified
- `labeled` - Label added to issue
- `assigned` - Issue assigned to user
> **Note**: Using `track_progress: true` with unsupported events will cause an error.
### Custom Template with Variables
**Before (v0.x):**
```yaml
- uses: anthropics/claude-code-action@beta
with:
override_prompt: |
Analyze PR #$PR_NUMBER in $REPOSITORY
Changed files: $CHANGED_FILES
Focus on security vulnerabilities
```
**After (v1.0):**
```yaml
- uses: anthropics/claude-code-action@v1
with:
prompt: |
REPO: ${{ github.repository }}
PR NUMBER: ${{ github.event.pull_request.number }}
Analyze this pull request focusing on security vulnerabilities in the changed files.
Note: The PR branch is already checked out in the current working directory.
```
> **💡 Tip**: While you can access GitHub context variables in your prompt, it's recommended to use the standard `REPO:` and `PR NUMBER:` format for consistency.
### Environment Variables
**Before (v0.x):**
```yaml
- uses: anthropics/claude-code-action@beta
with:
claude_env: |
NODE_ENV: test
CI: true
```
**After (v1.0):**
```yaml
- uses: anthropics/claude-code-action@v1
with:
settings: |
{
"env": {
"NODE_ENV": "test",
"CI": "true"
}
}
```
### Timeout Configuration
**Before (v0.x):**
```yaml
- uses: anthropics/claude-code-action@beta
with:
timeout_minutes: 30
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
```
**After (v1.0):**
```yaml
jobs:
claude-task:
runs-on: ubuntu-latest
timeout-minutes: 30 # Moved to job level
steps:
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
```
## How Mode Detection Works
The action now automatically detects the appropriate mode:
1. **If `prompt` is provided** → Runs in **automation mode**
- Executes immediately without waiting for @claude mentions
- Perfect for scheduled tasks, PR automation, etc.
2. **If no `prompt` but @claude is mentioned** → Runs in **interactive mode**
- Waits for and responds to @claude mentions
- Creates tracking comments with progress
3. **If neither** → No action is taken
## Advanced Configuration with claude_args
The `claude_args` input provides direct access to Claude Code CLI arguments:
```yaml
claude_args: |
--max-turns 15
--model claude-4-0-sonnet-20250805
--allowedTools Edit,Read,Write,Bash
--disallowedTools WebSearch
--system-prompt "You are a senior engineer focused on code quality"
--mcp-config '{"mcpServers": {"custom": {"command": "npx", "args": ["-y", "@example/server"]}}}'
```
### Common claude_args Options
| Option | Description | Example |
| ------------------------ | ------------------------------------------------------------------------- | ------------------------------------------------------ |
| `--max-turns` | Limit conversation turns | `--max-turns 10` |
| `--model` | Specify Claude model | `--model claude-4-0-sonnet-20250805` |
| `--allowedTools` | Enable specific tools | `--allowedTools Edit,Read,Write` |
| `--disallowedTools` | Disable specific tools | `--disallowedTools WebSearch` |
| `--system-prompt` | Replace the entire default system prompt | `--system-prompt "Focus on security"` |
| `--append-system-prompt` | Append to the default system prompt (keeps Claude Code's built-in prompt) | `--append-system-prompt "Follow our coding standards"` |
| `--mcp-config` | Add MCP server config | `--mcp-config '{"mcpServers": {...}}'` |
## Provider-Specific Updates
### AWS Bedrock
```yaml
- uses: anthropics/claude-code-action@v1
with:
use_bedrock: "true"
claude_args: |
--model anthropic.claude-4-0-sonnet-20250805-v1:0
```
### Google Vertex AI
```yaml
- uses: anthropics/claude-code-action@v1
with:
use_vertex: "true"
claude_args: |
--model claude-4-0-sonnet@20250805
```
## MCP Configuration Migration
### Adding Custom MCP Servers
**Before (v0.x):**
```yaml
- uses: anthropics/claude-code-action@beta
with:
mcp_config: |
{
"mcpServers": {
"custom-server": {
"command": "npx",
"args": ["-y", "@example/server"]
}
}
}
```
**After (v1.0):**
```yaml
- uses: anthropics/claude-code-action@v1
with:
claude_args: |
--mcp-config '{"mcpServers": {"custom-server": {"command": "npx", "args": ["-y", "@example/server"]}}}'
```
You can also pass MCP configuration from a file:
```yaml
- uses: anthropics/claude-code-action@v1
with:
claude_args: |
--mcp-config /path/to/mcp-config.json
```
## Step-by-Step Migration Checklist
- [ ] Update action version from `@beta` to `@v1`
- [ ] Remove `mode` input (auto-detected now)
- [ ] Replace `direct_prompt` with `prompt`
- [ ] Replace `override_prompt` with `prompt` using GitHub context
- [ ] Move `custom_instructions` to `claude_args` with `--append-system-prompt`
- [ ] Convert `max_turns` to `claude_args` with `--max-turns`
- [ ] Convert `model` to `claude_args` with `--model`
- [ ] Convert `allowed_tools` to `claude_args` with `--allowedTools`
- [ ] Convert `disallowed_tools` to `claude_args` with `--disallowedTools`
- [ ] Move `claude_env` to `settings` JSON format
- [ ] Move `mcp_config` to `claude_args` with `--mcp-config`
- [ ] Replace `timeout_minutes` with GitHub Actions `timeout-minutes` at job level
- [ ] **Optional**: Add `track_progress: true` if you need tracking comments in automation mode
- [ ] Test workflow in a non-production environment
## Getting Help
If you encounter issues during migration:
1. Check the [FAQ](./faq.md) for common questions
2. Review [example workflows](../examples/) for reference
3. Open an [issue](https://github.com/anthropics/claude-code-action/issues) for support
## Version Compatibility
- **v0.x workflows** will continue to work but with deprecation warnings
- **v1.0** is the recommended version for all new workflows
- Future versions may remove deprecated inputs entirely
+205
View File
@@ -0,0 +1,205 @@
# Security
## Access Control
- **Repository Access**: The action can only be triggered by users with write access to the repository. This is checked for issue, pull request, comment, and review events, and for `workflow_run` events, where both the workflow actor and the actor that started the upstream run are checked. `workflow_dispatch`, `repository_dispatch`, and `schedule` events are not checked separately — GitHub itself requires write access to dispatch a workflow, and scheduled runs have no external actor.
- **Bot User Control**: By default, GitHub Apps and bots cannot trigger this action for security reasons. Use the `allowed_bots` parameter to enable specific bots or all bots
- **⚠️ Allowed bots are not checked for repository permissions.** A bot that matches an entry does **not** need to be installed on your repository or have write access. On a **public repository**, external parties — including GitHub Apps created by anyone — may be able to trigger workflow events such as opening issues, commenting, or reviewing pull requests. If your workflow listens on those events and `allowed_bots` is set to `'*'`, any such App can invoke this action with a prompt it controls.
- Prefer an explicit list over `'*'`
- Only list App names you trust
- If you need `'*'`, scope workflow `permissions:` to the minimum required
- **⚠️ Non-Write User Access (RISKY)**: The `allowed_non_write_users` parameter allows bypassing the write permission requirement. **This is a significant security risk and should only be used for workflows with extremely limited permissions** (e.g., issue labeling workflows that only have `issues: write` permission). This feature:
- Only works when `github_token` is provided as input (not with GitHub App authentication)
- Accepts either a comma-separated list of specific usernames or `*` to allow all users
- **Should be used with extreme caution** as it bypasses the primary security mechanism of this action
- Is designed for automation workflows where user permissions are already restricted by the workflow's permission scope
- When set, Claude does a best-effort scrub of Anthropic, cloud, and GitHub Actions secrets from subprocess environments. On Linux runners with bubblewrap available, subprocesses additionally run with PID-namespace isolation. This reduces but does not eliminate prompt injection risk — keep workflow permissions minimal and validate all outputs. Set `CLAUDE_CODE_SUBPROCESS_ENV_SCRUB: 0` in your workflow or job `env:` block to opt out.
- Optionally set `CLAUDE_CODE_SCRIPT_CAPS` in your workflow `env:` block to limit how many times Claude can call specific scripts per run. Value is JSON: `{"script-name.sh": maxCalls}`. Example: `CLAUDE_CODE_SCRIPT_CAPS: '{"edit-issue-labels.sh":2}'` allows at most 2 calls to `edit-issue-labels.sh`. Useful for write-capable helper scripts.
- When using `allowed_non_write_users`, always pass `github_token: ${{ secrets.GITHUB_TOKEN }}`. The auto-generated workflow token is scoped to the job's declared permissions and expires when the job completes. **Do not use a personal access token** — a static token does not rotate between runs and could be partially or fully recovered over time via prompt injection. Restricting allowed tools via `claude_args` reduces the rate of recovery but may not eliminate the risk. We recommend restricting allowed tools (e.g. `claude_args: '--allowedTools "Bash(gh issue view:*)"'`) to the minimum required when using `allowed_non_write_users`.
- **Token Permissions**: The GitHub app receives only a short-lived token scoped specifically to the repository it's operating in
- **No Cross-Repository Access**: Each action invocation is limited to the repository where it was triggered
- **Limited Scope**: The token cannot access other repositories or perform actions beyond the configured permissions
## Using this action with `pull_request_target` or `workflow_run`
For `workflow_run` events, the action checks the repository access of the actor that started the upstream run (for example, the author of the fork pull request that triggered your CI workflow) in addition to the workflow actor. If that actor does not have write access, the action stops before running Claude. To run on `workflow_run` events downstream of pull requests from contributors without write access, add those users to `allowed_non_write_users` and pass `github_token: ${{ secrets.GITHUB_TOKEN }}` — see the notes on that input above and keep the workflow's permissions minimal.
`pull_request_target` and `workflow_run` execute with the **base repository's secrets**. If your workflow checks out the PR head (`ref: ${{ github.event.pull_request.head.sha }}` for `pull_request_target`, `ref: ${{ github.event.workflow_run.head_sha }}` for `workflow_run`) into `$GITHUB_WORKSPACE` before this action, the action and Claude run with that checkout as the working directory.
**Do not check out an untrusted ref into the workspace root before this action.** Use one of these patterns instead:
```yaml
# Preferred — check out the base ref (default).
- uses: actions/checkout@v6 # no `ref:` → base branch
- uses: anthropics/claude-code-action@v1
```
```yaml
# If you need the PR's files locally — check out the base ref at the workspace
# root (this action expects a git repo there), then check out the head ref into
# a subdirectory and pass it via --add-dir.
- uses: actions/checkout@v6 # no `ref:` → base branch at workspace root
- uses: actions/checkout@v6
with:
# For workflow_run use: ${{ github.event.workflow_run.head_sha }}
ref: ${{ github.event.pull_request.head.sha }}
path: pr-head
- uses: anthropics/claude-code-action@v1
with:
claude_args: "--add-dir pr-head"
```
This is general guidance for these event types — see [GitHub's documentation](https://securitylab.github.com/research/github-actions-preventing-pwn-requests/).
### Which files come from the base branch on pull requests
When the action runs against a pull request, it restores a fixed list of Claude configuration paths from the PR base branch before starting Claude: `.claude/`, `.mcp.json`, `.claude.json`, `.gitmodules`, `.ripgreprc`, `CLAUDE.md`, `CLAUDE.local.md`, and `.husky/`. Paths in that list that do not exist on the base branch are removed, and the PR-authored versions are kept under `.claude-pr/` for reference only.
Everything else in the working tree — including `package.json`, lockfiles, `Makefile`, `node_modules/`, and formatter/linter config files — stays at the PR head. If a hook, `apiKeyHelper`, or `statusLine` command in your base-branch `.claude/settings.json` runs a package-manager script (`bun run …`, `npm run …`, `yarn …`, `pnpm run …`), a `make` target, a repo-relative script, or a tool that loads executable project config, that command resolves through files the pull request supplies. Keep such commands self-contained: invoke the tool directly with a pinned version and pass its configuration on the command line (for example `bunx prettier@3.5.3 --no-config --write .` rather than `bun run format`).
Note that the runtime executing the tool also reads project config. `bunx <tool>` runs the tool's script under `node` when `node` is on `PATH` (as it is on GitHub-hosted runners); when only Bun is available, Bun executes the script itself and reads `bunfig.toml` from the checkout — including `preload` entries — which comes from the PR head. On such runners, make sure `node` is on `PATH` for the hook, and treat `bunfig.toml` and `.npmrc` in the checkout as PR-controlled runtime config.
### `claude-code-action` vs `claude-code-base-action`
`claude-code-base-action` is a lower-level building block that installs and runs Claude Code with the inputs you provide. It does not perform actor permission checks or restore project configuration from the base ref. If you need those behaviors, use this action (`claude-code-action`). See the [base-action README](../base-action/README.md#trust-model) for details.
## Pull Request Creation
In its default configuration, **Claude does not create pull requests automatically** when responding to `@claude` mentions. Instead:
- Claude commits code changes to a new branch
- Claude provides a **link to the GitHub PR creation page** in its response
- **The user must click the link and create the PR themselves**, ensuring human oversight before any code is proposed for merging
This design ensures that users retain full control over what pull requests are created and can review the changes before initiating the PR workflow.
## ⚠️ Prompt Injection Risks
**Beware of potential hidden markdown when tagging Claude on untrusted content.** External contributors may include hidden instructions through HTML comments, invisible characters, hidden attributes, or other techniques. The action sanitizes content by stripping HTML comments, invisible characters, markdown image alt text, hidden HTML attributes, and HTML entities, but new bypass techniques may emerge. We recommend reviewing the raw content of all input coming from external contributors before allowing Claude to process it.
On public repos, you can also use `include_comments_by_actor` to allowlist which users' comments are passed to Claude, reducing exposure to untrusted input. Use `exclude_comments_by_actor` to filter out noisy bot comments (e.g., `dependabot[bot]`, `renovate[bot]`). If an actor matches both lists, exclusion takes priority. See [Usage](./usage.md) for details.
## GitHub App Permissions
The [Claude Code GitHub app](https://github.com/apps/claude) requests the following permissions:
### Currently Used Permissions
- **Contents** (Read & Write): For reading repository files and creating branches
- **Pull Requests** (Read & Write): For reading PR data and creating/updating pull requests
- **Issues** (Read & Write): For reading issue data and updating issue comments
### Permissions for Future Features
The following permissions are requested but not yet actively used. These will enable planned features in future releases:
- **Discussions** (Read & Write): For interaction with GitHub Discussions
- **Actions** (Read): For accessing workflow run data and logs
- **Checks** (Read): For reading check run results
- **Workflows** (Read & Write): For triggering and managing GitHub Actions workflows
## Commit Signing
By default, commits made by Claude are unsigned. You can enable commit signing using one of two methods:
### Option 1: GitHub API Commit Signing (use_commit_signing)
This uses GitHub's API to create commits, which automatically signs them as verified from the GitHub App:
```yaml
- uses: anthropics/claude-code-action@main
with:
use_commit_signing: true
```
This is the simplest option and requires no additional setup. However, because it uses the GitHub API instead of git CLI, it cannot perform complex git operations like rebasing, cherry-picking, or interactive history manipulation.
### Option 2: SSH Signing Key (ssh_signing_key)
This uses an SSH key to sign commits via git CLI. Use this option when you need both signed commits AND standard git operations (rebasing, cherry-picking, etc.):
```yaml
- uses: anthropics/claude-code-action@main
with:
ssh_signing_key: ${{ secrets.SSH_SIGNING_KEY }}
bot_id: "YOUR_GITHUB_USER_ID"
bot_name: "YOUR_GITHUB_USERNAME"
```
Commits will show as verified and attributed to the GitHub account that owns the signing key.
**Setup steps:**
1. Generate an SSH key pair for signing:
```bash
ssh-keygen -t ed25519 -f ~/.ssh/signing_key -N "" -C "commit signing key"
```
2. Add the **public key** to your GitHub account:
- Go to GitHub → Settings → SSH and GPG keys
- Click "New SSH key"
- Select **Key type: Signing Key** (important)
- Paste the contents of `~/.ssh/signing_key.pub`
3. Add the **private key** to your repository secrets:
- Go to your repo → Settings → Secrets and variables → Actions
- Create a new secret named `SSH_SIGNING_KEY`
- Paste the contents of `~/.ssh/signing_key`
4. Get your GitHub user ID:
```bash
gh api users/YOUR_USERNAME --jq '.id'
```
5. Update your workflow with `bot_id` and `bot_name` matching the account where you added the signing key.
**Note:** If both `ssh_signing_key` and `use_commit_signing` are provided, `ssh_signing_key` takes precedence.
## ⚠️ Authentication Protection
**CRITICAL: Never hardcode your Anthropic API key or OAuth token in workflow files!**
Your authentication credentials must always be stored in GitHub secrets to prevent unauthorized access:
```yaml
# CORRECT ✅
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
# OR
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# NEVER DO THIS ❌
anthropic_api_key: "sk-ant-api03-..." # Exposed and vulnerable!
claude_code_oauth_token: "oauth_token_..." # Exposed and vulnerable!
```
## ⚠️ Full Output Security Warning
The `show_full_output` option is **disabled by default** for security reasons. When enabled, it outputs ALL Claude Code messages including:
- Full outputs from tool executions (e.g., `ps`, `env`, file reads)
- API responses that may contain tokens or credentials
- File contents that may include secrets
- Command outputs that may expose sensitive system information
**These logs are publicly visible in GitHub Actions for public repositories!**
### Automatic Enabling in Debug Mode
Full output is **automatically enabled** when GitHub Actions debug mode is active (when `ACTIONS_STEP_DEBUG` secret is set to `true`). This helps with debugging but carries the same security risks.
### When to Enable Full Output
Only enable `show_full_output: true` or GitHub Actions debug mode when:
- Working in a private repository with controlled access
- Debugging issues in a non-production environment
- You have verified no secrets will be exposed in the output
- You understand the security implications
### Recommended Practice
For debugging, prefer using `show_full_output: false` (the default) and rely on Claude Code's sanitized output, which shows only essential information like errors and completion status without exposing sensitive data.
+233
View File
@@ -0,0 +1,233 @@
# Setup Guide
## Manual Setup (Direct API)
**Requirements**: You must be a repository admin to complete these steps.
1. Install the Claude GitHub app to your repository: https://github.com/apps/claude
2. Add authentication to your repository secrets ([Learn how to use secrets in GitHub Actions](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions)):
- Either `ANTHROPIC_API_KEY` for API key authentication
- Or `CLAUDE_CODE_OAUTH_TOKEN` for OAuth token authentication (Pro and Max users can generate this by running `claude setup-token` locally)
3. Copy the workflow file from [`examples/claude.yml`](../examples/claude.yml) into your repository's `.github/workflows/`
> Don't want to store a static API key at all? See [Workload Identity Federation](#workload-identity-federation) below.
## Workload Identity Federation
Workload Identity Federation (WIF) lets the action authenticate to the Claude API by exchanging the workflow's GitHub Actions OIDC token for a short-lived Anthropic access token — no `ANTHROPIC_API_KEY` secret to create, store, or rotate.
### One-time setup in the Claude Console
You need admin access to your Anthropic organization (Console → **Settings → Workload identity**):
1. **Register an issuer** for GitHub Actions with issuer URL `https://token.actions.githubusercontent.com` (JWKS source: `discovery`).
2. **Create a service account** (Settings → Service accounts) and add it to the workspace it should act in. Note the `svac_...` ID.
3. **Create a federation rule** targeting that service account, matched to your repository's OIDC claims (for example a subject prefix of `repo:your-org/your-repo:`). Note the `fdrl_...` rule ID.
See the [Workload Identity Federation documentation](https://platform.claude.com/docs/en/manage-claude/workload-identity-federation) for full details.
### Workflow configuration
```yaml
jobs:
claude-response:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
issues: write
id-token: write # required: used to fetch the GitHub OIDC token
steps:
- uses: anthropics/claude-code-action@v1
with:
anthropic_federation_rule_id: fdrl_xxxxxxxxxxxx
anthropic_organization_id: 00000000-0000-0000-0000-000000000000
anthropic_service_account_id: svac_xxxxxxxxxxxx
# Optional when the federation rule targets a single workspace:
anthropic_workspace_id: wrkspc_xxxxxxxxxxxx
```
These values are identifiers, not credentials, so they can live directly in the workflow file (or in repository variables).
Notes:
- The workflow must grant `id-token: write` permission so the action can fetch a GitHub OIDC token. The default GitHub App authentication path already requires this permission.
- Do not set `anthropic_api_key` or `claude_code_oauth_token` alongside the federation inputs — a static credential takes precedence and federation will not be used.
- The GitHub OIDC token is requested with audience `https://api.anthropic.com` by default, so set the federation rule's expected audience to that value (or leave the rule's audience unmatched). Use `anthropic_oidc_audience` only if your rule expects a different audience.
- Inline comment classification (`classify_inline_comments`) currently requires `anthropic_api_key`; with federation it is skipped and unconfirmed inline comments are posted directly.
## Using a Custom GitHub App
If you prefer not to install the official Claude app, you can create your own GitHub App to use with this action. This gives you complete control over permissions and access.
**When you may want to use a custom GitHub App:**
- You need more restrictive permissions than the official app
- Organization policies prevent installing third-party apps
- You're using AWS Bedrock or Google Vertex AI
### Option 1: Quick Setup with App Manifest (Recommended)
The fastest way to create a custom GitHub App is using our pre-configured manifest. This ensures all permissions are correctly set up with a single click.
**Steps:**
1. **Create the app:**
**🚀 [Download the Quick Setup Tool](./create-app.html)** (Right-click → "Save Link As" or "Download Linked File")
After downloading, open `create-app.html` in your web browser:
- **For Personal Accounts:** Click the "Create App for Personal Account" button
- **For Organizations:** Enter your organization name and click "Create App for Organization"
The tool will automatically configure all required permissions and submit the manifest.
Alternatively, you can use the manifest file directly:
- Use the [`github-app-manifest.json`](../github-app-manifest.json) file from this repository
- Visit https://github.com/settings/apps/new (for personal) or your organization's app settings
- Look for the "Create from manifest" option and paste the JSON content
2. **Complete the creation flow:**
- GitHub will show you a preview of the app configuration
- Confirm the app name (you can customize it)
- Click "Create GitHub App"
- The app will be created with all required permissions automatically configured
3. **Generate and download a private key:**
- After creating the app, you'll be redirected to the app settings
- Scroll down to "Private keys"
- Click "Generate a private key"
- Download the `.pem` file (keep this secure!)
4. **Continue with installation** - Skip to step 3 in the manual setup below to install the app and configure your workflow.
### Option 2: Manual Setup
If you prefer to configure the app manually or need custom permissions:
1. **Create a new GitHub App:**
- Go to https://github.com/settings/apps (for personal apps) or your organization's settings
- Click "New GitHub App"
- Configure the app with these minimum permissions:
- **Repository permissions:**
- Contents: Read & Write
- Issues: Read & Write
- Pull requests: Read & Write
- **Account permissions:** None required
- Set "Where can this GitHub App be installed?" to your preference
- Create the app
2. **Generate and download a private key:**
- After creating the app, scroll down to "Private keys"
- Click "Generate a private key"
- Download the `.pem` file (keep this secure!)
3. **Install the app on your repository:**
- Go to the app's settings page
- Click "Install App"
- Select the repositories where you want to use Claude
4. **Add the app credentials to your repository secrets:**
- Go to your repository's Settings → Secrets and variables → Actions
- Add these secrets:
- `APP_ID`: Your GitHub App's ID (found in the app settings)
- `APP_PRIVATE_KEY`: The contents of the downloaded `.pem` file
5. **Update your workflow to use the custom app:**
```yaml
name: Claude with Custom App
on:
issue_comment:
types: [created]
# ... other triggers
jobs:
claude-response:
runs-on: ubuntu-latest
steps:
# Generate a token from your custom app
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
# Use Claude with your custom app's token
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
github_token: ${{ steps.app-token.outputs.token }}
# ... other configuration
```
**Important notes:**
- The custom app must have read/write permissions for Issues, Pull Requests, and Contents
- Your app's token will have the exact permissions you configured, nothing more
For more information on creating GitHub Apps, see the [GitHub documentation](https://docs.github.com/en/apps/creating-github-apps).
## Security Best Practices
**⚠️ IMPORTANT: Never commit API keys directly to your repository! Always use GitHub Actions secrets.**
To securely use your Anthropic API key:
1. Add your API key as a repository secret:
- Go to your repository's Settings
- Navigate to "Secrets and variables" → "Actions"
- Click "New repository secret"
- Name it `ANTHROPIC_API_KEY`
- Paste your API key as the value
2. Reference the secret in your workflow:
```yaml
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
```
**Never do this:**
```yaml
# ❌ WRONG - Exposes your API key
anthropic_api_key: "sk-ant-..."
```
**Always do this:**
```yaml
# ✅ CORRECT - Uses GitHub secrets
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
```
This applies to all sensitive values including API keys, access tokens, and credentials.
We also recommend that you always use short-lived tokens when possible
## Setting Up GitHub Secrets
1. Go to your repository's Settings
2. Click on "Secrets and variables" → "Actions"
3. Click "New repository secret"
4. For authentication, choose one:
- API Key: Name: `ANTHROPIC_API_KEY`, Value: Your Anthropic API key (starting with `sk-ant-`)
- OAuth Token: Name: `CLAUDE_CODE_OAUTH_TOKEN`, Value: Your Claude Code OAuth token (Pro and Max users can generate this by running `claude setup-token` locally)
5. Click "Add secret"
### Best Practices for Authentication
1. ✅ Always use `${{ secrets.ANTHROPIC_API_KEY }}` or `${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}` in workflows
2. ✅ Never commit API keys or tokens to version control
3. ✅ Regularly rotate your API keys and tokens
4. ✅ Use environment secrets for organization-wide access
5. ❌ Never share API keys or tokens in pull requests or issues
6. ❌ Avoid logging workflow variables that might contain keys
+599
View File
@@ -0,0 +1,599 @@
# Solutions & Use Cases
This guide provides complete, ready-to-use solutions for common automation scenarios with Claude Code Action. Each solution includes working examples, configuration details, and expected outcomes.
## 📋 Table of Contents
- [Automatic PR Code Review](#automatic-pr-code-review)
- [Review Only Specific File Paths](#review-only-specific-file-paths)
- [Review PRs from External Contributors](#review-prs-from-external-contributors)
- [Custom PR Review Checklist](#custom-pr-review-checklist)
- [Scheduled Repository Maintenance](#scheduled-repository-maintenance)
- [Issue Auto-Triage and Labeling](#issue-auto-triage-and-labeling)
- [Documentation Sync on API Changes](#documentation-sync-on-api-changes)
- [Security-Focused PR Reviews](#security-focused-pr-reviews)
---
## Automatic PR Code Review
**When to use:** Automatically review every PR opened or updated in your repository.
### Basic Example (No Tracking)
```yaml
name: Claude Auto Review
on:
pull_request:
types: [opened, synchronize]
jobs:
review:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
id-token: write
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 1
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
prompt: |
REPO: ${{ github.repository }}
PR NUMBER: ${{ github.event.pull_request.number }}
Please review this pull request with a focus on:
- Code quality and best practices
- Potential bugs or issues
- Security implications
- Performance considerations
Note: The PR branch is already checked out in the current working directory.
Use `gh pr comment` for top-level feedback.
Use `mcp__github_inline_comment__create_inline_comment` (with `confirmed: true`) to highlight specific code issues.
Only post GitHub comments - don't submit review text as messages.
claude_args: |
--allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*)"
```
**Key Configuration:**
- Triggers on `opened` and `synchronize` (new commits)
- Always include `REPO` and `PR NUMBER` for context
- Specify tools for commenting and reviewing
- PR branch is pre-checked out
**Expected Output:** Claude posts review comments directly to the PR with inline annotations where appropriate.
### Enhanced Example (With Progress Tracking)
Want visual progress tracking for PR reviews? Use `track_progress: true` to get tracking comments like in v0.x:
```yaml
name: Claude Auto Review with Tracking
on:
pull_request:
types: [opened, synchronize, ready_for_review, reopened]
jobs:
review:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
id-token: write
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 1
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
track_progress: true # ✨ Enables tracking comments
prompt: |
REPO: ${{ github.repository }}
PR NUMBER: ${{ github.event.pull_request.number }}
Please review this pull request with a focus on:
- Code quality and best practices
- Potential bugs or issues
- Security implications
- Performance considerations
Provide detailed feedback using inline comments for specific issues.
claude_args: |
--allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*)"
```
**Benefits of Progress Tracking:**
- **Visual Progress Indicators**: Shows "In progress" status with checkboxes
- **Preserves Full Context**: Automatically includes all PR details, comments, and attachments
- **Migration-Friendly**: Perfect for teams moving from v0.x who miss tracking comments
- **Works with Custom Prompts**: Your prompt becomes custom instructions while maintaining GitHub context
**Expected Output:**
1. Claude creates a tracking comment: "Claude Code is reviewing this pull request..."
2. Updates the comment with progress checkboxes as it works
3. Posts detailed review feedback with inline annotations
4. Updates tracking comment to "Completed" when done
---
## Review Only Specific File Paths
**When to use:** Review PRs only when specific critical files change.
**Complete Example:**
```yaml
name: Review Critical Files
on:
pull_request:
types: [opened, synchronize]
paths:
- "src/auth/**"
- "src/api/**"
- "config/security.yml"
jobs:
security-review:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
id-token: write
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 1
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
prompt: |
REPO: ${{ github.repository }}
PR NUMBER: ${{ github.event.pull_request.number }}
This PR modifies critical authentication or API files.
Please provide a security-focused review with emphasis on:
- Authentication and authorization flows
- Input validation and sanitization
- SQL injection or XSS vulnerabilities
- API security best practices
Note: The PR branch is already checked out.
Post detailed security findings as PR comments.
claude_args: |
--allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*)"
```
**Key Configuration:**
- `paths:` filter triggers only for specific file changes
- Custom prompt emphasizes security for sensitive areas
- Useful for compliance or security reviews
**Expected Output:** Security-focused review when critical files are modified.
---
## Review PRs from External Contributors
**When to use:** Apply stricter review criteria for external or new contributors.
**Complete Example:**
```yaml
name: External Contributor Review
on:
pull_request:
types: [opened, synchronize]
jobs:
external-review:
if: github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
id-token: write
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 1
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
prompt: |
REPO: ${{ github.repository }}
PR NUMBER: ${{ github.event.pull_request.number }}
CONTRIBUTOR: ${{ github.event.pull_request.user.login }}
This is a first-time contribution from @${{ github.event.pull_request.user.login }}.
Please provide a comprehensive review focusing on:
- Compliance with project coding standards
- Proper test coverage (unit and integration)
- Documentation for new features
- Potential breaking changes
- License header requirements
Be welcoming but thorough in your review. Use inline comments for code-specific feedback.
claude_args: |
--allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*),Bash(gh pr view:*)"
```
**Key Configuration:**
- `if:` condition targets specific contributor types
- Includes contributor username in context
- Emphasis on onboarding and standards
**Expected Output:** Detailed review helping new contributors understand project standards.
---
## Custom PR Review Checklist
**When to use:** Enforce specific review criteria for your team's workflow.
**Complete Example:**
```yaml
name: PR Review Checklist
on:
pull_request:
types: [opened, synchronize]
jobs:
checklist-review:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
id-token: write
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 1
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
prompt: |
REPO: ${{ github.repository }}
PR NUMBER: ${{ github.event.pull_request.number }}
Review this PR against our team checklist:
## Code Quality
- [ ] Code follows our style guide
- [ ] No commented-out code
- [ ] Meaningful variable names
- [ ] DRY principle followed
## Testing
- [ ] Unit tests for new functions
- [ ] Integration tests for new endpoints
- [ ] Edge cases covered
- [ ] Test coverage > 80%
## Documentation
- [ ] README updated if needed
- [ ] API docs updated
- [ ] Inline comments for complex logic
- [ ] CHANGELOG.md updated
## Security
- [ ] No hardcoded credentials
- [ ] Input validation implemented
- [ ] Proper error handling
- [ ] No sensitive data in logs
For each item, check if it's satisfied and comment on any that need attention.
Post a summary comment with checklist results.
claude_args: |
--allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*)"
```
**Key Configuration:**
- Structured checklist in prompt
- Systematic review approach
- Team-specific criteria
**Expected Output:** Systematic review with checklist results and specific feedback.
---
## Scheduled Repository Maintenance
**When to use:** Regular automated maintenance tasks.
**Complete Example:**
```yaml
name: Weekly Maintenance
on:
schedule:
- cron: "0 0 * * 0" # Every Sunday at midnight
workflow_dispatch: # Manual trigger option
jobs:
maintenance:
runs-on: ubuntu-latest
permissions:
contents: write
issues: write
pull-requests: write
id-token: write
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
prompt: |
REPO: ${{ github.repository }}
Perform weekly repository maintenance:
1. Check for outdated dependencies in package.json
2. Scan for security vulnerabilities using `npm audit`
3. Review open issues older than 90 days
4. Check for TODO comments in recent commits
5. Verify README.md examples still work
Create a single issue summarizing any findings.
If critical security issues are found, also comment on open PRs.
claude_args: |
--allowedTools "Read,Bash(npm:*),Bash(gh issue:*),Bash(git:*)"
```
**Key Configuration:**
- `schedule:` for automated runs
- `workflow_dispatch:` for manual triggering
- Comprehensive tool permissions for analysis
**Expected Output:** Weekly maintenance report as GitHub issue.
---
## Issue Auto-Triage and Labeling
**When to use:** Automatically categorize and prioritize new issues.
**Complete Example:**
```yaml
name: Issue Triage
on:
issues:
types: [opened]
jobs:
triage:
runs-on: ubuntu-latest
permissions:
issues: write
id-token: write
steps:
- uses: actions/checkout@v4
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
prompt: |
REPO: ${{ github.repository }}
ISSUE NUMBER: ${{ github.event.issue.number }}
TITLE: ${{ github.event.issue.title }}
BODY: ${{ github.event.issue.body }}
AUTHOR: ${{ github.event.issue.user.login }}
Analyze this new issue and:
1. Determine if it's a bug report, feature request, or question
2. Assess priority (critical, high, medium, low)
3. Suggest appropriate labels
4. Check if it duplicates existing issues
Use ./scripts/gh.sh to interact with GitHub:
- `./scripts/gh.sh issue view [number]` to view the issue
- `./scripts/gh.sh search issues "query"` to find similar issues
- `./scripts/gh.sh label list` to see available labels
Based on your analysis, add the appropriate labels using:
`./scripts/edit-issue-labels.sh --add-label "label1" --add-label "label2"`
(the issue number is read automatically from the workflow event)
If it appears to be a duplicate, post a comment mentioning the original issue.
claude_args: |
--allowedTools "Bash(./scripts/gh.sh:*),Bash(./scripts/edit-issue-labels.sh:*)"
```
**Key Configuration:**
- Triggered on new issues
- Issue context in prompt
- Label management capabilities
- Requires `scripts/gh.sh` and `scripts/edit-issue-labels.sh` in your repo (see this repo's `scripts/` directory for examples)
**Expected Output:** Automatically labeled and categorized issues.
---
## Documentation Sync on API Changes
**When to use:** Keep docs up-to-date when API code changes.
**Complete Example:**
```yaml
name: Sync API Documentation
on:
pull_request:
types: [opened, synchronize]
paths:
- "src/api/**/*.ts"
- "src/routes/**/*.ts"
jobs:
doc-sync:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
id-token: write
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.ref }}
fetch-depth: 0
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
prompt: |
REPO: ${{ github.repository }}
PR NUMBER: ${{ github.event.pull_request.number }}
This PR modifies API endpoints. Please:
1. Review the API changes in src/api and src/routes
2. Update API.md to document any new or changed endpoints
3. Ensure OpenAPI spec is updated if needed
4. Update example requests/responses
Use standard REST API documentation format.
Commit any documentation updates to this PR branch.
claude_args: |
--allowedTools "Read,Write,Edit,Bash(git:*)"
```
**Key Configuration:**
- Path-specific trigger
- Write permissions for doc updates
- Git tools for committing
**Expected Output:** API documentation automatically updated with code changes.
---
## Security-Focused PR Reviews
**When to use:** Deep security analysis for sensitive repositories.
**Complete Example:**
```yaml
name: Security Review
on:
pull_request:
types: [opened, synchronize]
jobs:
security:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
security-events: write
id-token: write
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 1
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
# Optional: Add track_progress: true for visual progress tracking during security reviews
# track_progress: true
prompt: |
REPO: ${{ github.repository }}
PR NUMBER: ${{ github.event.pull_request.number }}
Perform a comprehensive security review:
## OWASP Top 10 Analysis
- SQL Injection vulnerabilities
- Cross-Site Scripting (XSS)
- Broken Authentication
- Sensitive Data Exposure
- XML External Entities (XXE)
- Broken Access Control
- Security Misconfiguration
- Cross-Site Request Forgery (CSRF)
- Using Components with Known Vulnerabilities
- Insufficient Logging & Monitoring
## Additional Security Checks
- Hardcoded secrets or credentials
- Insecure cryptographic practices
- Unsafe deserialization
- Server-Side Request Forgery (SSRF)
- Race conditions or TOCTOU issues
Rate severity as: CRITICAL, HIGH, MEDIUM, LOW, or NONE.
Post detailed findings with recommendations.
claude_args: |
--allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*),Bash(gh pr diff:*)"
```
**Key Configuration:**
- Security-focused prompt structure
- OWASP alignment
- Severity rating system
**Expected Output:** Detailed security analysis with prioritized findings.
---
## Tips for All Solutions
### Always Include GitHub Context
```yaml
prompt: |
REPO: ${{ github.repository }}
PR NUMBER: ${{ github.event.pull_request.number }}
[Your specific instructions]
```
### Common Tool Permissions
- **PR Comments**: `Bash(gh pr comment:*)`
- **Inline Comments**: `mcp__github_inline_comment__create_inline_comment` — pass `confirmed: true` to post immediately. When omitted, the comment is buffered and classified after the session ends (real review comments post, test/probe comments are filtered). This prevents subagent test comments from reaching PRs. To disable classification entirely, set `classify_inline_comments: 'false'` on the action.
- **File Operations**: `Read,Write,Edit`
- **Git Operations**: `Bash(git:*)`
### Best Practices
- Be specific in your prompts
- Include expected output format
- Set clear success criteria
- Provide context about the repository
- Use inline comments for code-specific feedback
+307
View File
@@ -0,0 +1,307 @@
# Usage
Add a workflow file to your repository (e.g., `.github/workflows/claude.yml`):
```yaml
name: Claude Assistant
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
issues:
types: [opened, assigned, labeled]
pull_request_review:
types: [submitted]
jobs:
claude-response:
runs-on: ubuntu-latest
steps:
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
# Or use OAuth token instead:
# claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# Optional: provide a prompt for automation workflows
# prompt: "Review this PR for security issues"
# Optional: pass advanced arguments to Claude CLI
# claude_args: |
# --max-turns 10
# --model claude-4-0-sonnet-20250805
# Optional: add custom plugin marketplaces
# plugin_marketplaces: "https://github.com/user/marketplace1.git\nhttps://github.com/user/marketplace2.git"
# Optional: install Claude Code plugins
# plugins: "code-review@claude-code-plugins\nfeature-dev@claude-code-plugins"
# Optional: add custom trigger phrase (default: @claude)
# trigger_phrase: "/claude"
# Optional: add assignee trigger for issues
# assignee_trigger: "claude"
# Optional: add label trigger for issues
# label_trigger: "claude"
# Optional: grant additional permissions (requires corresponding GitHub token permissions)
# additional_permissions: |
# actions: read
# Optional: allow bot users to trigger the action
# allowed_bots: "dependabot[bot],renovate[bot]"
```
## Inputs
| Input | Description | Required | Default |
| -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | --------------------------- |
| `anthropic_api_key` | Anthropic API key (required for direct API, not needed for Bedrock/Vertex) | No\* | - |
| `claude_code_oauth_token` | Claude Code OAuth token (alternative to anthropic_api_key) | No\* | - |
| `anthropic_federation_rule_id` | Workload identity federation rule ID (`fdrl_...`). With `anthropic_organization_id`, authenticates via the workflow's GitHub OIDC token instead of a static API key. See [Setup Guide](./setup.md#workload-identity-federation) | No\* | - |
| `anthropic_organization_id` | Anthropic organization UUID for workload identity federation | No\* | - |
| `anthropic_service_account_id` | Service account ID (`svac_...`) the federated token acts as (optional) | No | - |
| `anthropic_workspace_id` | Workspace ID (`wrkspc_...`) for workload identity federation. Optional when the federation rule targets a single workspace | No | - |
| `anthropic_oidc_audience` | Audience requested on the GitHub OIDC token used for workload identity federation | No | `https://api.anthropic.com` |
| `prompt` | Instructions for Claude. Can be a direct prompt or custom template for automation workflows | No | - |
| `track_progress` | Force tag mode with tracking comments. Only works with specific PR/issue events. Preserves GitHub context | No | `false` |
| `include_fix_links` | Include 'Fix this' links in PR code review feedback that open Claude Code with context to fix the identified issue | No | `true` |
| `claude_args` | Additional [arguments to pass directly to Claude CLI](https://docs.claude.com/en/docs/claude-code/cli-reference#cli-flags) (e.g., `--max-turns 10 --model claude-4-0-sonnet-20250805`) | No | "" |
| `base_branch` | The base branch to use for creating new branches (e.g., 'main', 'develop') | No | - |
| `use_sticky_comment` | Use just one comment to deliver PR comments (only applies for pull_request event workflows) | No | `false` |
| `classify_inline_comments` | Buffer inline comments without `confirmed: true` and classify them (real review vs test/probe) via Haiku before posting after the session ends. Prevents subagent test comments. Set `'false'` to post all inline comments immediately | No | `true` |
| `github_token` | GitHub token for Claude to operate with. **Only include this if you're connecting a custom GitHub app of your own!** | No | - |
| `use_bedrock` | Use Amazon Bedrock with OIDC authentication instead of direct Anthropic API | No | `false` |
| `use_vertex` | Use Google Vertex AI with OIDC authentication instead of direct Anthropic API | No | `false` |
| `assignee_trigger` | The assignee username that triggers the action (e.g. @claude). Only used for issue assignment | No | - |
| `label_trigger` | The label name that triggers the action when applied to an issue (e.g. "claude") | No | - |
| `trigger_phrase` | The trigger phrase to look for in comments, issue/PR bodies, and issue titles | No | `@claude` |
| `branch_prefix` | The prefix to use for Claude branches (defaults to 'claude/', use 'claude-' for dash format) | No | `claude/` |
| `settings` | Claude Code settings as JSON string or path to settings JSON file | No | "" |
| `additional_permissions` | Additional permissions to enable. Currently supports 'actions: read' for viewing workflow results | No | "" |
| `use_commit_signing` | Enable commit signing using GitHub's API. Simple but cannot perform complex git operations like rebasing. See [Security](./security.md#commit-signing) | No | `false` |
| `ssh_signing_key` | SSH private key for signing commits. Enables signed commits with full git CLI support (rebasing, etc.). See [Security](./security.md#commit-signing) | No | "" |
| `bot_id` | GitHub user ID to use for git operations (defaults to Claude's bot ID). Required with `ssh_signing_key` for verified commits | No | `41898282` |
| `bot_name` | GitHub username to use for git operations (defaults to Claude's bot name). Required with `ssh_signing_key` for verified commits | No | `claude[bot]` |
| `include_comments_by_actor` | Comma-separated list of actor usernames to INCLUDE in comments. Supports the `*[bot]` wildcard to match all bot accounts. Empty (default) includes all actors | No | "" |
| `exclude_comments_by_actor` | Comma-separated list of actor usernames to EXCLUDE from comments. Supports the `*[bot]` wildcard to match all bot accounts. If an actor matches both lists, exclusion takes priority | No | "" |
| `allowed_bots` | Comma-separated list of allowed bot usernames, or '\*' to allow all bots. Empty string (default) allows no bots. **⚠️ On public repos with `'*'`, external Apps may be able to invoke this action.** See [Security](./security.md) | No | "" |
| `allowed_non_write_users` | **⚠️ RISKY**: Comma-separated list of usernames to allow without write permissions, or '\*' for all users. Only works with `github_token` input. See [Security](./security.md) | No | "" |
| `path_to_claude_code_executable` | Optional path to a custom Claude Code executable. Skips automatic installation. Useful for Nix, custom containers, or specialized environments | No | "" |
| `path_to_bun_executable` | Optional path to a custom Bun executable. Skips automatic Bun installation. Useful for Nix, custom containers, or specialized environments | No | "" |
| `plugin_marketplaces` | Newline-separated list of Claude Code plugin marketplace Git URLs to install from (e.g., see example in workflow above). Marketplaces are added before plugin installation | No | "" |
| `plugins` | Newline-separated list of Claude Code plugin names to install (e.g., see example in workflow above). Plugins are installed before Claude Code execution | No | "" |
### Deprecated Inputs
These inputs are deprecated and will be removed in a future version:
| Input | Description | Migration Path |
| --------------------- | -------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
| `mode` | **DEPRECATED**: Mode is now automatically detected based on workflow context | Remove this input; the action auto-detects the correct mode |
| `direct_prompt` | **DEPRECATED**: Use `prompt` instead | Replace with `prompt` |
| `override_prompt` | **DEPRECATED**: Use `prompt` with template variables or `claude_args` with `--system-prompt` | Use `prompt` for templates or `claude_args` for system prompts |
| `custom_instructions` | **DEPRECATED**: Use `claude_args` with `--append-system-prompt` or include in `prompt` | Move instructions to `prompt` or use `claude_args` |
| `max_turns` | **DEPRECATED**: Use `claude_args` with `--max-turns` instead | Use `claude_args: "--max-turns 5"` |
| `model` | **DEPRECATED**: Use `claude_args` with `--model` instead | Use `claude_args: "--model claude-4-0-sonnet-20250805"` |
| `fallback_model` | **DEPRECATED**: Use `claude_args` with fallback configuration | Configure fallback in `claude_args` or `settings` |
| `allowed_tools` | **DEPRECATED**: Use `claude_args` with `--allowedTools` instead | Use `claude_args: "--allowedTools Edit,Read,Write"` |
| `disallowed_tools` | **DEPRECATED**: Use `claude_args` with `--disallowedTools` instead | Use `claude_args: "--disallowedTools WebSearch"` |
| `mcp_config` | **DEPRECATED**: Use `claude_args` with `--mcp-config` instead | Use `claude_args: "--mcp-config '{...}'"` |
| `claude_env` | **DEPRECATED**: Use `settings` with env configuration | Configure environment in `settings` JSON |
\*Required when using direct Anthropic API (default and when not using Bedrock or Vertex)
> **Note**: This action is currently in beta. Features and APIs may change as we continue to improve the integration.
## Upgrading from v0.x?
For a comprehensive guide on migrating from v0.x to v1.0, including step-by-step instructions and examples, see our **[Migration Guide](./migration-guide.md)**.
### Quick Migration Examples
#### Interactive Workflows (with @claude mentions)
**Before (v0.x):**
```yaml
- uses: anthropics/claude-code-action@beta
with:
mode: "tag"
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
custom_instructions: "Focus on security"
max_turns: "10"
```
**After (v1.0):**
```yaml
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
claude_args: |
--max-turns 10
--append-system-prompt "Focus on security"
```
#### Automation Workflows
**Before (v0.x):**
```yaml
- uses: anthropics/claude-code-action@beta
with:
mode: "agent"
direct_prompt: "Update the API documentation"
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
model: "claude-4-0-sonnet-20250805"
allowed_tools: "Edit,Read,Write"
```
**After (v1.0):**
```yaml
- uses: anthropics/claude-code-action@v1
with:
prompt: |
REPO: ${{ github.repository }}
PR NUMBER: ${{ github.event.pull_request.number }}
Update the API documentation to reflect changes in this PR
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
claude_args: |
--model claude-4-0-sonnet-20250805
--allowedTools Edit,Read,Write
```
#### Custom Templates
**Before (v0.x):**
```yaml
- uses: anthropics/claude-code-action@beta
with:
override_prompt: |
Analyze PR #$PR_NUMBER for security issues.
Focus on: $CHANGED_FILES
```
**After (v1.0):**
```yaml
- uses: anthropics/claude-code-action@v1
with:
prompt: |
Analyze PR #${{ github.event.pull_request.number }} for security issues.
Focus on the changed files in this PR.
```
## Structured Outputs
Get validated JSON results from Claude that automatically become GitHub Action outputs. This enables building complex automation workflows where Claude analyzes data and subsequent steps use the results.
### Basic Example
```yaml
- name: Detect flaky tests
id: analyze
uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
prompt: |
Check the CI logs and determine if this is a flaky test.
Return: is_flaky (boolean), confidence (0-1), summary (string)
claude_args: |
--json-schema '{"type":"object","properties":{"is_flaky":{"type":"boolean"},"confidence":{"type":"number"},"summary":{"type":"string"}},"required":["is_flaky"]}'
- name: Retry if flaky
if: fromJSON(steps.analyze.outputs.structured_output).is_flaky == true
run: gh workflow run CI
```
### How It Works
1. **Define Schema**: Provide a JSON schema via `--json-schema` flag in `claude_args`
2. **Claude Executes**: Claude uses tools to complete your task
3. **Validated Output**: Result is validated against your schema
4. **JSON Output**: All fields are returned in a single `structured_output` JSON string
### Accessing Structured Outputs
All structured output fields are available in the `structured_output` output as a JSON string:
**In GitHub Actions expressions:**
```yaml
if: fromJSON(steps.analyze.outputs.structured_output).is_flaky == true
run: |
CONFIDENCE=${{ fromJSON(steps.analyze.outputs.structured_output).confidence }}
```
**In bash with jq:**
```yaml
- name: Process results
run: |
OUTPUT='${{ steps.analyze.outputs.structured_output }}'
IS_FLAKY=$(echo "$OUTPUT" | jq -r '.is_flaky')
SUMMARY=$(echo "$OUTPUT" | jq -r '.summary')
```
**Note**: Due to GitHub Actions limitations, composite actions cannot expose dynamic outputs. All fields are bundled in the single `structured_output` JSON string.
### Complete Example
See `examples/test-failure-analysis.yml` for a working example that:
- Detects flaky test failures
- Uses confidence thresholds in conditionals
- Auto-retries workflows
- Comments on PRs
### Documentation
For complete details on JSON Schema syntax and Agent SDK structured outputs:
https://docs.claude.com/en/docs/agent-sdk/structured-outputs
## Ways to Tag @claude
These examples show how to interact with Claude using comments in PRs and issues. By default, Claude will be triggered anytime you mention `@claude`, but you can customize the exact trigger phrase using the `trigger_phrase` input in the workflow.
Claude will see the full PR context, including any comments.
### Ask Questions
Add a comment to a PR or issue:
```
@claude What does this function do and how could we improve it?
```
Claude will analyze the code and provide a detailed explanation with suggestions.
### Request Fixes
Ask Claude to implement specific changes:
```
@claude Can you add error handling to this function?
```
### Code Review
Get a thorough review:
```
@claude Please review this PR and suggest improvements
```
Claude will analyze the changes and provide feedback.
### Fix Bugs from Screenshots
Upload a screenshot of a bug and ask Claude to fix it:
```
@claude Here's a screenshot of a bug I'm seeing [upload screenshot]. Can you fix it?
```
Claude can see and analyze images, making it easy to fix visual bugs or UI issues.
+39
View File
@@ -0,0 +1,39 @@
# Require human approvals on PRs that contain agent-authored commits.
#
# Both triggers run the workflow file from the BASE/DEFAULT branch, so a PR
# cannot edit this check to approve itself. (`pull_request_review` is not
# used because it runs from the merge ref, not the default branch; native
# Approve reviews are picked up on the next synchronize or `/approve`
# comment.)
#
# After adding this workflow, mark `agent-approval-check` as a required
# status check on your protected branches.
name: agent-approval-check
on:
pull_request_target:
types: [opened, synchronize, reopened, ready_for_review]
issue_comment:
types: [created]
permissions:
contents: read
pull-requests: write
statuses: write
jobs:
check:
# issue_comment also fires on plain issues; skip those early.
if: github.event_name != 'issue_comment' || github.event.issue.pull_request
runs-on: ubuntu-latest
steps:
- uses: anthropics/claude-code-action/agent-approval-check@main
with:
required_approvals: 2
agent_emails: noreply@anthropic.com
agent_logins: claude[bot],claude-code[bot]
# Uncomment to tune:
# excluded_approvers: dependabot[bot]
# exempt_path_prefixes: docs/
# protected_bases: main,release
+117
View File
@@ -0,0 +1,117 @@
name: Auto Fix CI Failures
# ⚠️ SECURITY NOTE
#
# This workflow checks out the PR branch and runs build/test commands
# (npm, bun, etc.) against it with elevated permissions (contents:write,
# id-token:write). This means code from the PR branch executes in a
# trusted context with access to secrets and the ability to push to the
# repository.
#
# Only use this workflow in repositories where everyone with write access
# is fully trusted with these permissions. Do not use this in repositories
# that accept contributions from untrusted or semi-trusted collaborators.
#
# The pull_requests[0] check below limits this to same-repo PRs (fork PRs
# are excluded), but anyone who can push a branch to this repository can
# control what code runs here.
on:
workflow_run:
workflows: ["CI"]
types:
- completed
permissions:
contents: write
pull-requests: write
actions: read
issues: write
id-token: write # Required for OIDC token exchange
jobs:
auto-fix:
if: |
github.event.workflow_run.conclusion == 'failure' &&
github.event.workflow_run.pull_requests[0] &&
!startsWith(github.event.workflow_run.head_branch, 'claude-auto-fix-ci-')
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
ref: ${{ github.event.workflow_run.head_branch }}
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup git identity
run: |
git config --global user.email "claude[bot]@users.noreply.github.com"
git config --global user.name "claude[bot]"
- name: Create fix branch
id: branch
env:
HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
RUN_ID: ${{ github.run_id }}
run: |
SAFE_BRANCH=$(printf '%s' "$HEAD_BRANCH" | tr -cd 'a-zA-Z0-9/_.-')
BRANCH_NAME="claude-auto-fix-ci-${SAFE_BRANCH}-${RUN_ID}"
git checkout -b "$BRANCH_NAME"
echo "branch_name=$BRANCH_NAME" >> "$GITHUB_OUTPUT"
- name: Get CI failure details
id: failure_details
uses: actions/github-script@v7
with:
script: |
const run = await github.rest.actions.getWorkflowRun({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: ${{ github.event.workflow_run.id }}
});
const jobs = await github.rest.actions.listJobsForWorkflowRun({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: ${{ github.event.workflow_run.id }}
});
const failedJobs = jobs.data.jobs.filter(job => job.conclusion === 'failure');
let errorLogs = [];
for (const job of failedJobs) {
const logs = await github.rest.actions.downloadJobLogsForWorkflowRun({
owner: context.repo.owner,
repo: context.repo.repo,
job_id: job.id
});
errorLogs.push({
jobName: job.name,
logs: logs.data
});
}
return {
runUrl: run.data.html_url,
failedJobs: failedJobs.map(j => j.name),
errorLogs: errorLogs
};
- name: Fix CI failures with Claude
id: claude
uses: anthropics/claude-code-action@v1
with:
prompt: |
/fix-ci
Failed CI Run: ${{ fromJSON(steps.failure_details.outputs.result).runUrl }}
Failed Jobs: ${{ join(fromJSON(steps.failure_details.outputs.result).failedJobs, ', ') }}
PR Number: ${{ github.event.workflow_run.pull_requests[0].number }}
Branch Name: ${{ steps.branch.outputs.branch_name }}
Base Branch: ${{ github.event.workflow_run.head_branch }}
Repository: ${{ github.repository }}
Error logs:
${{ toJSON(fromJSON(steps.failure_details.outputs.result).errorLogs) }}
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
claude_args: "--allowedTools 'Edit,MultiEdit,Write,Read,Glob,Grep,LS,Bash(git:*),Bash(bun:*),Bash(npm:*),Bash(npx:*),Bash(gh:*)'"

Some files were not shown because too many files have changed in this diff Show More