⚙️

Claude Code Routineの設定をfrontmatterでConfig as Code管理する

に公開

はじめに

Claude Code Routineを使い始めて少し経過し、運用しているうちに地味に気になってきたことがあります。

私はRoutineのプロンプトを .claude/routines/*.md ファイルとしてリポジトリに置き、/schedule コマンドでそのファイルを指定して登録・更新するようにしていました。

ところが「いつ実行するか」「どの環境で動くか」「どのモデルを使うか」「どのリポジトリ・コネクタを使うか」といった付帯情報は、クラウド側の内部状態にしか残らないことに気付きました。

リポジトリをcloneし直したとき、あるいは数週間前の自分が何を設定したか思い出したいとき、結局はWeb UIを開いてフォームをポチポチ確認することになります。やってみて初めて、設定の意図がコードに残っていないことのつらさを実感しました。

この記事では、.claude/routines/*.md のYAML frontmatterに設定を宣言してConfig as Codeとして管理するパターンを紹介します。後述しますが /schedule コマンドは内部的に RemoteTrigger API のラッパーになっていて、frontmatterのフィールドはそのままAPIの実フィールドに一致する設計にできます。単なるドキュメント層ではなく「APIフィールドをそのまま使っている」と捉えると、設計の意図がブレません。


何が問題だったか

私のリポジトリでは現在複数のClaude Code Routineが動いています。実行頻度や役割はそれぞれ異なり、毎朝起動するものもあれば、1日数回・週次で動くものもあります。

それぞれプロンプトは .claude/routines/*.md に置いてあるのでgitで追えます。一方で、各Routineの動作を規定する付帯情報は次の2箇所に分散していました。

関心事 置き場 状態
プロンプト .claude/routines/*.md 本文 version管理済み
cron_expression / environment / model / repositories / mcp_connections / allow_unrestricted_branch_pushes Claude Code Routines内部状態 リポジトリ外・不可視

このうちプロンプト以外はWeb UIで管理する前提になっていて、変更履歴がgitに残りません。

具体的に困ったのは次のような場面でした。

  • あるRoutineのスケジュールを変えたとき、どこにメモすればいいか分からない
  • 新マシンで環境を再構築するとき、どのenvironment・mcp_connectionsを紐付けたか思い出せない
  • モデルを claude-opus-4-6 から claude-opus-4-7 に上げたいが、どのRoutineが古いまま残っているか一覧できない
  • どのRoutineが allow_unrestricted_branch_pushes: true(branch push制限なし)で動いているか把握できない

要するに、設定の意図と実態がコードに残っていない問題です。


解決策:frontmatterで宣言する

シンプルな解決策として、各Routineのプロンプトファイル冒頭にYAML frontmatterで設定を宣言することにしました。

---
name: hello-world                        # /schedule での routine 識別名
cron_expression: "0 23 * * *"            # JST 8:00 毎日(UTC cron式)
environment: your-environment            # Claude Code cloud environment名
model: claude-sonnet-4-6
repositories: [your-repo, your-repo-2]   # /schedule で紐付けるリポジトリ(複数可)
mcp_connections: [your-connector]                 # /schedule で紐付けるMCPコネクタ
last_synced_at: 2026-04-25T00:00:00Z     # schedulerの実態を確認・反映した日時(ISO 8601 UTC)
allow_unrestricted_branch_pushes: false  # false = claude/ ブランチのみpush可(デフォルト)
enabled: true                            # false で routine を一時無効化(pause)
---

Say "Hello World"

これだけです。Markdownのfrontmatterなので、パーサも何も必要ありません。Claude Codeのscheduler本体はこのfrontmatterを直接読みに行くわけではなく、/schedule コマンドにこのファイルパスを渡したときの入力値として参照されます。実体としてはあくまで人間とエージェント向けのドキュメント層に近い位置づけです。

ただ、この「ただのドキュメント」が思った以上に効きました。


/schedule は RemoteTrigger API のラッパーである

frontmatter設計の核心はここです。Claude Codeの /schedule skillは、内部的には RemoteTrigger API を呼び出しています。APIには list / get / create / update / run といったアクションがあります。

frontmatterのフィールドは create(新規作成)と update(設定変更)の両方のbodyに対応しています。create は全フィールドを揃えて新規登録するとき、update は既存routineのスケジュール変更やモデル更新など部分的な変更を行うときに使います(update は部分更新なので変更したいフィールドだけ送ればよい)。

create 時のbodyの主要部分は次のような構造です。

{
  "name": "routine名",
  "cron_expression": "0 21 * * *",
  "job_config": {
    "ccr": {
      "environment_id": "string",
      "session_context": {
        "model": "claude-sonnet-4-6",
        "sources": [
          {"git_repository": {"url": "https://github.com/org/repo"}}
        ],
        "allowed_tools": ["Bash", "Read", "Write", "Edit"]
      },
      "events": [
        {"data": {"type": "user", "message": {"content": "プロンプト本文", "role": "user"}}}
      ]
    }
  },
  "mcp_connections": [{"connector_uuid": "...", "name": "slack", "url": "..."}],
  "allow_unrestricted_branch_pushes": false
}

つまりfrontmatterに書くべきフィールドは「ドキュメント上で便利そうなもの」を勘で選ぶのではなく、APIフィールドをそのまま使う形で設計できます。namecron_expressionmcp_connectionsallow_unrestricted_branch_pushes といったフィールドはAPIのトップレベルbodyフィールドと完全に同名なので、frontmatterは単なるメモではなく「APIへの入力宣言」として一貫した意味を持ちます。

実際の対応関係は次節の表にまとめます。

なお cron_expression 以外に run_once_at というフィールドもあり、ワンオフ実行の設定も可能です。enabled: false でroutineの一時無効化(pause)もできます。


フィールド設計の意図

各フィールドの役割と、設計判断のポイントを書いておきます。

frontmatter ↔ API フィールド対応表

まず対応関係を一覧で示しておきます。これがfrontmatterスキーマの背骨です。

frontmatterフィールド 対応するAPIフィールド 備考
name name フィールド名そのまま
cron_expression cron_expression UTC cron式・フィールド名そのまま
environment job_config.ccr.environment_id
model job_config.ccr.session_context.model
repositories job_config.ccr.session_context.sources[].git_repository.url 配列で複数指定可
mcp_connections mcp_connections フィールド名そのまま・配列
allow_unrestricted_branch_pushes allow_unrestricted_branch_pushes フィールド名そのまま・true/false
enabled enabled フィールド名そのまま・false で pause
last_synced_at (対応なし) ドキュメント用。手動記録

namecron_expressionmcp_connectionsallow_unrestricted_branch_pushesenabled はAPIのトップレベルbodyフィールドと完全に同名なので、frontmatter ↔ API の変換ルールを覚える必要がありません。environment / model / repositories だけは job_config.ccr.* の深いパスにマッピングされますが、こちらは「ネスト構造を short-hand 表記に展開している」程度の差です。last_synced_at だけは API には対応がなく、人間のためのフィールドです。

cron_expression

UTC cron式で書きます。Claude Code schedulerはUTC固定なので、JSTで「毎朝7時」を意図する場合は "0 22 * * *" になります。

ここで素直にUTCを書くと「これJSTで何時だっけ?」と毎回計算することになるので、コメントでJST表現を併記する運用にしています。

cron_expression: "0 22 * * *"   # JST 7:00 毎日(UTC cron式)

environment

Claude Codeのcloud environment名(Default または自分で作成したカスタム environment 名など)を書きます。

ネットワークポリシーや、environmentに設定済みの環境変数・MCPコネクタと結びつくので、「このRoutineを再現するならどの環境を選ぶべきか」が一目で分かります。

なお environment 自体(およびそこに紐付ける環境変数)は claude.ai または Claude Desktop 側で事前に作成しておく必要があります。/schedule はあくまで作成済みの environment を選択するだけです。

model

claude-sonnet-4-6claude-opus-4-6 のような使用モデルを書きます。

これも地味に重要で、Anthropicがモデルを更新したときにどのRoutineを追従させたか・していないかが一覧できます。手元でも複数のモデルバージョンが混在することがあり、追従の進捗管理に役立っています。

repositories

/schedule で紐付けるリポジトリ名を配列で書きます。Routineが起動する際にチェックアウトされるリポジトリです。複数指定すると、Routine内から複数リポジトリにまたがる操作ができます。

repositories: [your-repo, your-repo-2]

事前にClaude Code側でリポジトリ連携を済ませておく必要があります。

mcp_connections

/schedule で紐付けるMCPコネクタの名前を配列で書きます。

mcp_connections: [your-connector]

slack のようにコネクタ名を指定することで、Routineの中からそのコネクタの機能を呼び出せます。これも事前に claude.ai / Claude Desktop 側でMCPコネクタの認証を済ませておく必要があります。配列なので複数コネクタを同時に紐付けることもできます。

allow_unrestricted_branch_pushes

ブランチpushの権限スコープを指定します。

allow_unrestricted_branch_pushes: false  # false = claude/ ブランチのみpush可(デフォルト)
  • true: 任意のブランチ名にpushできる("Allow unrestricted branch pushes" 相当)
  • false(デフォルト): claude/ プレフィックスを持つブランチにのみpushできる

PR作成までを claude/... ブランチに固定したい安全運用ならデフォルトのままで問題ありません。Routine側で main 直push や、独自命名のブランチへのpushが必要なときだけ true にする、という使い分けです。frontmatterに書いておくと「このRoutineは無制限push権限を持っている」という事実がdiffで可視化されます。

name

/schedule 上での routine 識別名です。ファイル名と揃えておくのが分かりやすいです。

name: hello-world

enabled

Routineの有効/無効を切り替えます。デフォルトは true(有効)。一時的にRoutineを止めたいが設定は残したい、という場合に false を指定すると pause 状態になります。

enabled: false   # 一時的に無効化

frontmatter上で enabled: false のRoutineを並べておけば「いま停止中のRoutineはどれか」が一覧でき、削除する前のクールダウン期間としても扱いやすいです。

last_synced_at

Routineのscheduler側設定を最後に確認・反映した日時をISO 8601 UTC形式で書きます。これが今回いちばん効いたフィールドです。

last_synced_at: 2026-04-25T00:00:00Z

last_synced_at でdrift問題に対処する

Config as Codeの古典的な課題に drift(宣言と実態の乖離) があります。Infrastructure as Codeでも同じ問題が出ますが、LLM Automationでも同様に発生します。

具体的なdriftシナリオは次のようなものです。

  • frontmatterは更新したが、Web UIでschedule変更を忘れた → 「version管理された嘘」になる
  • Web UI側を直接変更してfrontmatter更新を忘れた → frontmatterが古いまま
  • そもそも「frontmatterと実態が合っているのか分からない」状態が続く

last_synced_at は「いつ実態を確認したか」を記録するフィールドです。これがあると、

  • フィールドが古い → frontmatterと実態が乖離している可能性が高い
  • フィールドが新しい → 少なくともその日時点では一致していた

という最低限の判断材料になります。完全な自動同期は無理でも、乖離の古さを可視化することはできるわけです。

運用ルールとしては次の1行を .claude/rules/routines.md に書いてあります。

/schedule でroutineの設定(スケジュール・環境・モデル)を変更したら、対応する .md ファイルのfrontmatterと last_synced_at も必ず更新する。

これを守れば、frontmatterは「最後に確認した時点での仕様書」として信頼できます。


実際のRoutine構成

参考までに、複数のRoutineを並べたときに見えてくる構成イメージを、汎用化したfrontmatter例で示します。

毎朝動く外部APIを叩くRoutine

---
name: your-routine
cron_expression: "0 22 * * *"            # JST 7:00 毎日
environment: your-environment
model: claude-opus-4-6
repositories: [your-repo]
mcp_connections: [your-connector]
last_synced_at: 2026-04-25T00:00:00Z
allow_unrestricted_branch_pushes: false
enabled: true
---

週次で動く品質チェック・通知Routine

---
name: your-routine-2
cron_expression: "0 21 * * 5"            # UTC 金曜21:00 = JST 土曜6:00
environment: your-environment
model: claude-opus-4-6
repositories: [your-repo]
mcp_connections: [your-connector]
last_synced_at: 2026-04-25T00:00:00Z
allow_unrestricted_branch_pushes: false
enabled: true
---

並べてみると、各Routineの実行枠と必要環境が一望できます。「同じ時刻に複数のRoutineが同時に走るが大丈夫か?」みたいな疑問も、frontmatterを並べて読めば確認できます(書き込み先ディレクトリが分離していれば競合しない、というような分析)。


正直な制約

このパターンは万能ではないので、limitationを書いておきます。

1. あくまでドキュメント層 + /schedule への入力

frontmatterはClaude Code scheduler本体が直接ポーリングするわけではなく、/schedule コマンドにファイルパスを渡したときの入力値として参照される位置づけです。「frontmatterを書き換えたら自動でschedule反映」のような完全な双方向同期は現時点では存在しません。実体としてはドキュメント層 + /schedule 用の入力テンプレート、と捉えるのが正確です。

また environment 自体・環境変数・mcp_connectionsrepositories の連携設定は、claude.ai / Claude Desktop 側で事前に作成しておく必要があります。frontmatterはあくまで「事前に用意した設定のうちどれを選ぶか」を宣言する層です。

2. driftの自動検知はない

last_synced_at は手動更新です。「frontmatterと実態が合っているか」を機械的にチェックする仕組みは作っていません。乖離の可能性を last_synced_at可視化できるだけで、検出まではできません。

3. 公式対応との衝突リスク

これは独自拡張なので、将来Anthropicが「frontmatterからauto-register」のような公式機能を提供すると、独自スキーマと標準スキーマでconflictする可能性があります。そのときは公式に寄せる必要があります。

4. routineの削除はWeb UIからのみ

RemoteTrigger APIには作成・更新・list・実行(trigger)はありますが、削除エンドポイントは存在しません。frontmatterファイルを消しても、Claude Code側のroutineは残り続けます。不要になったroutineを消したいときは https://claude.ai/code/routines を開いて手動で削除する必要があります。frontmatter運用と相性が悪い数少ないオペレーションです。

5. n=1の知見

単一リポジトリ・個人運用での知見であり、複数Routine・複数人開発のチーム環境で同じパターンが最適かは未検証です。


このパターンが効く理由

正直、最初は「Markdownのfrontmatterに書くだけで何が変わるんだ?」と半信半疑でした。実際に運用してみて効いた理由は次の4つだと思っています。

1. PRレビューで設定変更が見える

Routineのプロンプトを変更するときは必ずPRを作るので、「ついでにスケジュールも変えた」がdiffに出ます。レビュアー(多くの場合は将来の自分)が「あ、cron変わってる」と気付ける。

2. 設定の意図が言語化される

# JST 7:00 毎日 のようなコメントが残ることで、なぜその時刻にしたのか・なぜその環境を選んだのかが追えます。Web UIだとフォームの入力値しか残らない。

3. モデル更新時の追従漏れが防げる

grep -r "model:" .claude/routines/ で全Routineのモデルが一覧できるので、新モデルが出たときに「どれが古いまま残っているか」が即座に分かります。

4. APIフィールドと一致しているのでスキーマがブレない

frontmatterのフィールドはRemoteTrigger APIのbodyフィールドとそのまま同名で対応しているので(namecron_expressionmcp_connectionsallow_unrestricted_branch_pushesenabled はすべてAPIと完全一致)、「何を書くべきか」「どこまで書くべきか」が自然に決まります。試しにOpus・Sonnet両モデルにフォーマットの選択肢(YAML単体・TOML・JSON・frontmatter)を比較させたところ、両方とも frontmatter が最もこの用途に適していると評価しました。プロンプト本文と設定が同じファイルに同居しつつ、構造化部分は機械可読、というバランスが効いているようです。


まとめ

Claude Code Routineの設定(cron_expression・environment・model・repositories・mcp_connections・allow_unrestricted_branch_pushes・enabled)を .claude/routines/*.md のYAML frontmatterに宣言することで、Config as CodeパターンとしてRoutineの動作仕様をgit管理しています。

課題 対処
cron_expression・environmentがリポジトリ外で不可視 frontmatterに宣言してversion管理
repositories・mcp_connectionsの紐付けが追えない repositories / mcp_connections で明示
branch push権限の範囲が把握できない allow_unrestricted_branch_pushestrue/false を宣言
一時停止中のRoutineを把握できない enabled: false で明示
宣言と実態のdriftが分からない last_synced_at で確認日を記録
モデル更新時の追従漏れ grep で全Routineのモデルを一覧
frontmatterスキーマの根拠が曖昧 RemoteTrigger APIのbodyフィールドをそのまま使う

これは公式に推奨されたパターンではなく、.claude/routines/*.md という非公式パターンの上にさらに重ねた独自拡張です。ただ /schedule が RemoteTrigger API のラッパーである以上、frontmatterのフィールドはAPIフィールドそのものとして自然に位置づけられます。

参考リンク

GitHubで編集を提案

Discussion