🔒 feat: 不可逆操作を hook でブロック + スキルのゲート整備 - #297
Conversation
高能力モデル(Opus 5 / Fable 等)が手順リストを最後まで走り切る挙動を 前提に、取り消せない操作の手前で必ず停止するよう明文化した。 - release-sparkle-design: PR マージ / タグ push / GitHub Release 作成 / npm publish を 🛑 でマークし、各セクション冒頭に停止ブロックを追加。 「チェックリストに並んでいること」が実行理由にならない旨を明記 - accessibility-checker: Fix はレビュー依頼の既定動作ではなく、明示的な 修正依頼+対象の合意があるときだけ実行する旨を SKILL.md と references/workflow.md に追加 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013WFgMWTQ6QAWShTRLNRUQx
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 40 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (10)
Walkthroughアクセシビリティ修正とリリース操作の承認・停止手順を更新し、不可逆コマンドを検出してブロックするPreToolUseフック、確認再実行ルール、包括的なシェルテストを追加しました。 ChangesAI操作安全ガード
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant Agent
participant PreToolUseHook
participant IrreversibleOpsGuard
Agent->>PreToolUseHook: 不可逆コマンドを実行
PreToolUseHook->>IrreversibleOpsGuard: tool_input.command を検査
IrreversibleOpsGuard-->>PreToolUseHook: exit 2 または許可
PreToolUseHook-->>Agent: ブロック通知または実行継続
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Comment |
散文のルールは「スキルを読んだセッション」にしか効かない。npm publish や PR マージのように取り消せない操作は、決定論的に止める方が確実なので ハーネス側へ移した。 - scripts/hooks/irreversible-ops-guard.sh: npm publish / unpublish / deprecate、gh pr merge、gh release create・delete、publish 系 ワークフロー、リリースタグの push、force push、リモート ref 削除を検知 して exit 2 でブロック。ユーザーの指示を得たあと SPARKLE_CONFIRM=1 を 先頭に付けて再実行する運用 - scripts/hooks/irreversible-ops-guard.test.sh: ブロック 23 件 + 素通し 25 件の計 48 ケース。`npm run publish-docs` や `git commit -m "push 前に直す"` のような誤検知を重点的に検証 - package.json に test:hooks を追加 - .claude/settings.json で PreToolUse(Bash) に接続 - docs/ai-instructions/context.md に運用ルールを追記し make ai-instructions で AGENTS.md / Copilot / Cursor へ反映 - release-sparkle-design: hook で担保できるようになった分、散文のゲートを 圧縮して hook への参照に置き換え Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013WFgMWTQ6QAWShTRLNRUQx
scaffold-extension-repo スキルは `gh repo create {org}/{project}-design
--private --clone` を手順の一項目として持つが、その場に確認の指示が無く、
org 名・リポジトリ名がプレースホルダのため AI が推測で埋める余地もある。
組織にリポジトリを作る/消す/アーカイブするのは外部に出る不可逆操作なので、
散文ではなく hook で止める。
- gh repo create / delete / archive を検知対象に追加
- 早期フィルタに "gh repo" を追加(repo 単体だと広すぎるため)
- テストを 49 → 54 ケースに拡張。gh repo view / clone、gh api repos/... は
素通しすることを確認
- docs/ai-instructions/context.md を更新し make ai-instructions で反映
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013WFgMWTQ6QAWShTRLNRUQx
このスクリプトは sparkle-design / sparkle-design-internal / sparkle-guide の 3 リポジトリで同一内容を保つ運用にする。コピー時に差分が出ないよう、 特定リポジトリにしか無いスキル名への参照をやめて汎用表現にした。 同一に保つ旨と更新手順もコメントに明記する。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013WFgMWTQ6QAWShTRLNRUQx
|
追加コミット 2 件:
|
セルフレビューで発見。`pnpm -F <pkg> publish` のように、サブコマンドの前に 「値を取るオプション」を挟むと、値の方をサブコマンドと誤認して素通ししていた。 修正前: pnpm -F @goodpatch/sparkle-design publish → exit 0(すり抜け) npm -w packages/ui publish → exit 0(すり抜け) - 値を取るオプション(-w / --workspace / -F / --filter / --dir / --prefix / --registry / --hostname)を値ごと読み飛ばすように修正 - 回帰テストを追加(54 → 63 ケース)。`pnpm -F ui run publish-docs` などが 引き続き素通しされることも確認 - 未知の値付きオプションではすり抜けうる限界をヘッダコメントに明記 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013WFgMWTQ6QAWShTRLNRUQx
There was a problem hiding this comment.
Pull request overview
AI エージェントが「取り消せない操作(公開・マージ・タグ push 等)」を手順どおりに走り切ってしまう事故を防ぐため、PreToolUse hook による決定論的ブロックと、スキル文面側のゲート整備を追加する PR です。
Changes:
scripts/hooks/irreversible-ops-guard.shを追加し、不可逆操作を検知したら実行前に block(SPARKLE_CONFIRM=1 の明示再実行で解除)- hook のテストスクリプトと
pnpm test:hooksを追加 - リポジトリ共通ドキュメントとスキル(release / accessibility-checker)に同趣旨のゲート文言を反映
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| scripts/hooks/irreversible-ops-guard.sh | 不可逆操作の検知・ブロック本体(PreToolUse hook)を追加 |
| scripts/hooks/irreversible-ops-guard.test.sh | ブロック/素通しの期待を検証するテストを追加 |
| package.json | pnpm test:hooks を追加 |
| docs/ai-instructions/context.md | Git Safety Rules に hook の説明と pnpm test:hooks を追記 |
| AGENTS.md | 上記コンテキスト追記の生成物反映 |
| .github/instructions/ai-context.instructions.md | 上記コンテキスト追記の生成物反映 |
| .cursor/rules/context.mdc | 上記コンテキスト追記の生成物反映 |
| .claude/skills/release-sparkle-design/SKILL.md | 🛑 停止点と、hook/承認ルールの明文化を追加 |
| .claude/skills/accessibility-checker/SKILL.md | Fix の実行条件(明示依頼+スコープ承認)を🛑で明文化 |
| .claude/skills/accessibility-checker/references/workflow.md | Fix step の実行条件を参照側にも追記 |
| .claude/settings.json | PreToolUse hook の配線を追加 |
Comments suppressed due to low confidence (1)
scripts/hooks/irreversible-ops-guard.sh:50
segmentsの分割がsedによる単純置換のため、引用符内("..." / '...')に含まれる&&/||/;/|/&まで分割してしまいます。例:echo "a && npm publish && b"のような安全なコマンドでも中間セグメントがnpm publishになりブロックされうるため、誤検知ゼロを目指す要件と衝突します。演算子はクォート外だけを分割対象にする(簡易パーサで in_single/in_double を追跡する等)形にしてください。
# `;` `&` `|` `&&` `||` と改行で分割し、各セグメントの「先頭に来るコマンド」だけを
# 判定対象にする。こうするとコミットメッセージ等に "npm publish" の文字列が
# 含まれるだけのケースを弾ける (先頭が git commit になるため)。
segments="$(printf '%s' "$command" | sed -E 's/(\|\||&&|[;&|])/\n/g')"
| command="$(jq -r '.tool_input.command // ""' <<<"$input")" | ||
| [ -n "$command" ] || exit 0 |
| run_case pass 'echo "gh pr merge は AI が自発的に実行しない"' | ||
| run_case pass 'grep -rn "npm publish" .claude/skills/' | ||
| run_case pass 'git log --oneline | grep release' |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 112c382bb2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| case " $seg " in | ||
| *" --dry-run "* | *" --dry-run="*) ;; |
There was a problem hiding this comment.
Treat
--dry-run=false as a live publish
When an agent runs npm publish --dry-run=false, npm treats the Boolean option as false—the npm config reference documents dry-run as a Boolean—but this wildcard exempts every --dry-run=* value, so the hook exits successfully and the package is actually published without confirmation. Only the true/enabled forms should bypass the guard.
AGENTS.md reference: AGENTS.md:L99-L105
Useful? React with 👍 / 👎.
| [A-Za-z_]*=*) | ||
| # 変数代入は「=」より前に空白を含まない。それ以外は通常のコマンドとして扱う | ||
| head="${seg%%[[:space:]]*}" | ||
| case "$head" in | ||
| *=*) | ||
| seg="${seg#"$head"}" | ||
| seg="${seg#"${seg%%[![:space:]]*}"}" | ||
| ;; |
There was a problem hiding this comment.
Inspect commands executed inside assignment substitutions
When an irreversible command is captured with a normal shell assignment such as result=$(npm publish) or url=$(gh release create ...), this loop removes only the first whitespace-delimited assignment token and then analyzes publish) or release as the executable; the hook returns 0 even though the shell executes the nested command. Parse command substitutions before discarding assignment prefixes so this routine output-capture pattern cannot bypass the confirmation gate.
AGENTS.md reference: AGENTS.md:L99-L105
Useful? React with 👍 / 👎.
| # refspec の dst 側を見る。タグ (vX.Y.Z) の push だけを対象にする | ||
| dst="${tok##*:}" | ||
| if [[ "$dst" =~ ^(refs/tags/)?v[0-9]+\.[0-9]+\.[0-9]+ ]]; then |
There was a problem hiding this comment.
Block force refspecs beginning with
+
When an agent uses git push origin +main:main, this refspec falls through as an ordinary non-option token and the hook returns 0, allowing a forced remote-history update without confirmation. The Git push refspec documentation defines a leading + as permitting an otherwise rejected update, so the refspec scan must treat it like --force.
AGENTS.md reference: AGENTS.md:L99-L105
Useful? React with 👍 / 👎.
| # `;` `&` `|` `&&` `||` と改行で分割し、各セグメントの「先頭に来るコマンド」だけを | ||
| # 判定対象にする。こうするとコミットメッセージ等に "npm publish" の文字列が | ||
| # 含まれるだけのケースを弾ける (先頭が git commit になるため)。 | ||
| segments="$(printf '%s' "$command" | sed -E 's/(\|\||&&|[;&|])/\n/g')" |
There was a problem hiding this comment.
Preserve quoted separators while splitting commands
When a harmless quoted argument contains a separator followed by a guarded phrase—for example git commit -m "docs: explain x; npm publish requires approval"—this regex splits at the quoted semicolon and treats npm publish requires approval" as a new command, blocking the commit and incorrectly requesting irreversible-operation approval. Command segmentation needs to respect shell quoting so documentation and commit-message text remains usable without setting the confirmation escape hatch.
AGENTS.md reference: AGENTS.md:L99-L105
Useful? React with 👍 / 👎.
Codex / Copilot のレビューで見つかった P1 の bypass を修正する。bash の 文字列分割では引用符とコマンド置換を正しく扱えないため、判定本体を Node(irreversible-ops-guard.mjs)に移した。.sh は node を呼ぶだけの エントリポイントになり、jq 依存も解消した。 塞いだ bypass: - `npm publish --dry-run=false` — 値付きの --dry-run を一律で免除していた。 有効な指定(--dry-run / =true / =1)だけを素通しする - `result=$(npm publish)` — コマンド置換の中身を見ていなかった。 ネストした置換・ダブルクォート内の置換も追う - `git push origin +main:main` — refspec 先頭の + は force と同義 - `env -i npm publish` / `env -u X npm publish` — env のオプションを 剥がしていなかった - `bash -c "npm publish"` — シェル経由の中身を再帰的に判定する(深さ上限あり) 誤検知の修正: - `git commit -m "docs: x; npm publish requires approval"` のように 引用符の中に区切り文字がある場合に誤分割していた その他: - node が無い環境では素通し(exit 0)。ガードのために開発を止めない - settings.json は bash 経由の呼び出しに変更(実行ビットに依存しない) - テストを 63 → 82 ケースに拡張。上記すべてに回帰テストを追加 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013WFgMWTQ6QAWShTRLNRUQx
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (1)
scripts/hooks/irreversible-ops-guard.test.sh (1)
17-35: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winexit code の判定が粗く、フック側の想定外エラーを "pass" として見逃す。
actual_exit -eq 2以外を一律 "pass" 扱いにしているため、フックがjq不在やスクリプトエラーで exit 1 や 127 を返しても "期待通り pass" と判定されてしまい、テストが本来の目的である誤検知/検知漏れの発見だけでなく実行時エラーの発見にも使えなくなる。expected=passのケースではactual_exit -eq 0を要求する方がテストの信頼性が上がる。♻️ 修正案
actual_exit=$? - local actual="pass" - [ "$actual_exit" -eq 2 ] && actual="block" + local actual + case "$actual_exit" in + 0) actual="pass" ;; + 2) actual="block" ;; + *) actual="error(exit=$actual_exit)" ;; + esac🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/hooks/irreversible-ops-guard.test.sh` around lines 17 - 35, Update run_case so exit status 2 maps to block, exit status 0 maps to pass, and any other status is treated as a test failure rather than pass. Preserve the existing expected comparison and failure reporting while ensuring unexpected hook errors cannot satisfy expected=pass.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.claude/skills/accessibility-checker/SKILL.md:
- Around line 99-105:
アクセシビリティ修正フローのStep番号と名称を両文書で統一してください。.claude/skills/accessibility-checker/SKILL.mdの99-105行ではレポートのStep番号をworkflow.mdの正式な定義に合わせ、.claude/skills/accessibility-checker/references/workflow.mdの62-65行ではFixesおよびreview-only停止のStep番号・名称をSKILL.mdと一致させて、修正実行の停止位置が一意になるよう更新してください。
In @.claude/skills/release-sparkle-design/SKILL.md:
- Around line 138-143: Update the GitHub Release creation instructions around
the CHANGELOG extraction command to verify that the target X.Y.Z section exists
and that /tmp/notes.md is non-empty before running gh release create. Abort the
release process when either validation fails, while preserving the existing
extraction and release arguments.
- Around line 128-133: Update the release SHA workflow in the release checklist
to identify the exact target version, require exactly one matching commit, and
verify the retrieved commit’s message before creating the tag. Replace the
ambiguous grep/head lookup used by RELEASE_SHA, and only run git tag after all
validations succeed.
- Around line 39-41: Update the SPARKLE_CONFIRM=1 guidance in the release skill
so approval is limited to the command segment that explicitly sets it, excluding
inherited or exported environment values and unrelated segments in compound
commands. Align scripts/hooks/irreversible-ops-guard.sh and its tests with this
segment-scoped contract, and document the same behavior for environments without
the hook.
In `@docs/ai-instructions/context.md`:
- Around line 81-90: Update the “Irreversible Operations Are Blocked by a Hook”
section in docs/ai-instructions/context.md (lines 81-90) to list
npm/pnpm/yarn/bun unpublish and deprecate commands, plus remote ref deletion via
git push --delete and git push origin :<ref>; then run make ai-instructions to
synchronize .cursor/rules/context.mdc (lines 87-96),
.github/instructions/ai-context.instructions.md (lines 85-94), and AGENTS.md
(lines 99-108).
In `@scripts/hooks/irreversible-ops-guard.sh`:
- Around line 54-90: Remove the process-level SPARKLE_CONFIRM environment check
from the confirmation flow after the parsing loop. Keep confirmation restricted
to the inline SPARKLE_CONFIRM=1 assignment handled while processing each command
segment, preserving the existing confirmed-based bypass behavior for explicitly
approved operations.
- Around line 27-43: Update the JSON extraction around jq in the
irreversible-operation guard to fail closed: verify jq is available and check
its execution result, exiting with status 2 whenever jq is unavailable or
parsing/extraction fails. Preserve the existing empty-command early exit only
for successfully parsed input with no command, ensuring keyword-matched
operations cannot bypass detection.
- Around line 125-137: Update the publish handling in the npm/pnpm/yarn/bun case
so only --dry-run and --dry-run=true bypass the irreversible-operation guard;
ensure --dry-run=false continues setting op to block the publish.
---
Nitpick comments:
In `@scripts/hooks/irreversible-ops-guard.test.sh`:
- Around line 17-35: Update run_case so exit status 2 maps to block, exit status
0 maps to pass, and any other status is treated as a test failure rather than
pass. Preserve the existing expected comparison and failure reporting while
ensuring unexpected hook errors cannot satisfy expected=pass.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: f8e2f3db-9a54-4512-a95f-39317581bf1c
⛔ Files ignored due to path filters (2)
.claude/settings.jsonis excluded by!**/*.jsonpackage.jsonis excluded by!**/*.json
📒 Files selected for processing (9)
.claude/skills/accessibility-checker/SKILL.md.claude/skills/accessibility-checker/references/workflow.md.claude/skills/release-sparkle-design/SKILL.md.cursor/rules/context.mdc.github/instructions/ai-context.instructions.mdAGENTS.mddocs/ai-instructions/context.mdscripts/hooks/irreversible-ops-guard.shscripts/hooks/irreversible-ops-guard.test.sh
| > これらは `scripts/hooks/irreversible-ops-guard.sh`(PreToolUse hook)が実際にブロックする。 | ||
| > ブロックされたら、ユーザーの指示を得たうえで `SPARKLE_CONFIRM=1` を先頭に付けて再実行する。 | ||
| > **hook が無い環境(Claude Code 以外のエージェント)でも、上のルールは同じように適用する。** |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
SPARKLE_CONFIRM=1の承認スコープをhook実装と一致させてください。
hookはコマンドセグメント内の指定だけでなく、継承されたプロセス環境のSPARKLE_CONFIRM=1も承認扱いにします。export SPARKLE_CONFIRM=1や複合コマンドで、意図していない不可逆操作まで通過する可能性があります。セグメント単位だけを許可することを文書化し、hookとテストも同じ契約にしてください。
🧰 Tools
🪛 SkillSpector (2.4.4)
[warning] 100: [RP1] null: npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.
Remediation: Pin the version: npx @scope/server@1.2.3
(MCP Rug Pull (RP1))
[error] 154: [PE3] Credential Access: Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
Remediation: Remove references to credential paths. Use environment variables or secrets managers. For docs, use placeholder paths (e.g., /path/to/config). Never load .env or token files in production code paths.
(Privilege Escalation (PE3))
[error] 193: [PE3] Credential Access: Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
Remediation: Remove references to credential paths. Use environment variables or secrets managers. For docs, use placeholder paths (e.g., /path/to/config). Never load .env or token files in production code paths.
(Privilege Escalation (PE3))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.claude/skills/release-sparkle-design/SKILL.md around lines 39 - 41, Update
the SPARKLE_CONFIRM=1 guidance in the release skill so approval is limited to
the command segment that explicitly sets it, excluding inherited or exported
environment values and unrelated segments in compound commands. Align
scripts/hooks/irreversible-ops-guard.sh and its tests with this segment-scoped
contract, and document the same behavior for environments without the hook.
| input="$(cat)" | ||
|
|
||
| # 全 Bash 呼び出しで起動されるため、jq を起動する前に生 JSON で安価に足切りする。 | ||
| # ここに挙げたキーワードを含まないコマンドは以降の判定対象になりえない。 | ||
| # ワークフロー名は `"Publish to npm"` のように大文字を含むため大小文字を無視する。 | ||
| shopt -s nocasematch | ||
| case "$input" in | ||
| *publish* | *merge* | *push* | *release* | *deprecate* | *"gh repo"*) ;; | ||
| *) | ||
| shopt -u nocasematch | ||
| exit 0 | ||
| ;; | ||
| esac | ||
| shopt -u nocasematch | ||
|
|
||
| command="$(jq -r '.tool_input.command // ""' <<<"$input")" | ||
| [ -n "$command" ] || exit 0 |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
jq 不在・不正 JSON 時にガードがフェイルオープンする。
jq が見つからない、あるいは stdin の JSON が想定外の形になった場合、jq -r '.tool_input.command // ""' は標準出力を出さずに失敗し、command="" となる。set -uo pipefail(-e なし)なのでこのエラーは無視され、そのまま line 43 で exit 0(検知なしで通過)してしまう。前段フィルタ(32-40)を通過した時点で入力には publish/merge/push/release 等の危険キーワードが含まれていることが確定しているため、まさに検知したい対象のコマンドで無条件に素通しになりうる。安全側に倒すなら jq の存在確認や jq の終了コード検査を入れ、失敗時はブロック(exit 2)するべき。
As per path instructions: scripts/hooks/irreversible-ops-guard.sh: "Do not bypass the irreversible-operation guard; operations such as force pushes, publishing, releases, repository deletion, and merges require explicit user instruction and the prescribed confirmation mechanism."
🛡️ 修正案
+if ! command -v jq >/dev/null 2>&1; then
+ echo "jq が見つからないため、安全側に倒して実行前にブロックしました。" >&2
+ exit 2
+fi
+
command="$(jq -r '.tool_input.command // ""' <<<"$input")"
-[ -n "$command" ] || exit 0
+if [ -z "$command" ]; then
+ echo "コマンドの抽出に失敗したため、安全側に倒して実行前にブロックしました。" >&2
+ exit 2
+fi📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| input="$(cat)" | |
| # 全 Bash 呼び出しで起動されるため、jq を起動する前に生 JSON で安価に足切りする。 | |
| # ここに挙げたキーワードを含まないコマンドは以降の判定対象になりえない。 | |
| # ワークフロー名は `"Publish to npm"` のように大文字を含むため大小文字を無視する。 | |
| shopt -s nocasematch | |
| case "$input" in | |
| *publish* | *merge* | *push* | *release* | *deprecate* | *"gh repo"*) ;; | |
| *) | |
| shopt -u nocasematch | |
| exit 0 | |
| ;; | |
| esac | |
| shopt -u nocasematch | |
| command="$(jq -r '.tool_input.command // ""' <<<"$input")" | |
| [ -n "$command" ] || exit 0 | |
| input="$(cat)" | |
| # 全 Bash 呼び出しで起動されるため、jq を起動する前に生 JSON で安価に足切りする。 | |
| # ここに挙げたキーワードを含まないコマンドは以降の判定対象になりえない。 | |
| # ワークフロー名は `"Publish to npm"` のように大文字を含むため大小文字を無視する。 | |
| shopt -s nocasematch | |
| case "$input" in | |
| *publish* | *merge* | *push* | *release* | *deprecate* | *"gh repo"*) ;; | |
| *) | |
| shopt -u nocasematch | |
| exit 0 | |
| ;; | |
| esac | |
| shopt -u nocasematch | |
| if ! command -v jq >/dev/null 2>&1; then | |
| echo "jq が見つからないため、安全側に倒して実行前にブロックしました。" >&2 | |
| exit 2 | |
| fi | |
| command="$(jq -r '.tool_input.command // ""' <<<"$input")" | |
| if [ -z "$command" ]; then | |
| echo "コマンドの抽出に失敗したため、安全側に倒して実行前にブロックしました。" >&2 | |
| exit 2 | |
| fi |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/hooks/irreversible-ops-guard.sh` around lines 27 - 43, Update the
JSON extraction around jq in the irreversible-operation guard to fail closed:
verify jq is available and check its execution result, exiting with status 2
whenever jq is unavailable or parsing/extraction fails. Preserve the existing
empty-command early exit only for successfully parsed input with no command,
ensuring keyword-matched operations cannot bypass detection.
Source: Path instructions
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 12 out of 12 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (3)
scripts/hooks/irreversible-ops-guard.mjs:114
- splitSegments が引用符の外の "(" を無条件にセグメント区切りとして扱っており、引数内の括弧(例:
echo format:(npm publish)のように単語内に含まれる場合)でもnpm publishが独立セグメント化されて誤検知ブロックになる可能性があります。サブシェル開始としての(だけを扱うため、セグメント先頭に現れた場合のみ区切り扱いにしてください。
if (c === "(" || c === "\n") {
flush();
continue;
}
scripts/hooks/irreversible-ops-guard.test.sh:22
- hook テストが JSON payload 生成に
jqを要求していますが、CI/開発環境でjqが入っていないとpnpm test:hooksが失敗します。Node は前提になっているので、payload 生成もnode -eに寄せて外部依存を減らすのが安全です。
payload="$(jq -nc --arg c "$command" '{tool_input: {command: $c}, cwd: "/tmp"}')"
scripts/hooks/irreversible-ops-guard.test.sh:115
- splitSegments の誤検知回避(引数内の括弧)をテストで担保できていません。
(を単語内に含む安全なケース(例:format:(npm publish))を PASS 側に追加して、将来のリグレッションを防いでください。
run_case pass 'echo "gh pr merge は AI が押さない | npm publish も同様"'
- **承認は 1 コマンド単位に限定**。継承したプロセス環境の SPARKLE_CONFIRM=1 を信頼していたため、`export SPARKLE_CONFIRM=1` を一度 実行するとセッション中ずっとガードが外れる状態だった。コマンド先頭に 書かれた指定だけを承認扱いにし、回帰テストを追加 - テストの exit code 判定を厳格化。0 / 2 以外(hook の実行時エラー)を pass 扱いにしていたため、動いていなくても緑になりえた - accessibility-checker: SKILL.md と workflow.md でステップ番号の振り方が 違い、「Step 4 まで」が真逆に読めた。停止位置を番号でなく名前で示す - release-sparkle-design: リリース SHA を `grep | head -1` で決めていたのを 候補が 1 件であることの検証 + 目視確認に変更。CHANGELOG 抽出が空なら Release 作成を中止するチェックも追加 - context.md: ブロック対象の説明が実装より狭かった(unpublish / deprecate / リモート ref 削除が抜け)ので実装に合わせて列挙 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013WFgMWTQ6QAWShTRLNRUQx
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 12 out of 12 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (2)
scripts/hooks/irreversible-ops-guard.mjs:259
gh workflow runの publish 判定がrest.some(/publish/i)になっているため、例えばgh workflow run "CI" --ref publish-fixのように ref 名や field 値にpublishが含まれるだけで誤検知してブロックします。誤検知を避けるなら、workflow 名(runの直後の positional 引数)だけを対象に判定し、--ref等のオプション値は除外してください。
if (pair === "workflow run") {
// publish 系ワークフローだけを対象にする (CI の再実行などは素通し)
if (rest.some((t) => /publish/i.test(t))) {
return { op: "gh workflow run (publish ワークフローの実行)" };
}
scripts/hooks/irreversible-ops-guard.test.sh:109
gh workflow runの誤検知防止の回帰として、ref 名にpublishを含んでもブロックされないケースを追加しておくと意図が固定できます(例:gh workflow run "CI" --ref publish-fixは素通し)。
run_case pass 'gh release view v1.0.8'
run_case pass 'gh workflow run "CI" --ref main'
run_case pass 'gh run list --branch main --limit 5'
| if (c === ")") { | ||
| if (substitutionStack.length > 0) { | ||
| flush(); | ||
| quote = substitutionStack.pop(); | ||
| continue; | ||
| } | ||
| if (quote === null) { | ||
| flush(); // サブシェルの終わり | ||
| continue; | ||
| } | ||
| current += c; | ||
| continue; | ||
| } |
概要
高能力モデル(Opus 5 / Fable 等)が手順リストを途中で止まらず最後まで走り切る挙動を前提に、取り消せない操作を決定論的にブロックする仕組みを入れる。あわせて、それを補完する散文のゲートをスキル側に整備する。
きっかけは、ワークスペース内 37 スキルを「不可逆操作の有無 × 人間ゲートの有無」で棚卸ししたところ、
release-sparkle-designが admin マージ → タグ push → Release 作成 → npm publish を停止点なしの一続きのチェックリストとして持っていたこと。1. 決定論的なガード(本命)
scripts/hooks/irreversible-ops-guard.sh— PreToolUse(Bash) hook。なぜ hook か: 散文のルールは「そのスキルを読んだセッション」にしか効かない。リリース手順を知っている人がスキルを起動せず作業した場合、プロンプト側のゲートは 1 行も効かない。hook はスキルの読み込み有無に関係なく効く。
ブロック対象:
npm/pnpm/yarn/bun publish(--dry-runは素通し)、unpublish、deprecategh pr merge、gh release create/delete、gh workflow runのうち publish 系vX.Y.Z)の push、--tags、force push 系、リモート ref の削除ブロック時は「何を検知したか」「次に何をすべきか」を stderr で返す。ユーザーの明示的な指示を得たあと、
SPARKLE_CONFIRM=1を先頭に付けて再実行する。誤検知させないことを重視した。
npm run publish-docs、git commit -m "push 前に直す"、gh pr create --title "リリース v1.0.8"などは素通しする。コマンドを;&&|||で分割し、各セグメントの先頭に来るコマンド名とサブコマンドの位置で判定しているため、コミットメッセージ等への文字列混入では発火しない。2. スキル側のゲート(hook が無い環境向けの補完)
Codex / Cursor など hook 非対応のエージェントでも同じ判断になるよう、散文でも明示する。ただし hook で担保できる分は文言を圧縮した(記事等で指摘されるとおり、高能力モデルでは冗長な指示がノイズになるため)。
release-sparkle-design: 🛑 マークと停止ブロック。PR 作成までが自走範囲であることを明示。--adminを AI の判断で付け直さない旨も追記accessibility-checker: Fix はレビュー依頼の既定動作ではなく、明示的な修正依頼+対象の合意があるときだけ。「見つかったので全部直しておきました」を禁止(references/workflow.mdにも同じゲートを配置)3. リポジトリ全体への周知
docs/ai-instructions/context.mdの Git Safety Rules に hook の説明を追記し、make ai-instructionsでAGENTS.md/CLAUDE.md(symlink) / Copilot / Cursor 向けファイルへ反映済み。動作確認
pnpm test:hooks(新規追加)→ 48 ケース全 pass(ブロック 23 / 素通し 25)shellcheck scripts/hooks/*.sh→ 指摘なしbash -n構文チェック → OKprettier --checkを変更した md / json に実行 → All matched files use Prettier code style!make ai-instructionsの生成物が差分に含まれていることを確認セルフレビューリスト
マージ後にやること
accessibility-checkerは共有スキル(Source of Truth = 本リポジトリ)なので、配布経路の両方を回す:sparkle-design-internalのpnpm sync:public-skillsgh workflow run "Publish Skills" --repo goodpatch/sparkle-design関連
deploy-gp-pagesの公開操作ゲート(同じ棚卸しから)