朝7時のタスクスケジューラが、また空振りしていた。PCがスリープに入ったまま朝を迎えると、楽天証券のポートフォリオを Google Sheets に流し込む update-portfolio.mjs がそのまま無音で一日サボる。気付くのはたいてい昼前で、シートを開いて前日の日付がそのまま残っているのを見て舌打ちする、というのを繰り返していた。
タスクスケジューラを直すより、毎朝必ず叩いている /make-diary の末尾にぶら下げてしまうほうが早い、という結論に行き着いたので、その作業ログを残す。
やりたかったこと
- 毎朝7時の Windows タスクスケジューラで
update-portfolio.mjs(楽天証券の父・母ポートフォリオを Stooq とみんかぶ投信から取得して Google Sheets に upsert)を回している - ただし PC がスリープしていれば実行されない。連続性が切れる
/make-diaryは毎朝必ず手動で叩くので、その末尾にチェーン実行を仕込めば「忘れた日の保険」になる- ただし両方走ると Google Sheets を二重に書きにいくのは避けたい
やったこと
Step 1. タスクスケジューラを特定する
まず「朝7時に何が動いているか」が手元で曖昧だったので、Claude Code に schtasks //query を叩かせてタスクを特定してもらった。PowerShell の実行ポリシーで弾かれた回と、Git Bash のパス変換で /query が C:/Program Files/Git/query に化けた回で2回つまずいたあと、Shift-JIS の出力を UTF-8 に変換させてようやく該当タスクの .cmd パスが出てきた。
中身は node update-portfolio.mjs 一発の素朴な起動スクリプトで、Python だと思い込んでいた箇所が Node.js だと判明した、というおまけ付き。
Step 2. ユーザーレベルのスラッシュコマンドに切り出す
/make-diary は既に ~/.claude/commands/ 配下の check-earnings.md 等を Step 9〜11.5 でチェーン参照している。同じ流儀で ~/.claude/commands/update-portfolio.md を1枚足し、/make-diary の末尾に「Step 12. /update-portfolio をチェーン実行」を追記する形に揃えてもらった。
これで /make-diary から見ると、自プロジェクト外のスクリプトでも他のチェーンと同じ顔をして並ぶ。
Step 3. 二重実行をどう避けるか
ここがいちばん悩んだ。タスクスケジューラ側を消すとスリープ問題が直接解決するが、保険として「朝起きていれば7時に動く」レーンも残しておきたい。両方残すと、朝7時に走ったあと10時頃に /make-diary がもう一度走らせて、Google Sheets を二重に書きにいく。
選んだ方針は 「実行側に当日スキップを組み込む」:
update-portfolio.mjs自身に--skip-if-today-successフラグを足す- 成功時に
.last-success-dateという日付だけ書いたファイルを残す - フラグ付きで叩かれた場合、当日の成功記録があれば1行ログを吐いて
exit 0する - フラグなしで叩けば従来通り強制実行
これでタスクスケジューラ側のショートカットは無印(強制実行)、/make-diary から呼ぶ側だけ --skip-if-today-success を付ける形にすれば、どちらが先に走っても二重書き込みは起きない。
判定ロジックの核心はこれだけ:
if (args.includes('--skip-if-today-success')) {
const last = readLastSuccessDate() // .last-success-date を読む
const today = nowJst().toISOString().slice(0, 10)
if (last === today) {
console.log(`[skip] already succeeded today (${today})`)
process.exit(0)
}
}
// 通常の取得・upsert 処理に進む
nowJst() は元からあった JST 換算ヘルパーをそのまま流用。.last-success-date は git 管理に入れたくないので .gitignore に追記してもらった。
Step 4. 当日分は手動で回す
設計を入れ込んだ今日の段階では、朝7時のタスクスケジューラはスリープで空振りしていた。明日からは /make-diary 経由で勝手に補完される。今日分だけは --skip-if-today-success を外して手動で1回叩いてもらい、Google Sheets の更新と .last-success-date の初期化をまとめて済ませた。
学びメモ
- タスクスケジューラの空振りは、別レーンで叩く運用で塞ぐほうがコストが低い。Wake on LAN や復帰後の遅延起動を仕込むより、自分が毎朝必ず触る
/make-diaryにぶら下げるほうが、忘却にも強い - 二重実行ガードは「呼び出し側」ではなく「実行側」に置くほうが楽。今回は
update-portfolio.mjsに1ファイル増やすだけで済んだ。/make-diary側に if 文を書こうとすると、フラグや日付の取り回しが他のチェーン Step にも波及する .last-success-date方式は素朴だが効く。タイムゾーン跨ぎの曖昧さはnowJst()で吸収して、ファイルにはYYYY-MM-DD文字列だけ書く。読み比べが1行で終わるschtasks //queryの Git Bash 越し起動でハマる。/queryがパス変換されるので//queryで叩く、出力は Shift-JIS なのでiconv -f sjisで UTF-8 に直す、の2点を覚えておく
明日確認すること
- 翌朝、タスクスケジューラ→
/make-diaryの順で走ったときに、/make-diary側で[skip] already succeeded todayが1行だけ出て exit 0 しているか - スリープで7時の側が落ちた日に、
/make-diary側が代わりに Google Sheets を更新できているか -
.last-success-dateの日付が、JST 基準で当日になっているか(UTC ずれが起きていないか)