From a7c05c4cf02c24d625101e9c30295c5a837cd84a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Miko=C5=82ajczak?= Date: Mon, 25 May 2026 16:35:55 +0200 Subject: [PATCH 1/2] Add run-claude-action composite action Centralises the anthropics/claude-code-action SHA pin currently duplicated across Expensify/App, Expensify/Auth, and Expensify/Web-Expensify into a single composite. Client workflows consume this via Expensify/GitHub-Actions/.github/actions/run-claude-action@ so future pin bumps only need to land here. The composite stays thin: it sets the common defaults (model, display_report, allowed_non_write_users) and forwards structured_output. Caller-specific concerns (toolkit setup, eyes reaction, post-violations loop, schema extension) remain in the caller workflow. --- .github/actions/run-claude-action/README.md | 45 +++++++++++++++++++ .github/actions/run-claude-action/action.yml | 47 ++++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 .github/actions/run-claude-action/README.md create mode 100644 .github/actions/run-claude-action/action.yml diff --git a/.github/actions/run-claude-action/README.md b/.github/actions/run-claude-action/README.md new file mode 100644 index 0000000..15980e2 --- /dev/null +++ b/.github/actions/run-claude-action/README.md @@ -0,0 +1,45 @@ +# Run Claude Code Action + +Composite action that pins [`anthropics/claude-code-action`](https://github.com/anthropics/claude-code-action) to a single SHA shared across `Expensify/App`, `Expensify/Auth`, and `Expensify/Web-Expensify`. Bump the pin here once instead of editing every client `claude-review.yml`. + +The composite intentionally stays thin: it sets the common defaults (`display_report`, `allowed_non_write_users`, `--model`) and exposes the upstream `structured_output`. Caller-specific concerns - the `claude-review-toolkit` setup, the eyes-reaction lifecycle, the post-violations loop, and any schema extension - remain in the caller workflow. + +## Usage + +```yaml +- name: Setup Claude review toolkit + id: toolkit + uses: Expensify/GitHub-Actions/.github/actions/claude-review-toolkit@ + +- name: Run Claude Code + id: code-review + uses: Expensify/GitHub-Actions/.github/actions/run-claude-action@ + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + github_token: ${{ secrets.GITHUB_TOKEN }} + prompt: "/review-code-pr REPO: ${{ github.repository }} PR_NUMBER: ${{ github.event.pull_request.number }}" + claude_args: | + --allowedTools "Task,Glob,Grep,Read,Bash(gh pr diff:*),Bash(gh pr view:*)" --json-schema '${{ steps.toolkit.outputs.schema_json }}' +``` + +## Inputs + +| Name | Required | Default | Description | +| --- | --- | --- | --- | +| `anthropic_api_key` | yes | - | Anthropic API key. Pass via a caller secret. | +| `github_token` | yes | - | GitHub token used by `claude-code-action`. | +| `prompt` | yes | - | Prompt passed to Claude. Typically the slash command plus PR context (`REPO:`, `PR_NUMBER:`). | +| `model` | no | `claude-opus-4-6` | Model passed via `--model` in `claude_args`. | +| `claude_args` | no | `''` | Caller-specific extra args appended after `--model` (e.g. `--allowedTools`, `--json-schema`). Tools and schemas vary per caller so they live here, not in the composite. | +| `allowed_non_write_users` | no | `*` | Passthrough to `claude-code-action`. | +| `display_report` | no | `true` | Passthrough to `claude-code-action`. | + +## Outputs + +| Name | Description | +| --- | --- | +| `structured_output` | Passthrough of `claude-code-action.outputs.structured_output`. Callers parse this for the post-violations loop. | + +## Bumping the pin + +Update the `uses:` line in `action.yml` to the new SHA and version tag comment. Client workflows automatically pick up the new pin on the next merge that floats their `Expensify/GitHub-Actions/...@` reference forward. diff --git a/.github/actions/run-claude-action/action.yml b/.github/actions/run-claude-action/action.yml new file mode 100644 index 0000000..edebb9d --- /dev/null +++ b/.github/actions/run-claude-action/action.yml @@ -0,0 +1,47 @@ +name: 'Run Claude Code Action' +description: 'Pins anthropics/claude-code-action to a centralised SHA with the defaults used across Expensify client repos.' +inputs: + anthropic_api_key: + description: 'Anthropic API key. Pass via secret from the caller workflow.' + required: true + github_token: + description: 'GitHub token used by claude-code-action for repo interactions.' + required: true + prompt: + description: 'Prompt passed to Claude (typically a slash command plus PR context).' + required: true + model: + description: 'Model passed via --model in claude_args. Defaults to claude-opus-4-6.' + required: false + default: 'claude-opus-4-6' + claude_args: + description: 'Additional arguments appended to the default claude_args (e.g. --allowedTools, --json-schema). Caller-specific, varies per invocation.' + required: false + default: '' + allowed_non_write_users: + description: 'Passthrough to claude-code-action allowed_non_write_users. Defaults to "*".' + required: false + default: '*' + display_report: + description: 'Passthrough to claude-code-action display_report. Defaults to "true".' + required: false + default: 'true' +outputs: + structured_output: + description: 'Passthrough of the structured_output produced by claude-code-action.' + value: ${{ steps.claude.outputs.structured_output }} +runs: + using: 'composite' + steps: + - name: Run Claude Code + id: claude + uses: anthropics/claude-code-action@ba026a3e56b9f646ae3b1be02dd9c0812aa2f8ae # v1.0.86 + with: + anthropic_api_key: ${{ inputs.anthropic_api_key }} + github_token: ${{ inputs.github_token }} + prompt: ${{ inputs.prompt }} + allowed_non_write_users: ${{ inputs.allowed_non_write_users }} + display_report: ${{ inputs.display_report }} + claude_args: | + --model ${{ inputs.model }} + ${{ inputs.claude_args }} From 594081dca9e97739085087497164a0af7e3b1844 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Miko=C5=82ajczak?= Date: Mon, 25 May 2026 16:45:34 +0200 Subject: [PATCH 2/2] Drop model input from run-claude-action composite The composite forced --model claude-opus-4-6 by default, but Auth and Web-Expensify currently run on claude-sonnet-4-6 (the CLI's built-in default since they don't pass --model). Forcing Opus is a real behaviour change. Move model selection into claude_args - App passes --model claude-opus-4-6 explicitly; Auth and Web let the CLI default apply. Composite stays a pure SHA-pin wrapper. --- .github/actions/run-claude-action/README.md | 8 +++++--- .github/actions/run-claude-action/action.yml | 10 ++-------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/.github/actions/run-claude-action/README.md b/.github/actions/run-claude-action/README.md index 15980e2..581d1c7 100644 --- a/.github/actions/run-claude-action/README.md +++ b/.github/actions/run-claude-action/README.md @@ -2,7 +2,7 @@ Composite action that pins [`anthropics/claude-code-action`](https://github.com/anthropics/claude-code-action) to a single SHA shared across `Expensify/App`, `Expensify/Auth`, and `Expensify/Web-Expensify`. Bump the pin here once instead of editing every client `claude-review.yml`. -The composite intentionally stays thin: it sets the common defaults (`display_report`, `allowed_non_write_users`, `--model`) and exposes the upstream `structured_output`. Caller-specific concerns - the `claude-review-toolkit` setup, the eyes-reaction lifecycle, the post-violations loop, and any schema extension - remain in the caller workflow. +The composite intentionally stays thin: it sets the common defaults (`display_report`, `allowed_non_write_users`) and exposes the upstream `structured_output`. The model, allowed tools, and JSON schema vary per caller and live in `claude_args`. Caller-specific concerns - the `claude-review-toolkit` setup, the eyes-reaction lifecycle, the post-violations loop, and any schema extension - remain in the caller workflow. ## Usage @@ -19,9 +19,12 @@ The composite intentionally stays thin: it sets the common defaults (`display_re github_token: ${{ secrets.GITHUB_TOKEN }} prompt: "/review-code-pr REPO: ${{ github.repository }} PR_NUMBER: ${{ github.event.pull_request.number }}" claude_args: | + --model claude-opus-4-6 --allowedTools "Task,Glob,Grep,Read,Bash(gh pr diff:*),Bash(gh pr view:*)" --json-schema '${{ steps.toolkit.outputs.schema_json }}' ``` +If you omit `--model`, `claude-code-action` uses its own default (currently Sonnet). Pass `--model claude-opus-4-6` (or whichever model you want) explicitly to lock the choice. + ## Inputs | Name | Required | Default | Description | @@ -29,8 +32,7 @@ The composite intentionally stays thin: it sets the common defaults (`display_re | `anthropic_api_key` | yes | - | Anthropic API key. Pass via a caller secret. | | `github_token` | yes | - | GitHub token used by `claude-code-action`. | | `prompt` | yes | - | Prompt passed to Claude. Typically the slash command plus PR context (`REPO:`, `PR_NUMBER:`). | -| `model` | no | `claude-opus-4-6` | Model passed via `--model` in `claude_args`. | -| `claude_args` | no | `''` | Caller-specific extra args appended after `--model` (e.g. `--allowedTools`, `--json-schema`). Tools and schemas vary per caller so they live here, not in the composite. | +| `claude_args` | no | `''` | Forwarded verbatim to `claude-code-action.claude_args`. Put `--model`, `--allowedTools`, `--json-schema`, etc. here. Model selection lives caller-side because today's three client repos disagree (App: Opus; Auth/Web: CLI default). | | `allowed_non_write_users` | no | `*` | Passthrough to `claude-code-action`. | | `display_report` | no | `true` | Passthrough to `claude-code-action`. | diff --git a/.github/actions/run-claude-action/action.yml b/.github/actions/run-claude-action/action.yml index edebb9d..f213229 100644 --- a/.github/actions/run-claude-action/action.yml +++ b/.github/actions/run-claude-action/action.yml @@ -10,12 +10,8 @@ inputs: prompt: description: 'Prompt passed to Claude (typically a slash command plus PR context).' required: true - model: - description: 'Model passed via --model in claude_args. Defaults to claude-opus-4-6.' - required: false - default: 'claude-opus-4-6' claude_args: - description: 'Additional arguments appended to the default claude_args (e.g. --allowedTools, --json-schema). Caller-specific, varies per invocation.' + description: 'Arguments forwarded to claude-code-action.claude_args (e.g. --model, --allowedTools, --json-schema). Pass --model X if you want a specific model; otherwise the underlying CLI default is used.' required: false default: '' allowed_non_write_users: @@ -42,6 +38,4 @@ runs: prompt: ${{ inputs.prompt }} allowed_non_write_users: ${{ inputs.allowed_non_write_users }} display_report: ${{ inputs.display_report }} - claude_args: | - --model ${{ inputs.model }} - ${{ inputs.claude_args }} + claude_args: ${{ inputs.claude_args }}