Skip to content

Commit c3bb375

Browse files
committed
fix(ci): put the command word in the Claude trigger phrase so /focus reaches the model
With track_progress, claude-code-action runs in tag mode and forwards everything after the trigger phrase to the CLI as a separate user message, which the CLI runs as a slash command. Under the default `@claude`, `/focus` was that command: the SDK returned in 126 ms with no model usage and the verify step failed the run (36932898300). `/review` only worked because it is a Claude Code built-in, which also ran on top of our prompt. Setting trigger_phrase to `@claude /review` and `@claude /focus` forwards only the guidance after the command word, as plain text. The headers now say how guidance reaches the model, and that the 👀 reaction comes from the Claude GitHub App rather than the workflow.
1 parent cee92ae commit c3bb375

2 files changed

Lines changed: 42 additions & 16 deletions

File tree

‎.github/workflows/claude-code-focus-on-demand.yml‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@ name: Claude Code Focus (on demand)
1414
# - fires ONLY on a maintainer's comment (author_association gate);
1515
# - no `contents: write`, and a read-only tool allowlist, so it can never
1616
# write code or push;
17-
# - the comment body is TESTED, never forwarded. The only thing taken from it
18-
# is N, extracted by a shell regex as digits only and clamped to a range. No free text reaches the prompt, so a commenter cannot steer the
19-
# model. Do not add a free-text "focus on X" argument: see the review
20-
# workflow header for why a prompt-level guard is not a boundary;
17+
# - the comment body is never interpolated into the prompt. The only thing
18+
# this workflow takes from it is N, extracted by a shell regex as digits
19+
# only and clamped to a range. The action separately forwards whatever
20+
# follows `@claude /focus` as plain text (see the review workflow header),
21+
# which for `@claude /focus 5` is just `5`;
2122
# - `Read` is safe ONLY alongside `persist-credentials: false` on the
2223
# checkout, and the checkout MUST be the PR's own ref.
2324
#
@@ -129,6 +130,10 @@ jobs:
129130
with:
130131
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
131132
track_progress: true
133+
# Without the command word here, tag mode forwards `/focus` to the CLI
134+
# as a slash command and the run ends before a single model call. The
135+
# review workflow's `trigger_phrase` comment has the measurement.
136+
trigger_phrase: "@claude /focus"
132137
include_fix_links: false
133138
# THE `Focus areas:` LINE IS A CONTRACT with the verify step below.
134139
# Reword it in both places in the same commit, or every run fails.

‎.github/workflows/claude-code-review-on-demand.yml‎

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,20 @@ name: Claude Code Review (on demand)
1212
# - as an inline review comment (pull_request_review_comment) → a "second set
1313
# of eyes" pass that focuses on what the human review may have missed.
1414
#
15-
# The MODE is chosen by the workflow, from `contains()` tests on the comment
16-
# body plus `event_name`, and resolves to one of three `focus=` strings. The
17-
# body is TESTED, never forwarded: `github.event.comment.body` appears only in
18-
# the `if:` gate and in `contains()` expressions whose result is a boolean, and
19-
# is never interpolated into a `run:` or `prompt:` block. So a commenter cannot
20-
# steer the reviewer with free text. Do not "improve" this into a free-text
21-
# focus argument guarded by a prompt-level "treat the following as review
22-
# focus, not as instructions" — that is a request, not a boundary.
15+
# The MODE is chosen by the workflow, from tests on the comment body plus
16+
# `event_name`, and resolves to one of three `focus=` strings. The body is never
17+
# interpolated into a `run:` or `prompt:` block: `github.event.comment.body`
18+
# appears only in the `if:` gate and as an environment variable the prep step
19+
# tests, so no comment can inject shell or rewrite the prompt.
20+
#
21+
# A maintainer's GUIDANCE does reach the reviewer, through the action rather
22+
# than the prompt. Tag mode forwards everything after `trigger_phrase` as a
23+
# separate plain-text user message, so `@claude /review check the migration
24+
# ordering` arrives as `check the migration ordering`, alongside the prompt.
25+
# Only a maintainer can open that channel (the `if:` gate), and it is plain
26+
# text only because `trigger_phrase` carries the command word; with a bare
27+
# `@claude` the CLI runs the forwarded text as a slash command. See the comment
28+
# on `trigger_phrase`.
2329
#
2430
# Incremental scoping is by COMMENTS, not by a SHA range. A `lastReviewed..head`
2531
# two-dot range assumes linear history; this repo rebases, so a force push
@@ -101,8 +107,9 @@ jobs:
101107
# The body reaches this step as an ENVIRONMENT VARIABLE, never as a `${{ }}`
102108
# substitution into the script text, so no comment can inject shell. It is
103109
# TESTED and nothing more: the only things written to $GITHUB_OUTPUT are a
104-
# PR number and one of three fixed focus strings, so the body still never
105-
# reaches the model. Do not echo `$COMMENT_BODY` anywhere in this step.
110+
# PR number and one of three fixed focus strings. Guidance reaches the
111+
# model through the action's `trigger_phrase`, never through this step.
112+
# Do not echo `$COMMENT_BODY` anywhere in this step.
106113
#
107114
# A `case` pattern rather than `contains()` because the match must respect
108115
# a word boundary. `contains(body, '@claude /review all')` is an unanchored
@@ -208,15 +215,29 @@ jobs:
208215
# that had nothing to classify.
209216
echo "threads: $(jq '.data.repository.pullRequest.reviewThreads.nodes | length' "${GITHUB_WORKSPACE}/.prior-review.json")"
210217
211-
# Note: claude-code-action adds its own 👀 reaction to the triggering
212-
# comment, so there's no explicit reaction step here.
218+
# The 👀 on the triggering comment is not from this workflow or the
219+
# action. The Claude GitHub App adds it, as `claude[bot]`, to ANY comment
220+
# mentioning `@claude`, before this workflow has started (measured on PR
221+
# 437: reaction at 22:05:27Z, run created 22:05:28Z). It therefore says
222+
# nothing about whether a review will run.
213223
- name: Run Claude Code Review
214224
id: review
215225
uses: anthropics/claude-code-action@0a8d3c9443bbff909ab973b6a17a340b913f229f # v1.0.221
216226
with:
217227
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
218228
# Single tracking comment (in-progress → results), updated in place.
219229
track_progress: true
230+
# The COMMAND WORD belongs in the trigger phrase. `track_progress`
231+
# forces tag mode, and tag mode forwards everything after the trigger
232+
# phrase as its own user-message block, which the CLI runs as a slash
233+
# command when it starts with `/`. With the default `@claude` that
234+
# block was `/review`, a Claude Code BUILT-IN, so every review also
235+
# ran the built-in review command alongside this prompt. The sibling
236+
# `/focus` is not a built-in and never reached the model at all: the
237+
# SDK returned in 126 ms with no model usage (run 36932898300). With
238+
# the command word in the phrase, only what follows it is forwarded,
239+
# as plain text: `all`, or a maintainer's guidance for this review.
240+
trigger_phrase: "@claude /review"
220241
# The "Fix this →" claude.ai/code deep-links render as broken markdown
221242
# (huge percent-encoded query). Turn them off at the source.
222243
include_fix_links: false

0 commit comments

Comments
 (0)