Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 31 additions & 8 deletions .github/workflows/claude.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@
# Notes:
# - No concurrency group is set, so overlapping triggers on the same
# issue/PR run in parallel rather than queuing or cancelling each other.
# - claude-code-action picks its own push target from context (existing
# branch for an open PR, a new branch for issues/closed PRs) — independent
# of the ref the "Resolve checkout ref" step resolves.
#
# Security Notes:
# - The `claude` job has no actor/permission gate of its own — it relies on
# claude-code-action's built-in check that the triggering user has write
# access to this repo. A mention or review from anyone without write
# access (most fork PR authors included) fails the "Run Claude Code" step
# with "Action failed with error: User does not have write access on this
# repository" rather than being silently skipped.
# access to this repo, failing the "Run Claude Code" step loudly (not
# silently skipping) for anyone else.

name: Claude Code

Expand All @@ -41,29 +44,49 @@ jobs:
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
issues: write
id-token: write
id-token: write # Needed for Claude Code Action
# Checkout and claude-code-action both use the ocs-agent app token.

steps:
- name: Check for a real @claude mention
id: check
# Untrusted comment/issue text goes through env:, never interpolated
# directly into the script below, to avoid shell injection.
env:
COMMENT_BODY: ${{ github.event.comment.body }}
REVIEW_BODY: ${{ github.event.review.body }}
ISSUE_BODY: ${{ github.event.issue.body }}
ISSUE_TITLE: ${{ github.event.issue.title }}
run: |
text="$COMMENT_BODY"$'\n'"$REVIEW_BODY"$'\n'"$ISSUE_BODY"$'\n'"$ISSUE_TITLE"
if grep -Piq '(?<!\w)@claude(?![\w-])' <<< "$text"; then
echo "triggered=true" >> "$GITHUB_OUTPUT"
else
echo "triggered=false" >> "$GITHUB_OUTPUT"
fi

- name: Generate app token
id: app-token
if: steps.check.outputs.triggered == 'true'
Comment thread
lisa-tarbo marked this conversation as resolved.
uses: actions/create-github-app-token@v3
with:
app-id: ${{ vars.OCS_AGENT_APP_ID }}
private-key: ${{ secrets.OCS_AGENT_PRIVATE_KEY }}

- name: Checkout repository
if: steps.check.outputs.triggered == 'true'
uses: actions/checkout@v7
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.ref || github.ref }}
token: ${{ steps.app-token.outputs.token }}

- name: Install uv
if: steps.check.outputs.triggered == 'true'
uses: astral-sh/setup-uv@v7

- name: Install dependencies
if: steps.check.outputs.triggered == 'true'
run: uv sync --locked

- name: Run Claude Code
Expand Down