-
Notifications
You must be signed in to change notification settings - Fork 5
210 lines (184 loc) · 9.83 KB
/
claude-mention.yml
File metadata and controls
210 lines (184 loc) · 9.83 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
name: Claude Mention Handler
# Responds to `@claude …` in PR comments and PR review (inline) comments.
# Claude enters the action's "agent mode": reads the commenter's request,
# uses the PR/issue as context, and can edit + commit + push. Gated to
# HarperFast org members/collaborators so external contributors can't
# trigger work against the repo.
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
concurrency:
group: claude-mention-${{ github.event.issue.number || github.event.pull_request.number }}
cancel-in-progress: false
jobs:
work:
# Belt-and-suspenders gate:
# 1. Comment must contain the trigger phrase.
# 2. Commenter must be HarperFast org OWNER / MEMBER or a repo
# COLLABORATOR (the action also performs its own write-access
# check on the actor as a fallback).
if: >-
contains(github.event.comment.body, '@claude') &&
contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'),
github.event.comment.author_association)
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
# Write access is intentional — mention mode is "do work", which
# means editing files, committing, and pushing (either to the PR's
# branch or a new claude/… branch for issue-originated asks).
contents: write
pull-requests: write
issues: write
id-token: write
steps:
- name: Checkout
# Default shallow fetch (depth 1). The agent can commit and push on a
# shallow clone; `git log` / `git blame` aren't reached for by the
# current prompt. Bump to a deeper fetch only if we see the agent
# blocked on history lookups.
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- name: Parse mention
# Real precision gate (the job-level `if:` is a cheap pre-filter).
# Enforces:
# 1. `@claude` must be the FIRST non-whitespace token (word-
# boundary after) — rules out `@claudette`, inline prose
# mentions ("saw @claude's fix"), and quoted replies
# (`> @claude ...`) where the reply is addressing a human.
# 2. Case-insensitive word-boundary `deep` anywhere in the body
# → escalate to Opus. Sonnet is the default.
id: mention
env:
BODY: ${{ github.event.comment.body }}
run: |
set -uo pipefail
if ! printf '%s' "$BODY" | grep -Pqz '\A\s*@claude\b'; then
echo "Comment does not start with @claude; skipping."
echo "proceed=false" >> "$GITHUB_OUTPUT"
exit 0
fi
if printf '%s' "$BODY" | grep -Piq '\bdeep\b'; then
echo "model=claude-opus-4-7" >> "$GITHUB_OUTPUT"
echo "Selected claude-opus-4-7 (deep requested)"
else
echo "model=claude-sonnet-4-6" >> "$GITHUB_OUTPUT"
echo "Selected claude-sonnet-4-6 (default)"
fi
echo "proceed=true" >> "$GITHUB_OUTPUT"
- name: Clone shared Harper skills
# Pinned to a SHA so agent behavior is reproducible across runs —
# updates require an explicit pin bump here.
if: steps.mention.outputs.proceed == 'true'
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
repository: HarperFast/skills
ref: d2db99bb37a6dde868cbc5ac81ca4146be8956fb # 1.3.0 (2026-04-16)
path: .harper-skills
- name: Setup Node.js
# Needed so the agent can run `npm ci` / `npm run <script>` when a
# specific mention actually requires it. We DON'T eagerly `npm ci`
# here — most mentions (explain, review, tiny edits) don't need
# deps, and ~35-60s install cost × every mention adds up. The
# prompt tells the agent to run `npm ci` itself before any script
# that needs dependencies.
if: steps.mention.outputs.proceed == 'true'
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: '22'
cache: 'npm'
- name: Claude (agent mode)
if: steps.mention.outputs.proceed == 'true'
id: claude-agent
uses: anthropics/claude-code-action@c3d45e8e941e1b2ad7b278c57482d9c5bf1f35b3 # v1.0.99
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
show_full_output: true
claude_args: |
--model ${{ steps.mention.outputs.model }}
--max-turns 48
# Tool allowlist is a security boundary — every entry is a
# potential RCE primitive if a prompt injection succeeds.
# Deliberately ABSENT:
# * `Bash(npx:*)` — would let an injected instruction run
# arbitrary published packages.
# Deliberately TIGHTENED:
# * `Bash(npm install)` (no-arg, not `Bash(npm install:*)`) —
# blocks `npm install @attacker/<name>`. BUT: the agent
# also has `Write`/`Edit` on package.json. A successful
# injection could add a malicious `postinstall` script
# and then invoke bare `npm install` to execute it, with
# GITHUB_TOKEN + the claude[bot] installation token
# reachable from the subprocess. This allowlist ALONE
# does not close that path — branch protection + the
# author_association gate are what actually bound blast
# radius. A future PR may add `ignore-scripts=true` via
# .npmrc and/or drop `Bash(npm install)` entirely,
# deferring installs to a separate CI job.
--allowedTools "Read,Write,Edit,Grep,Glob,mcp__github_inline_comment__create_inline_comment,Bash(gh pr view:*),Bash(gh pr diff:*),Bash(gh pr comment:*),Bash(gh pr checkout:*),Bash(gh pr create:*),Bash(gh issue view:*),Bash(gh issue comment:*),Bash(git:*),Bash(npm install),Bash(npm ci:*),Bash(npm run:*),Bash(npm test:*)"
# In agent mode the action can use the triggering comment as the
# prompt, but we inline it explicitly below to guarantee the agent
# always has PR/issue number, URL, and the commenter's exact
# request.
#
# TODO: revisit if a future claude-code-action release reliably
# forwards the triggering comment as the prompt.
prompt: |
You were invoked via an `@claude` mention on ${{ github.repository }}.
## Mention context
- Repo: ${{ github.repository }}
- Target number: #${{ github.event.issue.number || github.event.pull_request.number }}
- Target URL: ${{ github.event.issue.html_url || github.event.pull_request.html_url }}
- Target kind: ${{ github.event.issue.pull_request && 'pull request' || (github.event.pull_request && 'pull request' || 'issue') }}
- Commenter: @${{ github.event.comment.user.login }}
The commenter wrote (verbatim, including any multi-line content):
```
${{ github.event.comment.body }}
```
Start by reading the target so you have real context:
- For a PR: `gh pr view <N>` then `gh pr diff <N>` if you need
the changes.
- For an issue: `gh issue view <N>`.
Then act on the request. If the request is "review this PR",
follow the review discipline from HarperFast/ai-review-prompts
(see .github/workflows/claude-review.yml for the layered
scope this repo uses) — do NOT treat review as a code-edit task.
## Conventions
Read the repo's agent context files first (commonly
`CLAUDE.md`, `AGENTS.md`, or similar at the repo root). Their
conventions and gotchas apply to any code you write. Match
the repo's existing style rather than introducing a new one.
Harper-specific notes for code work:
- Linter is oxlint, not eslint. `npm run lint` runs oxlint.
- Primary storage is RocksDB (LMDB is the alternate via
`HARPER_STORAGE_ENGINE=lmdb`).
- TypeStrip-compatible (`erasableSyntaxOnly`). Don't use
TypeScript features that break typestrip.
- `dependencies.md` documents all npm packages; if you add
a runtime dep, add an entry there.
## Before committing
Scale validation to the kind of change you made:
- Doc-only change (only `*.md` / `README.md` / `CLAUDE.md` /
`AGENTS.md` / `dependencies.md`, or `package.json`
keyword/description edits): run `npm run format:check` and
`npm run lint`. Do NOT run `npm run build` /
`npm run test:unit` — they are not affected and waste turns.
- Code change that affects behavior: run `npm ci` first
(deps aren't pre-installed in this workflow), then
`npm run build && npm run lint && npm run format:check && npm run test:unit`.
Fix anything that fails. Integration tests
(`npm run test:integration`) are slow; run them only if
the change plausibly affects integration behavior.
When in doubt, err toward the fuller validation.
## Output
- Scope your changes to exactly what the mention asked for.
Don't refactor unrelated code.
- Commit with a descriptive message referencing the
issue/PR.
- If the request is ambiguous or you have to make a
judgment call that changes public API or semantics, post
a comment on the PR/issue explaining your interpretation
and stop — do NOT push speculative changes.
- Do NOT use REQUEST_CHANGES or APPROVE on PRs. Post
comments or push commits only.