Repository navigation
Expand file tree
/
Copy pathaction.yml
More file actions
313 lines (285 loc) · 17.9 KB
/
Copy pathaction.yml
File metadata and controls
313 lines (285 loc) · 17.9 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
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
name: 'Claude Code Actions for YassLab team'
description: 'YassLab用にカスタマイズされた Claude AI コードレビュー'
author: 'YassLab Inc.'
branding:
icon: 'message-circle'
color: 'blue'
# Docs: https://github.com/anthropics/claude-code-action#readme
inputs:
additional_permissions:
description: 'Tell Claude that you are allowed to do given permissions (e.g. Can read CI results)'
required: false
default: |
actions: read
claude_code_oauth_token:
description: 'Claude Code API Token'
required: true
system_prompt:
description: 'Appended to the system prompt (style and default behavior)'
required: false
# v1 では prompt を渡すと automation モードになり @claude の指示が無視される。
# 渡さずに interactive モードで動かし、常に効かせたい規範だけをここに置く。
# これにより「@claude review this」なら定型レビュー、具体的な指示があれば
# その指示に従う、という使い分けが分岐なしで成立する。
#
# ⚠️ 記法の制約: この文字列は claude_args 内でダブルクォートに囲まれて
# shell-quote に渡される。次を使うと壊れる(Validate inputs で検査している)。
# - ダブルクォート
# - 行頭の # (トークナイズ前にコメントとして行ごと削除される)
# - $ とバッククォート(shell-quote が展開を試みる)
default: |
Always respond in Japanese, even if the instructions are written in English.
Use Japanese for all outputs, explanations, and code comments.
If the person who mentioned you gave a specific instruction, follow it.
If they did not, review the changes and give constructive feedback on:
- Best practices on Ruby, Jekyll, or Ruby on Rails.
- Critical bugs, security concerns, and performance considerations.
- General principles on YAGNI, UNIX Philosophy, and Premature Optimization.
Consider the project context and existing patterns when suggesting changes.
You can edit files locally to verify your reasoning, but you cannot commit or push.
Present any change you propose as a diff in your comment, not as a commit.
When you write a review, organize it with collapsed sections using <details> tags:
- 🚨 重大な問題 (セキュリティ、破壊的変更)
- ⚠️ 改善推奨 (ベストプラクティス違反)
- 💡 提案 (より良い実装方法)
- ✅ 良い実装 (参考になる箇所)
Include only relevant sections. Skip a category when it has no items.
model:
description: 'Specify model (default: Claude Opus 5)'
required: false
# 日付サフィックス付きスナップショット ID は retire 時に 404 になるため、
# 日付なしエイリアスを指定する
default: 'claude-opus-5'
#default: 'claude-opus-4-8' # 切り戻し先
allowed_tools:
description: 'Allow specific tools to exec'
required: false
# Ruby 系コマンド (bundle, rake, rspec, rubocop など) を許可する。
#
# ⚠️ bundle 系は Gemfile や Rakefile、spec を評価するため、実質的に任意コード実行になる。
# Action は fork PR のブランチも runner にチェックアウトするので、認証ステップで
# Issue/PR の作成者もメンバーであることを確認している(非メンバーの PR は実行しない)。
# ここに実行系コマンドを足す時は、その前提が崩れていないか必ず確認すること。
#
# ⚠️ Edit / MultiEdit / Write は書かない。
# Action 側は意図的にこれらを外し、--permission-mode acceptEdits によって
# 「workspace 内の編集のみ自動許可、外(~/.bashrc など)は拒否」にしている。
# ここに列挙すると runner 全体への書き込みを解禁してしまう。
# (anthropics/claude-code-action の src/modes/tag/index.ts のコメント参照)
#
# 注: Glob / Grep / LS / Read は Action 側が常に付与するため、ここには書かない。
default: 'Bash(bundle install:*),Bash(bundle update:*),Bash(bundle exec rake:*),Bash(bundle exec rails:*),Bash(bundle exec rspec:*),Bash(bundle exec rubocop:*),Task,TodoWrite,WebFetch,WebSearch'
disallowed_tools:
description: 'Disallow specific tools to exec'
required: false
# PR 内での自動コミットおよび権限取得コマンドを明示的に禁止する。
#
# 注: Bash(git push:*) は書いても効かない。Action は push を
# scripts/git-push.sh 経由で許可するため、このパターンにマッチしない。
# コミットを止めることで push まで止める形にしている。
#
# 注: mcp__github_file_ops__* は use_commit_signing: true にした時だけ
# Action 側が付与する。今は無効だが、有効にした瞬間に Bash パターンの
# deny をすり抜けてコミットできてしまうため、先に塞いでおく。
default: 'Bash(git commit:*),Bash(git add:*),Bash(git rm:*),Bash(rm -rf:*),Bash(sudo:*),mcp__github_file_ops__commit_files,mcp__github_file_ops__delete_files'
# v1 で使える主なオプション(必要になったら有効化する)
#
# trigger_phrase メンション語を変える (既定: @claude)
# assignee_trigger Issue の担当者アサインで起動する
# label_trigger ラベル付与で起動する
# base_branch ブランチ作成時の分岐元
# use_sticky_comment PR へのコメントを 1 つに集約する
# settings Claude Code の設定を JSON で渡す (環境変数など)
#
# 以下は v1 で claude_args の CLI フラグに移行した。上の claude_args に足す。
#
# max_turns --max-turns 10
# custom_instructions --append-system-prompt (この Action では system_prompt)
# mcp_config --mcp-config
#
# timeout_minutes は廃止。呼び出し元のジョブで timeout-minutes を指定する。
runs:
using: 'composite'
steps:
# claude_args は shell-quote でトークナイズされるため、壊れる文字が混ざると
# ツール指定がサイレントに無効化される。無言で権限が変わるより、明示的に落とす。
- name: Validate inputs
shell: bash
env:
SYSTEM_PROMPT: ${{ inputs.system_prompt }}
ALLOWED_TOOLS: ${{ inputs.allowed_tools }}
DISALLOWED_TOOLS: ${{ inputs.disallowed_tools }}
MODEL: ${{ inputs.model }}
run: |
set -euo pipefail
fail() { echo "::error::$1"; exit 1; }
for name in SYSTEM_PROMPT ALLOWED_TOOLS DISALLOWED_TOOLS MODEL; do
value="${!name}"
# 空だと nextArg が falsy になり、Action 側が積んだ値ごと null で
# 上書きされる (base-action/src/parse-sdk-options.ts)。ツールが
# 無言で全て消えるため、空は必ず落とす。
[ -n "$value" ] || fail "$name が空です"
case "$value" in
*'"'*) fail "$name にダブルクォートは使えません (claude_args の引用が壊れます)" ;;
*'\'*) fail "$name にバックスラッシュは使えません (末尾にあると閉じ引用符が無効化されます)" ;;
*'`'*) fail "$name にバッククォートは使えません" ;;
*'$'*) fail "$name に \$ は使えません (shell-quote が展開を試みます)" ;;
esac
done
# 単一行で渡す値に改行が混ざると、後続がフラグとして解釈されうる
for name in ALLOWED_TOOLS DISALLOWED_TOOLS MODEL; do
case "${!name}" in
*$'\n'*) fail "$name に改行は使えません" ;;
esac
done
# 行頭 # の行は、トークナイズ前にコメントとして行ごと削除される
if printf '%s\n' "$SYSTEM_PROMPT" | grep -qE '^[[:space:]]*#'; then
fail "SYSTEM_PROMPT の行頭に # は使えません (その行が無言で削除されます)"
fi
- name: Check if available members
id: auth
shell: bash
# ${{ }} を run 内に直接展開せず env 経由で渡す(スクリプトインジェクション対策)
env:
# ACTOR: このイベントを起こした本人。全 webhook payload に存在する。
# イベント別のフォールバックは使わない。たとえば pull_request_review で
# PR 作成者にフォールバックすると、非メンバーがメンバーの PR に
# レビューを付けるだけでメンバーとして通過できてしまう。
ACTOR: ${{ github.event.sender.login }}
# AUTHOR: Claude が読み込む内容(PR の差分・Issue 本文)の作成者。
# Action は fork PR のブランチも runner にチェックアウトするため、
# 作成者を検証しないと非メンバーのコードが実行され得る。
AUTHOR: ${{ github.event.pull_request.user.login || github.event.issue.user.login }}
# 数値 ID も payload に入っているため、API を叩かずに照合できる
ACTOR_ID: ${{ github.event.sender.id }}
AUTHOR_ID: ${{ github.event.pull_request.user.id || github.event.issue.user.id }}
# メンバー名簿。ここが唯一の認可の根拠。
#
# ⚠️ inputs にしていないのは、呼び出し元から上書きできないようにするため。
# inputs にすると、呼び出し元ワークフローを 1 つ書き換えるだけで
# 名簿ごと差し替えられてしまう。
#
# GitHub のユーザー名は、改名なら即座に、退会なら 90 日後に解放され、第三者が
# 取得できる。名前だけで照合すると、解放された名前を取得した第三者が通過する。
# 数値 ID は「不変で、別のユーザーを指すために再利用されない」ことが GitHub 公式に
# 明言されているため、同じ login でも ID が違えば別人と判定できる。
# https://docs.github.com/en/apps/creating-github-apps/setting-up-a-github-app/best-practices-for-creating-a-github-app
#
# 注: 人気リポジトリの namespace retirement は OWNER/REPO の組み合わせが対象で、
# ユーザー名自体は再取得できる。この仕組みは防御にならない。
#
# bot はここに載せない。載せると ACTOR としても通過してしまう
# (= bot が Claude を起動できてしまう)。
#
# 追加するとき: gh api users/<name> --jq .id で ID を調べて 1 行足す。
# 大文字小文字は GitHub の正式表記に合わせること(照合は完全一致)。
# 外すとき: 該当行を削除する。
MEMBER_IDS: |
himajin315:1221303
nanophate:6015450
yasulab:155807
hachi8833:4289625
rakuda-san-desu:41533420
nalabjp:1544172
nacchan99:173054484
run: |
set -euo pipefail
deny() {
echo "::warning::実行しません: $1"
echo "authorized=false" >> "$GITHUB_OUTPUT"
exit 0
}
# 空文字は必ず拒否する
[ -n "$ACTOR" ] || deny "イベントを起こしたユーザーを特定できません"
[ -n "$AUTHOR" ] || deny "Issue/PR の作成者を特定できません"
[ -n "$ACTOR_ID" ] || deny "イベントを起こしたユーザーの GitHub ID を特定できません"
[ -n "$AUTHOR_ID" ] || deny "Issue/PR の作成者の GitHub ID を特定できません"
# 名簿から期待する数値 ID を引く(未登録なら空を返す)
expected_id() {
printf '%s\n' "$MEMBER_IDS" | awk -F: -v u="$1" '
{
gsub(/^[ \t]+|[ \t]+$/, "", $1)
gsub(/^[ \t\r]+|[ \t\r]+$/, "", $2)
}
$1 == u { print $2; exit }
'
}
# ユーザー名リサイクル対策。GitHub の数値 ID は不変で再利用されないため、
# 同じ login でも ID が違えば別人。未登録も拒否する(fail-closed)。
# 「対応表への追加を忘れる」事故は、ここで拒否されることで即座に発覚する。
verify_id() { # $1=login $2=payload の ID $3=ラベル
want="$(expected_id "$1")"
[ -n "$want" ] || deny "$3 $1 が member_ids に未登録です。gh api users/$1 --jq .id で ID を調べ、action.yml の member_ids に追加してください"
[ "$want" = "$2" ] || deny "$3 $1 の GitHub ID が一致しません (対応表 $want / 実際 $2)。ユーザー名が別人に取得された可能性があります"
}
# ACTOR は名簿のみ。bot は名簿に載せていないため、ここで自動的に拒否される
# (Dependabot が Claude を起動する、という使い方は想定しない)。
verify_id "$ACTOR" "$ACTOR_ID" "イベントを起こした"
# AUTHOR は名簿に加えて Dependabot も許可する。
# 「メンバーが Dependabot の PR にレビューを依頼する」という運用があるため。
# Rails アプリでは依存更新を自動マージしておらず、人が確認している。
#
# - GitHub のユーザー名に [ ] は使えず、dependabot のアプリスラッグは
# GitHub が保有しているため、この名前は詐称できない
# - Dependabot は fork ではなく対象リポジトリ内にブランチを作るため、
# PR に含まれるのは write 権限保持者が管理する lockfile 更新のみ
# - 名前だけの許可にはせず、ID も照合する
#
# ただし「CI も bundle install しているから同じ」ではない。CI のジョブに
# CLAUDE_CODE_OAUTH_TOKEN は無いため、こちらで bundle install を走らせると
# CI には無かった露出が増える(gem のビルドスクリプトは任意コード実行)。
if [ "$AUTHOR" = "dependabot[bot]" ]; then
[ "$AUTHOR_ID" = "49699333" ] \
|| deny "dependabot[bot] の GitHub ID が一致しません (期待 49699333 / 実際 $AUTHOR_ID)"
else
verify_id "$AUTHOR" "$AUTHOR_ID" "対象の Issue/PR を作成した"
fi
# Claude が読むコメントをメンバーのものだけに絞るため、名前一覧を渡す。
# 非メンバーのコメントが混ざると、そこに書かれた文章を指示として
# 解釈してしまう余地が残る(プロンプトインジェクション)。
#
# このリストは allowlist として働き、載っていない投稿者のコメントは
# すべて落ちる。そのため次の 2 つを必ず含める。
# dependabot[bot] : 依存更新の説明をコメントに書くため
# claude[bot] : Claude 自身の過去のレビュー。外すと
# 「さっきの 2 番目の指摘を直して」が通じなくなる
#
# ユーザー名として不正な文字列は落とす($GITHUB_OUTPUT の行が壊れないように)。
# bot 名は [ ] を含みこの検査を通らないため、下で明示的に追記する。
COMMENT_AUTHORS="$(printf '%s\n' "$MEMBER_IDS" | awk -F: '
{ gsub(/^[ \t]+|[ \t\r]+$/, "", $1) }
$1 ~ /^[A-Za-z0-9-]+$/ { printf "%s%s", sep, $1; sep = "," }
')"
[ -n "$COMMENT_AUTHORS" ] || deny "メンバー名簿から有効な名前を取り出せません"
echo "members=${COMMENT_AUTHORS},dependabot[bot],claude[bot]" >> "$GITHUB_OUTPUT"
echo "::notice::承認: $ACTOR (対象の作成者: $AUTHOR)"
echo "authorized=true" >> "$GITHUB_OUTPUT"
# Docs: https://github.com/anthropics/claude-code-action#readme
- name: Claude Code Actions
if: steps.auth.outputs.authorized == 'true'
# 可変タグを使う。一度 SHA で固定したが、次の理由で戻した。
#
# - upstream は週に数回リリースするため、固定すると急速に古くなる。
# 既知の脆弱性が公開された版に留まり続ける方が危険
# - SHA + Dependabot の利点は「時間差」であって「レビュー」ではない。
# バンドルされた Action の差分を実際に読む人はいないため、
# PR を挟んでも検知にはつながらない
# - その時間差に週次の PR 対応を払う価値があるか、という判断で見送った
#
# 判断を見直すのは、upstream のリリース頻度が落ちた時か、
# Action の差分を実際にレビューする運用を始める時。
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ inputs.claude_code_oauth_token }}
additional_permissions: ${{ inputs.additional_permissions }}
# 非メンバーのコメントをコンテキストから除去する
include_comments_by_actor: ${{ steps.auth.outputs.members }}
# prompt は渡さない。渡すと automation モードになり @claude の指示が
# 無視される。渡さないことで interactive モードになり、メンションの
# 内容がそのまま指示になる(指示がなければ system_prompt に従って
# 定型レビューを行う)。
claude_args: |
--model "${{ inputs.model }}"
--allowedTools "${{ inputs.allowed_tools }}"
--disallowedTools "${{ inputs.disallowed_tools }}"
--append-system-prompt "${{ inputs.system_prompt }}"