朝、前日のログを確認しようとして手が止まりました。
2026-06-13.txt を開くと、そこにあるはずの夜間バッチの記録が消えていて、代わりにその日の朝に走ったタスクの一行だけが残っていたのです。
ファイルは確かに存在している。中身だけが入れ替わっている。最初はタスクが二重起動して片方が壊れたのかと疑いましたが、調べていくと、犯人はずっと素朴な場所にいました。コンテナの中の date です。
個人開発で複数のサイトを無人運用していると、こうした「静かにデータが欠ける」種類の不具合がいちばん怖いと感じています。エラーで止まってくれれば気づけます。けれど黙って上書きされると、失われたことにすら気づけません。
何が起きていたか — ログが消えるのではなく、別の日付に書かれていた
私のスケジュールタスクは、実行のたびに日付別のログを残す設計でした。骨格はこうです。
LOG_DIR="$WS/_updated_article_log/claudelab"
echo "[$(date +%H:%M)] 記事を1本公開: $TITLE" > "$LOG_DIR/$(date +%F).txt"ローカルの Mac で動かしている分には、何の問題もありませんでした。Mac のタイムゾーンは日本時間だからです。
破綻したのは、同じスクリプトをクラウドのコンテナ上で無人実行に切り替えたときでした。コンテナの既定タイムゾーンは UTC です。日本時間との差は9時間。つまり、日本時間の朝8時に走るタスクは、UTC ではまだ前日の23時です。
# 日本時間 2026-06-13 08:00 にタスクが起動
$ date
Thu Jun 12 23:00:00 UTC 2026 # コンテナの中ではまだ「12日」
$ date +%F
2026-06-12 # 当日のつもりが、前日の日付になるここに、もうひとつの落とし穴が重なりました。出力リダイレクトに >(上書き)を使っていたことです。
朝のタスクは「12日」のファイル名を計算し、> で開いて、前日の夜にきちんと書かれていた12日分のログを丸ごと消してから自分の一行を書き込んでいました。ログが消えたのではなく、間違った日付のファイルに、上書きモードで書かれていた。これが真相でした。
最初に疑ったこと、そして実際の原因
最初に疑ったのは二重起動でした。スケジューラが同じタスクを重ねて起動し、競合したのではないかと。
けれど実行履歴を見ると、起動は1日に想定どおりの回数しかありません。競合の痕跡もない。
次に疑ったのはファイルシステムの同期です。クラウド同期フォルダ越しに書いていたため、同期の遅延で古い版が復活したのではないかと。これも、ローカルとリモートのタイムスタンプを突き合わせて否定できました。
決め手は、消えたログのファイル名と中身のズレでした。2026-06-13.txt のはずの内容が 2026-06-12.txt に紛れ込んでいる。日付がきっかり一日ずれている。9時間の時差を思い出した瞬間に、すべてがつながりました。
切り分けで効いたのは、推測を増やす前に「実際に何が出力されるか」を一度だけ確認したことです。
# コンテナ内で、スクリプトが見ている日付をそのまま出す
$ TZ=Asia/Tokyo date +%F
2026-06-13
$ date +%F
2026-06-12二つの値が違う。これで仮説は確定しました。コードのどこを直すかではなく、date が何を返しているかが問題だったのです。
恒久対策 — タイムゾーンを固定し、追記にし、消えたら気づけるようにする
直し方は三段構えにしました。一つでは不十分だと考えたからです。
第一に、日付の計算でタイムゾーンを明示します。 これが根本原因への対処です。
# 悪い例: コンテナの既定(UTC)に依存する
DAY="$(date +%F)"
# 良い例: 日本時間で固定する
DAY="$(TZ=Asia/Tokyo date +%F)"スクリプトの先頭で export TZ=Asia/Tokyo を一度宣言しておけば、以降の date 呼び出しすべてに効きます。タスクごとに書き忘れる余地を消せるので、私はこちらを好んでいます。
#!/usr/bin/env bash
set -euo pipefail
export TZ=Asia/Tokyo # この行を入れた瞬間に、以降の date は全て日本時間
LOG_DIR="$WS/_updated_article_log/claudelab"
mkdir -p "$LOG_DIR"第二に、上書きをやめて追記にします。 同じ日に複数回走っても記録が積み上がるようにするためです。
# 悪い例: 同じ日の2本目の実行が1本目を消す
echo "[$(date +%H:%M)] $MSG" > "$LOG_DIR/$DAY.txt"
# 良い例: その日の記録を積み上げる
echo "[$(date +%H:%M)] $MSG" >> "$LOG_DIR/$DAY.txt"たとえタイムゾーンの修正を入れ忘れた未来の自分がいても、追記であれば「別の日付に紛れ込む」ことはあっても「既存の記録を消す」ことは起きません。被害の上限を下げておく、という発想です。
第三に、異常を検知できるようにします。 静かに壊れる不具合は、静かなままにしないことがいちばんの対策です。
# その日のログが、実行後に空でないことを確認する
DAY="$(TZ=Asia/Tokyo date +%F)"
LOG="$LOG_DIR/$DAY.txt"
if [ ! -s "$LOG" ]; then
echo "WARN: 本日($DAY)のログが空です。タイムゾーンか書き込み先を確認してください" >&2
fi
# ファイル名の日付と、中身の先頭行の日付が一致するかを軽く点検する
head -1 "$LOG" | grep -q "$(TZ=Asia/Tokyo date +%H)" || echo "NOTE: 直近のログ時刻が現在時刻と離れています" >&2無音で不発に終わるタスクを外側から見張る考え方は、スケジュール生成が無音で不発に終わったことに気づく仕組みでも書きました。今回のログ上書きは、その「気づけない失敗」の一種でした。
なぜローカルでは一度も再現しなかったのか
この不具合がやっかいだったのは、開発環境では絶対に起きないことでした。
手元の Mac はタイムゾーンが日本時間です。date も date +%F も常に正しい日付を返します。何度試しても再現しない。けれど無人のコンテナに移した瞬間に、環境のタイムゾーンという見えない前提が変わり、同じコードが別の振る舞いをしました。
私自身、個人開発の現場でこの一件に時間を取られました。ここから学んだのは、時刻に依存する処理は「どのタイムゾーンで動くか」を環境任せにしてはいけないということです。ローカルとコンテナで暗黙の前提が食い違うと、テストをすり抜けて本番でだけ壊れます。
同じ理由で、コンテナと手元で挙動が割れる落とし穴は他にもあります。クローンが古いまま判断材料になってしまう件は浅いクローンが古いまま判断に使われる前にリフレッシュするに、無人タスクが黙って止まる側の対処はCowork スケジュールタスクが黙って止まる理由と自動復帰の作り方にまとめてあります。
もうひとつ、混同しやすい点を補足しておきます。スケジューラが「日本時間の朝8時に起動する」ことと、起動した先のシェルの date が日本時間を返すことは、別の話です。トリガーの時刻設定が正しくても、シェルの中身が UTC のままなら、今回と同じズレが起きます。トリガーの時刻と、スクリプト内で時刻を計算するタイムゾーンは、それぞれ独立して合わせる必要があります。
次に同じ轍を踏まないために
時刻を使うスクリプトを書いたら、push する前に一行だけ確認するようにしました。
# 「環境の date」と「意図したタイムゾーンの date」がズレていないか
diff <(date +%F) <(TZ=Asia/Tokyo date +%F) && echo "OK: 同一" || echo "要注意: タイムゾーン差あり"この一行が 要注意 を返すなら、そのスクリプトはコンテナでだけ壊れる候補です。たった9時間の差が、一日分のログを消すには十分でした。
無人運用は、止まらないことよりも「壊れたときに気づけること」のほうが大切だと、今は考えています。同じように複数の自動タスクを回している方の、静かなデータ欠損を一つでも減らせれば幸いです。