Claude CodeでTaskfile(go-task)を使う — task --listを入口に権限を絞る
Taskfile.ymlのタスクをClaude Codeの実行単位にする。descとtask --listで入口をそろえ、CLAUDE.mdへの書き方、task単位の権限ルール、出力を絞るoutput設定を示します。
Taskfile(go-task)をClaude Codeの実行単位にするなら、やることは3つです。検証コマンドを Taskfile.yml に名前付きで並べること、CLAUDE.mdには「まず task --list を見る」とだけ書くこと、承認ルールを task <名前> の単位で作ることです。
こうすると、Claudeが毎回違うコマンドの組み合わせを打つ必要がなくなります。承認ルールも短くなります。一方で、ルールが見るのはClaudeが書いた task test という文字列だけです。タスクの中身は照合されません。この記事では、その前提で Taskfile.yml と権限をどう組むかを順に扱います。
Taskfileを実行単位にすると何が変わるか
Taskは、YAMLで書いたタスクを task <名前> で実行するタスクランナーです。make の代わりに使われることが多く、Claude Codeから見ると「プロジェクトの検証手順に付いた名前の一覧」になります。
Claudeに検証を任せるとき、名前のない状態では次の問題が出ます。
Taskfileなしで起きがちなこと
コマンドが毎回揺れる
npm test、npx jest、npm run test -- --watchのように、同じ目的でも書き方が変わります。承認ルールが広がる
揺れを許すために
Bash(npm *)のような広いルールを置きがちです。手順の前提が伝わらない
lintを通してからテストを回す、といった順序が会話に依存します。
Taskfileにまとめれば、検証は task verify の1語になります。順序や並列実行は Taskfile.yml 側が持ちます。同じ発想のDeno版はdeno taskと--allowの二重の権限設計で扱っています。
インストールと動作確認
Taskの公式ドキュメントには、Homebrewとnpmで入れる手順があります。Homebrewなら次のとおりです。
brew install go-task
task --versionnpmなら npm install -g @go-task/cli です。Homebrewには go-task/tap/go-task のタップ版もあります。apt、dnf、WinGetなどのパッケージも用意されています。
コマンド名は task ですが、ディストリビューションによっては実行ファイル名が go-task になる場合があります。シェル補完の設定手順にも TASK_EXE='go-task' を設定する例が載っています。task: command not found が出たら、go-task --version も試してください。その場合は、後述する権限ルールとCLAUDE.mdの task も実際の名前に読み替えます。
Claudeが読みやすいTaskfile.ymlの形
Claudeにとって最初の入口になるのは task --list の出力です。ここに出るのは desc を持つタスクだけです。desc のないタスクは --list-all を使わない限り一覧に出ません。
Node.jsプロジェクトを例にします。
version: '3'
silent: true
output:
group:
error_only: true
tasks:
verify:
desc: lint・型検査・テストをまとめて実行する
deps: [lint, typecheck, test]
lint:
desc: ESLintを実行する
cmds:
- npm run lint
typecheck:
desc: TypeScriptの型検査を実行する
cmds:
- npx tsc --noEmit
test:
desc: テストを実行する。絞り込みは -- の後ろに書く
cmds:
- npm test {{.CLI_ARGS}}
deploy:
desc: 本番へデプロイする
preconditions:
- sh: test "$(git branch --show-current)" = main
msg: 'mainブランチでのみ実行できます'
cmds:
- ./scripts/deploy.shtask --list は次の形で一覧を出します。以下は公式ドキュメントにある出力の形式に沿った例です。
* deploy: 本番へデプロイする
* lint: ESLintを実行する
* test: テストを実行する。絞り込みは -- の後ろに書く
* typecheck: TypeScriptの型検査を実行する
* verify: lint・型検査・テストをまとめて実行する一覧に出す・出さないを決める
Claudeに見せたくない補助タスクには internal: true を付けます。task --list と --list-all のどちらにも出ず、CLIから直接呼ぶと失敗します。他のタスクからは呼べるので、公開するタスクが入力を渡す形にできます。
詳細が必要なタスクには summary を書きます。task --summary release で説明文と依存タスクとコマンドが表示されるので、Claudeに「実行前に中身を読ませる」運用に向きます。
task --list --json で一覧をJSONにもできます。CLAUDE.mdの記述を生成するスクリプトを書くときの入口になります。
並列と順序を選ぶ
deps に並べたタスクは並列で動き、deps の並び順は実行順を決めません。lintが通ってからテストを回したい場合は、cmds の中で - task: lint、- task: test と呼びます。Taskは各コマンドの成功を待ってから次へ進み、lintが落ちればテストは動きません。
上の verify は並列です。失敗した時点で残りを止めたいなら、タスクに failfast: true を付けるか、task verify --failfast で実行します。
出力を絞る — outputのerror_only
Claude Codeに返るBashの出力には上限があります。ログが長いと、肝心の失敗箇所が抜粋で切られかねません。上限そのものの扱いはbashOutputMaxCharsの解説にあります。ここでは出力の量をTask側で減らします。
上のTaskfileの先頭にある設定が、その役割です。
silent: true
output:
group:
error_only: truesilent: true は、Taskが実行前に出すコマンド文の表示を消します。output: group はコマンドごとに出力をバッファし、error_only: true で失敗したコマンドの出力だけを表示します。成功したタスクは何も出力せず、終了コードも変わりません。
効き方は次の表のとおりです。
| 設定 | 成功時 | 失敗時 |
|---|---|---|
既定(interleaved) | 成功時コマンド文と出力が流れる | 失敗時同じ形で流れる |
output: group のみ | 成功時コマンドごとにまとまって出る | 失敗時同じ |
group + error_only: true | 成功時何も出ない | 失敗時失敗したコマンドの出力と失敗メッセージ |
注意点が2つあります。group はコマンドが終わるまで出力を溜めるので、長く走るテストの途中経過は見えません。また、silent: true と error_only を組み合わせた設定で、default タスクから task --list を呼ばせた場合は、成功時の出力が消えて一覧が見えなくなる可能性があります。一覧は task --list を直接打つ形で呼ばせると確実です。
実行ごとに切り替えたい場合は、Taskfileに書かずCLIで指定できます。
task verify --output group --output-group-error-only成功時に無出力だと、Claudeが「動いたのか」を迷うかもしれません。CLAUDE.mdに「出力がなく終了コード0なら成功」と書いておくと、読み違いを防げます。
CLAUDE.mdにはタスク名を全部書かない
Claude Codeは、CLAUDE.mdを強制される設定ではなく、読んで従う文脈として扱います。書く指示は「確認できるくらい具体的」にします。例えば「テストを確認する」でなく「npm test を実行する」の書き方です。
Taskfileを使うなら、書くのは入口の一行と、運用のルールです。
## コマンド
- 作業の最初に `task --list` を実行し、使えるタスクを確認する
- 変更後の検証は `task verify` を実行する。出力がなく終了コードが0なら成功
- テストの絞り込みは `task test -- <パターン>` で渡す
- `task deploy` は実行しないこう書く理由は2つです。ひとつは、タスクを追加してもCLAUDE.mdを直さずに済むことです。desc に書いた説明が task --list の出力になるので、一覧の正本は常に Taskfile.yml です。もうひとつは、CLAUDE.mdを短く保てることです。CLAUDE.mdは1ファイルあたり200行未満が目安で、長いほど従う度合いが下がります。
一覧をファイルに書き出してimportする方法
task --list の出力をファイルに保存し、CLAUDE.mdから @path で取り込む方法もあります。ただし取り込んだファイルは起動時にCLAUDE.mdと一緒に読み込まれ、文脈を消費します。さらに保存した時点の一覧なので、Taskfile.yml を直すたびに更新が要ります。
使い分けは次のとおりです。
一覧の渡し方
task --listを実行させる
タスクが増えても指示は変わりません。実行に1回分のBash呼び出しが要ります。
書き出してimportする
最初のターンから一覧が見えます。更新を忘れると古い一覧を読みます。
タスクが数個の小さなプロジェクトなら、CLAUDE.mdに直接書いても足ります。数が増えてきたら実行させる形が管理しやすくなります。
権限ルールをtask単位で作る
Claude Codeの権限ルールは、Claudeが書いたコマンドの文字列に対して照合されます。複合コマンドは分割され、各部分が個別に照合されます。評価順はdeny、ask、allowで、最初に一致したものが結果を決めます。
この性質から、task 単位の許可は次のように書けます。
{
"permissions": {
"allow": [
"Bash(task --list *)",
"Bash(task --summary *)",
"Bash(task verify)",
"Bash(task lint)",
"Bash(task typecheck)",
"Bash(task test *)"
],
"deny": [
"Bash(task deploy *)"
]
}
}末尾の * は、スペースを挟むと引数なしの呼び出しにも一致します。Bash(task deploy *) は task deploy そのものも止めます。スペースを入れない Bash(task*) は task で始まる別コマンドにも広がるので避けてください。
プロジェクト共有なら .claude/settings.json、個人用なら .claude/settings.local.json に置けます。ルールの書き方全体はClaude CodeでOpenTofuを使う権限設計の例も参考になります。
文字列で見る限り抜け道がある
ルールが見るのは文字列なので、タスクの中身は審査されません。次の4点は、設計時に押さえておく価値があります。
落とし穴
allowしたタスクの中身は広い権限で動く
Bash(task verify) を許可すると、verify が呼ぶコマンドはすべて承認なしで動きます。Taskfile.yml にデプロイ用のコマンドを足せば、そのまま通ってしまいます。Taskfile.yml 自体を、Claudeが勝手に編集できる状態にしないことが前提です。必要なら Edit(Taskfile.yml) をaskかdenyにします。
落とし穴
引数を転送するタスクは広く開く
task test -- ... の -- 以降は CLI_ARGS に入り、npm test {{.CLI_ARGS}} に展開されます。Bash(task test *) は、テストランナーに何を渡しても通ります。引数を受けないタスクは Bash(task test) のように完全一致で許可し、渡したい引数が決まっているときは Bash(task test -- --coverage) のように書きます。
落とし穴
ワイルドカードのタスク名は名前そのものが入力になる
タスク名に * を使うと、task start:api のような呼び出しの一部が入力になります。捕まえた値は .MATCH 配列に入り、vars で SERVICE: '{{index .MATCH 0}}' のように名前を付けて使います。権限ルール側の書き方には注意が要ります。Taskfileの start:* と同じ見た目で Bash(task start:*) と書くと、末尾の :* は「スペース + 任意の引数」の旧記法として解釈され、Bash(task start *) と同じ意味になります。task start と task start <引数> は通りますが、task start:api は承認待ちのままです。かといって Bash(task start*) とスペースなしで書くと、task start: に続く任意の文字列に加えて task startover のような別タスクまで通ります。引数を -- の後ろに置く CLI_ARGS と同じく、広く開く入口になります。
start:*:* のように * を2つ並べた名前は、2つ目の値(例の REPLICAS)まで受け取ります。aliases: [run:*] を付けると task run:foo でも同じタスクが動くので、start: だけを見たルールでは別名の側が漏れます。許可したいのが start:api と start:worker だけなら、ワイルドカードを避けて Bash(task start:api) のように個別に書くか、サービス名を Taskfile.yml 側の requires や preconditions で絞ります。
落とし穴
denyは別名や別の書き方を止めない
aliases: [d] を付けたタスクは task d でも呼べます。Bash(task deploy *) では止まりません。task --taskfile ... deploy のようにオプションを前置した形も、別の文字列です。Claude Codeの説明でも、denyルールは「Claudeが普通に書く形」を止めるもので、セキュリティの境界ではないとされています。止めたい操作のタスクには別名を付けないこと、強く止めたいなら権限ルールだけに頼らずサンドボックスやPreToolUseフックを併用することが現実的です。
失敗の読み方と、Taskfile側の防御
Claudeに失敗を読ませるとき、Taskの終了コードが手がかりになります。公式のCLIリファレンスでは、Taskfileが見つからない場合は100、タスクが見つからない場合は200、コマンドの実行エラーは201、内部タスクの直接呼び出しは202、必須変数の不足は206と定められています。
-x(--exit-code)を付けると、失敗したコマンドの終了コードがそのまま返ります。テストランナーの終了コードで分岐させたいときの選択肢ですが、Taskの終了コードとは別の値になる点に注意してください。
タスク側に置ける防御も、Claudeの誤操作を減らす手として使えます。
| 目的 | Taskの機能 | 要点 |
|---|---|---|
| 前提が欠けたら失敗させる | Taskの機能preconditions | 要点msg で直し方を書ける |
| 変数が必須の入力を要求する | Taskの機能requires | 要点不足すると終了コード206 |
| 一時ファイルを必ず消す | Taskの機能defer | 要点後続のコマンドが失敗しても動く |
| 実行前に内容を確認する | Taskの機能--dry | 要点展開後のコマンドを表示する |
preconditions の msg は、Claudeが次の手を決める材料になります。「mainブランチでのみ実行できます」のように、人間にもClaudeにも読める文で書いてください。
落とし穴: --dryとsourcesの扱い
--dry は「何も実行しない」という意味ではありません。公式の説明では、sh で宣言した変数は、ドライラン中でもシェルコマンドが実行されることがあります。副作用のあるコマンドを vars の sh に入れないでください。
sources と generates を使う更新判定も、検証タスクには向きません。入力が変わらなければTaskはコマンドをスキップし、「最新」と報告します。Claudeにとっては、テストが走らないまま成功に見えます。検証系のタスクには sources を付けないか、毎回 --force を付けて実行させます。なお --force は preconditions もスキップします。
既存のコマンドから移行する順序
すでに package.json のscriptsやMakefileがある場合は、置き換えず薄く包むところから始められます。
Taskfileへの移行の流れ
- 1
検証コマンドを3つ選ぶ
lint、型検査、テストなど、Claudeに繰り返し打たせているコマンドを選びます。
- 2
descを付けたタスクにする
cmdsには既存のコマンドをそのまま書きます。descは一覧に出る文なので、1行で目的が伝わる形にします。 - 3
verifyでまとめる
depsに3つを並べ、task verifyの1語にします。 - 4
権限とCLAUDE.mdを揃える
Bash(task verify)を許可し、CLAUDE.mdに「検証はtask verify」と書きます。
テストの書き方そのものをCLAUDE.mdに書く例は、VitestとCLAUDE.mdにあります。巨大なビルドツールの出力を絞る発想は、Bazelのテスト出力にも通じます。
まとめ
Taskfileの価値は、検証手順に名前が付き、名前の単位で承認ルールを書けることです。入口は task --list、検証は task verify、成功時の出力は error_only で絞る、という3点で構成が決まります。
一方で、承認ルールはタスクの中身まで守りません。Taskfile.yml の編集権限、引数を転送するタスクとワイルドカードのタスク名、別名の3つは、設計時に手当てしておく箇所です。止めたい操作が本番デプロイのように重いなら、権限ルールとTaskfileの preconditions を二重にしておくと、片方の漏れを補えます。