これは何
社内ツール(ブラウザで動く資料生成アプリ)を Claude Code で改修していたとき、
「毎回手でデプロイするのが面倒。変更したら勝手に本番へ上げてほしい」と丸投げした記録です。
最終的に 「Claude Code が応答を終えるたびに、変更があれば自動で本番デプロイする」 環境が
Stop フックで完成しました。途中、デプロイ先を盛大に間違えていたり、PowerShell の文字化けにハマったりしたので、その流れをほぼそのまま残します。
環境: Windows 11 / PowerShell 5.1 / Claude Code (Opus 4.7) / ConoHa(Plesk) + SSH デプロイ
ステップ1.「デプロイして」→ まず DryRun で気づいた地雷
「この変更を本番に上げて」と頼んだら、Claude はプロジェクト内の deploy.ps1(scp でアップするスクリプト)を見つけてきた。
いきなり実行せず、まず DryRun(アップ対象の確認だけ) を走らせたのが良かった。
アップ対象一覧に、こんなファイルが混ざっていた。
このはサーバー情報.md ← サーバーの認証情報!
.playwright-mcp\...yml ← 検証中に出た一時ファイル
WebTan提案スライドスキル_チーム配布版\...
このはサーバー情報.md が 公開ディレクトリにアップされる = https://~/このはサーバー情報.md で
誰でも閲覧できてしまう状態だった。デプロイの除外リストが甘かったのだ。
学び: デプロイは必ず DryRun から。「何が上がるか」を一覧で見てから本番を叩く。
ステップ2. そもそもデプロイ先が間違っていた
実行しようとすると mkdir: Permission denied で失敗。
調べると、deploy.ps1 のアップ先パスが現実のサーバー構成と全く合っていなかった。
| スクリプトの想定 | 実際 | |
|---|---|---|
| サーバー種別 | ConoHa WING | ConoHa の Plesk サーバー |
| Web ルート |
public_html/ 直下 |
public_html は読み取り専用
|
| 対象ディレクトリ | public_html/shiryou-maker |
(存在しない) |
find でサーバーを探しても shiryou-maker が見つからない。「過去にデプロイした URL」を教えてもらって判明したのは、
- 本当のパスは
public_html/<サブドメイン名>/shiryo-maker - しかもディレクトリ名は
shiryo-maker("u" が無い) という綴り違い
shiryou で探していたので、ずっと「未デプロイ」だと誤判定していた。
落とし穴: スクリプトのコメント(「ConoHa WING」)を鵜呑みにしない。
sshで実際のlsを取って構成を確認する。綴り1文字違いは検索で引っかからない。
正しいパスと除外リストに deploy.ps1 を修正し、DryRun でアップ対象が
必要な6ファイルだけになったのを確認してからデプロイした。
ステップ3.「これからは全部デプロイ前提で動いて」
ここで一言。
「基本的にこのプロジェクトフォルダ内はすべてデプロイ前提で動いてほしい」
これは 「変更が終わったら、いちいち聞かずにデプロイしていい」 という恒久ルールの依頼。
Claude はこれをメモリ(永続設定)に保存して、以降は確認なしでデプロイするようになった。
ステップ4.「メモリって再起動したら変わる?」— メモリ と フックの違い
ここが一番の山場だった。こう質問した。
「メモリーに保存ってことは、開き直したらまた変わったりしますか?」
返ってきた説明が分かりやすかったので要約する。
| メモリ(preference) | フック(hook) | |
|---|---|---|
| 消えるか | 消えない(ファイル永続) | 消えない |
| 性質 | Claude が読んで“従う”指針 | イベントで機械的に実行される仕組み |
| 確実性 | 基本従うが 100% ではない | イベント発火で確実に走る |
つまり「何があっても確実に、変更したら自動デプロイ」を保証したいなら、
メモリではなく hook(settings.json の自動実行設定) にする必要がある、と。
「それで(フックで)」と即決した。
ステップ5. Stop フックで自動デプロイを組む
なぜ Stop フックか
候補は2つ。
- PostToolUse(Write/Edit ごと): 1編集ごとにデプロイ → 連続編集で何度も走って遅い
- Stop(応答を終えたとき): 1ターンに1回だけ → 全編集が終わってから1回デプロイ ◎
後者を選択。ただし Stop は 何も変更していない返答でも発火するので、
そのまま毎回 scp すると無駄な再アップが起きる。
変更があった時だけ走らせるガード
そこで、デプロイ対象6ファイルの更新時刻を「前回デプロイ時刻(マーカー)」と比較し、
変更があった時だけ deploy.ps1 を呼ぶラッパーを作った。
# deploy-hook.ps1(要点)
$Marker = Join-Path $Root ".deploy-marker"
$Watch = @("config.js","convert.php","favicon.png","index.html",
"logo-webtan-transparent.png.webp","save.php") |
ForEach-Object { Join-Path $Root $_ } | Where-Object { Test-Path $_ }
$markerTime = if (Test-Path $Marker) { (Get-Item $Marker).LastWriteTime } else { [datetime]::MinValue }
$changed = @($Watch | Where-Object { (Get-Item $_).LastWriteTime -gt $markerTime })
if ($changed.Count -eq 0) { exit 0 } # 変更なし → 何もしない
& (Join-Path $Root "deploy.ps1") *> $null
Set-Content -Path $Marker -Value (Get-Date -Format "o") -Encoding utf8
Write-Output (@{ systemMessage = "Auto-deployed ($($changed.Count) files)" } | ConvertTo-Json -Compress)
ハマり: PowerShell 5.1 の日本語文字化け
最初、スクリプトの systemMessage を日本語で書いたら、こうなった。
Unexpected token '繝輔ぃ繧、繝ォ螟画峩讀懃衍・・' in expression or statement.
原因は、エディタが BOM なし UTF-8 で保存 → Windows PowerShell 5.1 がそれを
システムの ANSI(Shift-JIS)として読んだため。日本語コメント/文字列が全滅した。
対処: フックから呼ぶ
.ps1は ASCII のみで書く(メッセージも英語)。
これで文字コード事故を根本回避できる。
settings.json への登録
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "powershell.exe -NoProfile -ExecutionPolicy Bypass -File \"C:\\...\\deploy-hook.ps1\"",
"timeout": 120,
"statusMessage": "本番へ自動デプロイ中..."
}
]
}
]
}
}
登録前に パイプテスト(フックに渡る JSON を手で流し込む)で動作確認した。
echo '{}' | powershell.exe -NoProfile -ExecutionPolicy Bypass -File "...\deploy-hook.ps1"
# 1回目: 6ファイルデプロイ&JSON出力 / 2回目: 変更なしで無音 → OK
ステップ6. 最後の落とし穴: フックは追加直後は動かない
設定したのに、次の変更が自動デプロイされなかった。本番を確認すると古いまま。
原因は、Claude Code の 設定ウォッチャーが、フック追加後すぐには再読込しないこと。
解決は簡単で、
-
/hooksを一度開く(設定が再読み込みされる)か、 - Claude Code を再起動する
/hooks を開くと上部に「2 hooks configured」と出て、ここで初めてフックが有効になった。
(このメニュー自体は読み取り専用。Esc で閉じるだけでいい)
完成した仕組み
ローカルで index.html などを編集
│ Claude Code が応答を終える(Stop イベント)
▼
deploy-hook.ps1 が変更を検知
│ 変更あり → deploy.ps1(scp で6ファイル)
▼
ConoHa(Plesk) の公開ディレクトリへ自動反映
- 変更がない返答ではスキップ(無駄な再アップなし)
- 機密ファイル・一時ファイル・配布物はデプロイ除外
- 「デプロイ前提で動く」方針はメモリにも記録 → 別セッションでも引き継がれる
まとめ(学びのポイント)
- デプロイは DryRun から。 公開ディレクトリに機密ファイルが混ざる事故を未然に防げる。
-
スクリプトのコメントを信じすぎない。 実サーバーを
ssh lsで確認。綴り1文字違いは検索に出ない。 - 「確実に自動実行」はメモリではなくフック。 メモリは“従う指針”、フックは“走る仕組み”。
- フックから呼ぶ PowerShell は ASCII のみ。 PS 5.1 の BOM なし UTF-8 文字化けを回避。
-
Stop フックは追加直後は無効。
/hooksを開く or 再起動で有効化。
「面倒だから自動化して」と丸投げしただけで、ここまで設計・検証込みでやってくれるのは助かる。
特に DryRun で機密ファイルに気づいて止まってくれたのが一番ありがたかった。