* 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.
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.
* 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>
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>
stripMarkdownImageAltText removed alt text from inline images
() 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>
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.
* 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>
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.
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>
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.
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
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>
* 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>
`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>