AIエージェントに任せる定期実行の仕事が増えるほど、効いてくるのは置き場所の設計です。
私の場合、Claude Cowork で回している定期タスクが26本を超えたあたりで、ひとつの事実に気づきました。
タスクの定義そのものが、Gitの外に置きっぱなしになっていたのです。
コードは丁寧にバージョン管理しているのに、それを動かす手順書だけが履歴も差分も持たないまま、アプリの設定画面とPCのユーザーフォルダに散らばっていました。
同じ置き忘れ、心当たりはありませんか。
結論から書くと、定義の正本はリポジトリに置き、実行側には「そこを読め」という1行だけを残す形に組み替えたところ、履歴もPC移行も一気に扱いやすくなりました。
前提(最小限)
Cowork は Claude デスクトップアプリのタスク実行機能で、接続フォルダを通じてローカルのファイルを読み書きします。
定期タスクの定義が保存されるのは、アプリ内のプロンプトと、ユーザープロファイル直下 %USERPROFILE%\Claude\Scheduled 配下の SKILL.md です。
スケジュール実行の仕様そのものは、公式ヘルプのSchedule recurring tasks in Claude Coworkにまとまっています。
ここで問題になるのは、どちらの保存先もリポジトリの外にあり、git status にも git diff にも出てこない点でした。
検証したのは2026年8月3日で、Cowork 側の仕様は変わりうる前提で読んでください。
タスク定義がGitの外にあると何が起きるか
置き場所の話を後回しにしていると、困りごとが三つの形で表面化します。
一つ目は、変更履歴が残らないこと。
プロンプトを一行直したときに、前が何だったのか、なぜ直したのかを後から追えません。
エージェントの挙動が変わったときに、原因が指示側にあるのか実行環境側にあるのかを切り分ける材料が消えてしまうわけです。
二つ目は、PCを買い替えた瞬間にタスク定義が丸ごと消える危険でした。
アプリの設定とユーザープロファイル配下にしか実体が無いので、バックアップの網から漏れやすい場所に居座っています。
三つ目は、定義と成果物が別々の場所に散ること。
タスクが出力したレポートは接続フォルダに溜まるのに、それを作った指示書は別の階層にある、というねじれが起きます。
三つとも根は同じで、実行環境が定義の保管庫を兼ねてしまっていることに尽きるのです。
26本まで増えてから気づいたのは、正直なところ遅すぎたと感じます。
正本をリポジトリへ、実行側には1行だけ
やったことは単純で、定義の実体をGitリポジトリへ移し、実行側には委譲だけを残しました。
出来上がったのは三層の構造です。
アプリ内のプロンプトが1行ポインタ、%USERPROFILE%\Claude\Scheduled 配下の SKILL.md も1行ポインタ、その先のリポジトリ側だけが正本。
二段構えにしたのは、アプリ側を触りにくい状況でも、Scheduled 側を差し替えるだけで委譲先を変えられるからでした。
ポインタの実物
Scheduled 側に残したファイルは、これだけです。
# blog-daily(ポインタ)
正本はリポジトリ側にある。接続フォルダ cowork の `10.blog-daily/blog-daily.SKILL.md` を読み、
その指示に従って実行する。
このファイルは編集しない(定義の変更はリポジトリ側で行い commit する)。
実行のたびにエージェントが正本を読みに行くので、定義を直せば次回の実行から自動で反映されます。
コミットしたかどうかにも左右されません。
その代わり、正本の場所は一意に決めておく必要がありました。
パス解決を機械に任せる
ポインタと同じくらい効いたのが、ポインタに絶対パスを書かないという縛りです。
PCによって変わる値はGit管理外の設定ファイルへ逃がし、無ければ既定値で解決する形にします。
$scheduled = $null
$localCfg = Join-Path $PSScriptRoot 'local.paths.json'
if (Test-Path $localCfg) {
$cfg = Get-Content $localCfg -Raw -Encoding UTF8 | ConvertFrom-Json
if ($cfg.scheduledDir) {
$scheduled = [Environment]::ExpandEnvironmentVariables($cfg.scheduledDir)
}
}
if (-not $scheduled) {
$scheduled = Join-Path $env:USERPROFILE 'Claude\Scheduled'
}
# 略: 見つからなければ throw する。旧配置は自動採用しない
おかげで別のPCへ移すときの手順は、clone、接続フォルダの登録、設定ファイルの作成、タスクの再登録という四つに収まりました。
再登録のときに貼り付けるのも、1行ポインタだけです。
移行の実際と、そこで出た数字
設計より手間がかかったのは、実際の引っ越し作業のほうでした。
一括書き換えと検証
移したのは548ファイル、約27MBです。
テキスト99件に埋まっていた旧い絶対パスを新しいパスへ一括で置換し、grep で残存0件まで確認しました。
効いたのは、インポート用の PowerShell スクリプトを DryRun、Import、WritePointers の三段に分けたことです。
いきなり本番実行せず、何がどこへ動くのかを一覧で眺めてから進めたので、取り返しのつかない移動を避けられました。
taskId とフォルダの対応表は機械可読なJSONへ正本化し、推測で埋めた行には目印を付けてスクリプト側が警告するようにしています。
対応表を人の記憶に置いたままだと、移行の途中で必ず食い違いが出るのです。
ダッシュボード代わりに作っていた画面のHTMLも、書き出してリポジトリへ入れました。
PowerShell 5.1 の落とし穴
移行スクリプトの側にも、地味に時間を溶かした問題がひとつ。
Windows PowerShell 5.1 は、BOM 無しの UTF-8 を ANSI と誤解釈します。
日本語コメント入りのJSONを読むなら文字コードの明示が要りますし、スクリプト自体も BOM 付きの UTF-8 で保存しておくのが安全でした。
実機でパースに失敗してから、この二点を直しています。
つまずいたのは設計ではなく環境のほう
面白いことに、詰まった箇所は三つとも設計の外側でした。
マウント越しのgitは壊れる
接続フォルダのマウント上で git を動かした結果は、はっきりしていました。
init と add と commit は unlink の警告つきで通るのに、checkout が「error: unable to unlink old」で失敗します。
HEAD.lock のようなロックファイルも消せずに残留。
原因を細かく追うより、git 操作はホストOS側だけで行うと決めたほうが早いという結論になりました。
同じ理由で、マウント上のファイルは削除もできません。
不要になったものは _to_delete フォルダへ移し、ホスト側でまとめて消す運用にしています。
委譲は通ったのに接続フォルダで止まった
最初のテスト実行は、期待どおりには動きませんでした。
ポインタ化そのものは成功していたのに、そのタスクの接続フォルダ設定が旧フォルダのままで、参照先を解決できずに中止となりました。
委譲の仕組みが正しくても、委譲先へ到達する経路が古いままなら動かないわけです。
恒久対応としてタスク側の接続フォルダを切り替え、保険としてポインタ自身に接続要求のフォールバックを1行足しています。
正本の場所を1回間違えた
移行の途中で、正本の場所そのものを取り違えました。
過去のメモには別のクラウド同期フォルダ配下が正本だと書いてあり、実際に taskId 名のフォルダが25個ほど残っていたのです。
ところが本人に確認すると、現役の正本はユーザープロファイル直下のほうでした。
それらしいフォルダが実在することは、そこが正本である証拠になりません。
編集して実行結果が変わるかどうかで裏を取るか、素直に持ち主へ聞くほうが速かったです。
最後に
振り返ると、効いたのは特別な仕組みではなく、置き場所の分け方でした。
エージェントのタスク定義は実行環境の中に埋めず、Git管理の正本と実行側の1行ポインタに分ける。
この形なら変更は履歴に残り、差分でレビューでき、PCが変わっても壊れません。
逆に、正本を二重に持ったり、ポインタへ絶対パスを書いたりした瞬間に、この利点は消えるのです。
同じ発想は Cowork に限らず、CIのジョブ定義や手元の自動化スクリプトにもそのまま持ち込めます。
自分が動かしている自動化の定義が今どこに保存されているのか、一度たどってみると発見があるかもしれません。
定義を移す前に踏んだ、起動まわりの障害の記録はこちらです。
以上です。











コメントを残す