Skip to content

🔒 feat: 不可逆操作を hook でブロック + スキルのゲート整備 - #297

Merged
touyou merged 7 commits into
mainfrom
harden/skill-irreversible-ops-guard
Jul 29, 2026
Merged

🔒 feat: 不可逆操作を hook でブロック + スキルのゲート整備#297
touyou merged 7 commits into
mainfrom
harden/skill-irreversible-ops-guard

Conversation

@touyou

@touyou touyou commented Jul 29, 2026

Copy link
Copy Markdown
Member

概要

高能力モデル(Opus 5 / Fable 等)が手順リストを途中で止まらず最後まで走り切る挙動を前提に、取り消せない操作を決定論的にブロックする仕組みを入れる。あわせて、それを補完する散文のゲートをスキル側に整備する。

きっかけは、ワークスペース内 37 スキルを「不可逆操作の有無 × 人間ゲートの有無」で棚卸ししたところ、release-sparkle-designadmin マージ → タグ push → Release 作成 → npm publish を停止点なしの一続きのチェックリストとして持っていたこと。

1. 決定論的なガード(本命)

scripts/hooks/irreversible-ops-guard.sh — PreToolUse(Bash) hook。

なぜ hook か: 散文のルールは「そのスキルを読んだセッション」にしか効かない。リリース手順を知っている人がスキルを起動せず作業した場合、プロンプト側のゲートは 1 行も効かない。hook はスキルの読み込み有無に関係なく効く。

ブロック対象:

分類 検知するもの
npm npm/pnpm/yarn/bun publish--dry-run は素通し)、unpublishdeprecate
GitHub gh pr mergegh release create / deletegh workflow run のうち publish 系
git リリースタグ(vX.Y.Z)の push、--tags、force push 系、リモート ref の削除

ブロック時は「何を検知したか」「次に何をすべきか」を stderr で返す。ユーザーの明示的な指示を得たあと、SPARKLE_CONFIRM=1 を先頭に付けて再実行する。

$ gh workflow run "Publish to npm" --ref v1.0.8
取り消せない操作を検知したため、実行前にブロックしました。

  検知した操作: gh workflow run (publish ワークフローの実行)
  ...
  3. 指示を得たら、コマンドの先頭に SPARKLE_CONFIRM=1 を付けて再実行する

誤検知させないことを重視した。npm run publish-docsgit 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-instructionsAGENTS.md / CLAUDE.md(symlink) / Copilot / Cursor 向けファイルへ反映済み。

動作確認

  • pnpm test:hooks(新規追加)→ 48 ケース全 pass(ブロック 23 / 素通し 25)
  • shellcheck scripts/hooks/*.sh → 指摘なし
  • bash -n 構文チェック → OK
  • prettier --check を変更した md / json に実行 → All matched files use Prettier code style!
  • make ai-instructions の生成物が差分に含まれていることを確認
  • hook のライブ動作(settings.json の読み込みにはセッション再起動が必要なため、本セッションでは hook の直接実行で contract を検証。実運用での初回発火は要確認)

セルフレビューリスト

  • AIレビューを活用しましたか? — 本 PR 自体が AI による棚卸し結果。マージ前に人の目で確認してほしい(特に hook の誤検知パターン)
  • 動作確認の手順を実行しましたか? — 上記のとおり
  • その他のコンポーネント関連項目 — 対象外(コンポーネント変更なし)

マージ後にやること

accessibility-checker は共有スキル(Source of Truth = 本リポジトリ)なので、配布経路の両方を回す:

  1. 経路A: sparkle-design-internalpnpm sync:public-skills
  2. 経路B: gh workflow run "Publish Skills" --repo goodpatch/sparkle-design

関連

  • goodpatch/sparkle-guide#18 — deploy-gp-pages の公開操作ゲート(同じ棚卸しから)

高能力モデル(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
@vercel

vercel Bot commented Jul 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
sparkle-design Ready Ready Preview, Comment Jul 29, 2026 6:56am

Request Review

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 40 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a2a60ea8-e4fc-489e-bdf0-0fc60f5c4beb

📥 Commits

Reviewing files that changed from the base of the PR and between 112c382 and 689f8d9.

⛔ Files ignored due to path filters (1)
  • .claude/settings.json is excluded by !**/*.json
📒 Files selected for processing (10)
  • .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.md
  • AGENTS.md
  • docs/ai-instructions/context.md
  • scripts/hooks/irreversible-ops-guard.mjs
  • scripts/hooks/irreversible-ops-guard.sh
  • scripts/hooks/irreversible-ops-guard.test.sh

Walkthrough

アクセシビリティ修正とリリース操作の承認・停止手順を更新し、不可逆コマンドを検出してブロックするPreToolUseフック、確認再実行ルール、包括的なシェルテストを追加しました。

Changes

AI操作安全ガード

Layer / File(s) Summary
修正・リリース手順の停止条件
.claude/skills/accessibility-checker/..., .claude/skills/release-sparkle-design/SKILL.md
修正やリリース操作を、ユーザーの明示的依頼と承認後に実行し、指定箇所で停止して確認を待つ手順に更新しました。
不可逆操作ガードの実装と検証
scripts/hooks/irreversible-ops-guard*
公開、マージ、リリース作成、タグ・force pushなどを解析してブロックするフックと、block/passおよび確認再実行を検証するテストを追加しました。
エージェント向け運用ルールの配線
.cursor/rules/context.mdc, .github/instructions/..., AGENTS.md, docs/ai-instructions/context.md
不可逆操作の対象、SPARKLE_CONFIRM=1による再実行、非対応エージェントの扱い、テスト手順を各コンテキストへ追加しました。

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: ブロック通知または実行継続
Loading

Poem

うさぎが跳ねて、フックを見張る
危ないコマンドは、ぴたりと止まる
合図があれば、もう一度
修正もリリースも、順番どおり
安全な手順を、月まで届ける otsu!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed タイトルは不可逆操作のブロックとスキル側ゲート整備という主要変更を簡潔に表しています。
Description check ✅ Passed 概要・変更内容・動作確認・自己レビューを含み、主要情報は揃っていますが、テンプレの見出し構成とは少し異なります。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch harden/skill-irreversible-ops-guard

Comment @coderabbitai help to get the list of available commands.

散文のルールは「スキルを読んだセッション」にしか効かない。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
@touyou touyou changed the title 🔒 chore: スキルの不可逆操作にヒューマンゲートを追加 🔒 feat: 不可逆操作を hook でブロック + スキルのゲート整備 Jul 29, 2026
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
@touyou

touyou commented Jul 29, 2026

Copy link
Copy Markdown
Member Author

追加コミット 2 件:

  1. gh repo create / delete / archive をブロック対象に追加(テスト 49 → 54 ケース)。scaffold-extension-repo(internal)が gh repo create {org}/{project}-design を確認なしの手順項目として持っており、プレースホルダを AI が推測で埋める余地があったため。gh repo view / clone / gh api repos/... は素通しすることを確認済み
  2. ヘッダコメントをリポジトリ非依存に変更。このスクリプトは sparkle-design / sparkle-design-internal / sparkle-guide の 3 リポジトリでバイト単位で同一に保つ運用にしたため(更新は本リポジトリを先に直して他へコピー)

セルフレビューで発見。`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
@touyou
touyou marked this pull request as ready for review July 29, 2026 06:37
Copilot AI review requested due to automatic review settings July 29, 2026 06:37

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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')"

Comment thread scripts/hooks/irreversible-ops-guard.sh Outdated
Comment on lines +42 to +43
command="$(jq -r '.tool_input.command // ""' <<<"$input")"
[ -n "$command" ] || exit 0
Comment on lines +98 to +100
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'

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread scripts/hooks/irreversible-ops-guard.sh Outdated
Comment on lines +129 to +130
case " $seg " in
*" --dry-run "* | *" --dry-run="*) ;;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment thread scripts/hooks/irreversible-ops-guard.sh Outdated
Comment on lines +72 to +79
[A-Za-z_]*=*)
# 変数代入は「=」より前に空白を含まない。それ以外は通常のコマンドとして扱う
head="${seg%%[[:space:]]*}"
case "$head" in
*=*)
seg="${seg#"$head"}"
seg="${seg#"${seg%%[![:space:]]*}"}"
;;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment thread scripts/hooks/irreversible-ops-guard.sh Outdated
Comment on lines +181 to +183
# refspec の dst 側を見る。タグ (vX.Y.Z) の push だけを対象にする
dst="${tok##*:}"
if [[ "$dst" =~ ^(refs/tags/)?v[0-9]+\.[0-9]+\.[0-9]+ ]]; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment thread scripts/hooks/irreversible-ops-guard.sh Outdated
# `;` `&` `|` `&&` `||` と改行で分割し、各セグメントの「先頭に来るコマンド」だけを
# 判定対象にする。こうするとコミットメッセージ等に "npm publish" の文字列が
# 含まれるだけのケースを弾ける (先頭が git commit になるため)。
segments="$(printf '%s' "$command" | sed -E 's/(\|\||&&|[;&|])/\n/g')"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (1)
scripts/hooks/irreversible-ops-guard.test.sh (1)

17-35: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

exit 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

📥 Commits

Reviewing files that changed from the base of the PR and between cbf5502 and 112c382.

⛔ Files ignored due to path filters (2)
  • .claude/settings.json is excluded by !**/*.json
  • package.json is 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.md
  • AGENTS.md
  • docs/ai-instructions/context.md
  • scripts/hooks/irreversible-ops-guard.sh
  • scripts/hooks/irreversible-ops-guard.test.sh

Comment thread .claude/skills/accessibility-checker/SKILL.md
Comment on lines +39 to +41
> これらは `scripts/hooks/irreversible-ops-guard.sh`(PreToolUse hook)が実際にブロックする。
> ブロックされたら、ユーザーの指示を得たうえで `SPARKLE_CONFIRM=1` を先頭に付けて再実行する。
> **hook が無い環境(Claude Code 以外のエージェント)でも、上のルールは同じように適用する。**

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 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.

Comment thread .claude/skills/release-sparkle-design/SKILL.md
Comment thread .claude/skills/release-sparkle-design/SKILL.md
Comment thread docs/ai-instructions/context.md
Comment thread scripts/hooks/irreversible-ops-guard.sh Outdated
Comment on lines +27 to +43
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 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.

Suggested change
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

Comment thread scripts/hooks/irreversible-ops-guard.sh Outdated
Comment thread scripts/hooks/irreversible-ops-guard.sh Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copilot AI review requested due to automatic review settings July 29, 2026 06:55
@touyou
touyou merged commit a075460 into main Jul 29, 2026
6 checks passed
@touyou
touyou deleted the harden/skill-irreversible-ops-guard branch July 29, 2026 06:57

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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'

Comment on lines +81 to +93
if (c === ")") {
if (substitutionStack.length > 0) {
flush();
quote = substitutionStack.pop();
continue;
}
if (quote === null) {
flush(); // サブシェルの終わり
continue;
}
current += c;
continue;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants