`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>
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.
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>
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>
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>
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>
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
* 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.
* 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