この記事はシリーズ「Claude Code で技術ブログを無人運用する — 無料章から読む仕組みの全体像」の第 4 回(全 6 回)です。
Claude Code のスケジュール実行だけで技術ブログ(Qiita 主軸・Zenn 試験投稿・X 告知・画像生成)が人手なしで回り続ける仕組みを、運用しているリポジトリの実測ログとコマンド結果で解説する連載です。Zenn 本『Claude Code で技術ブログを無人運用する』の無料章(第 1〜3 章・第 18 章・第 20 章)をもとにしており、各回は本の該当章へ戻れる導線を持ちます。掲載する数値と実行結果は各回の執筆時点で採取し直します。
仕組み全体を自分のリポジトリで組み直したい方へ: 無料章の続き(記事の型・品質ゲート・Qiita/Zenn/X の公開設計・無人実行・計測)を全 20 章の手順書として書いた Zenn Book を公開しています(有料 500 円・試し読みあり)。
シリーズ全体の目次
- 第 1 回 1 日 4 スロットのスケジュール実行で、技術ブログが人手なしで出続ける仕組みの全体像
- 第 2 回 ガバナンスの土台は公開ベースを当てるだけ。その上にブログ層を足す境界の引き方
- 第 3 回 無人ブログの現在地を 3 か所から数える: 台帳・公開ログ・エンゲージメント実績
- 第 4 回 Zenn でデプロイ成功なのに非公開・Qiita の 429: 無人運用で踏んだ 7 症状(この記事)
- 第 5 回 X の 403・承認待ちでスロットが空振り・git push が 403: 無人運用の拡散と運用の症状
- 第 6 回 本を Claude Code に読ませて自分のリポジトリに再現する: 読む順路・検証済み SHA・改訂の追随
はじめに
自動公開を組んだことがあれば、「エラーは出ていないのに記事が出ていない」場面に一度は出会っているはずです。git push は通り、デプロイ履歴は成功表示で、スケジュール実行の履歴も緑のままです。それでも公開 URL は 404 を返します。失敗の信号がどこにも出ないので、どこから疑えばよいかが分かりません。
この回では、本リポジトリの無人運用で実際に踏んだ公開まわりの症状を 7 つ、逆引きの形で並べます。各症状は「症状 → 原因 → 対処 → 判定コマンド」の順で書き、判定コマンドには実出力を添えました。症状名で探して、当てはまる項目だけを読めば足りるようにしてあります。X への拡散とスケジュール・git まわりの症状は第 5 回で扱います。
対象読者は、自分でも自動公開を組んでいて「動いているはずなのに出ない」を経験した人と、症状から原因へ直行したい人です。
掲載した出力は、2026-10-04(JST)に本リポジトリ(コミット 1b6862a)で採取したものです。実行環境は Linux のクラウド実行環境と Node v22.22.0 で、どのコマンドもファイルや公開状態を読むか自己テストを走らせるだけです(check:publish-stall だけは判定結果をログへ追記します)。投稿や課金を伴う処理は実行していません。
TL;DR
| # | 症状 | 原因の要点 | 判定コマンド | 正常時の出力 |
|---|---|---|---|---|
| T-01 | Zenn のデプロイは成功表示なのに公開 URL が 404 | 上限被弾はエラーにならず、サイレントに非公開になる | npm run audit:zenn |
ZENN_VERIFY OK |
| T-02 | 記事 1 本の不備で Zenn のデプロイ全体が止まる | title 70 文字超など。published: false も検証対象 |
npm run lint:zenn |
✅ Zenn フォーマット検証 OK |
| T-03 | Qiita 連載の回が Zenn 側にも出た | 投稿先の振り分けが Zenn の間隔しか見ていない | npm run check:series-zenn |
SERIES_ZENN_EXCLUSION OK |
| T-04 | Qiita が 429 を返し、以降の新規投稿が止まる | 新規作成専用の非公開制限。リトライしない設計 | cat scripts/qiita-cooldown.json |
ファイルが無い、または期限が過去 |
| T-05 | 429 も無いのに新規投稿がスキップされる | 直近 168 時間で 14 本の週次窓が満杯 | node scripts/lib/qiita-publish-logic.js --self-test |
passed |
| T-06 | スロットは正常なのに何日も出ていない | 入力側しか観測していない | npm run check:publish-stall |
PUBLISH_STALL OK(CAPPED も正常) |
| T-07 | 特定の記事だけが公開対象から消える | 連番の桁数を固定した判定 | npm run test:article-number |
すべて PASS |
- Zenn も Qiita も、上限に当たったときに分かりやすい失敗を返すとは限りません。成功表示と実際の公開状態を別の経路で突き合わせるコマンドを持っておくと、症状から原因まで 1 本の線でたどれます。
- Qiita の律速は日次上限ではなく直近 168 時間の窓です。429 は叩き直しても窓が空くまで通らないので、リトライせずに止めます。
- 「出しすぎ」を止めるゲートだけでは「1 本も出ていない」は見つかりません。出力側の観測点を別に置く必要があります。
症状から判定コマンドへの引き方は次のとおりです。
Zenn に出ない
T-01 デプロイは成功表示なのに、公開 URL が 404 のまま
症状: push もマージも通り、Zenn のデプロイ履歴も成功と表示されているのに、記事の公開 URL が 404 を返します。
原因: Zenn の投稿数上限は記事なら直近 24 時間、本なら直近 1 週間の窓で判定され、上限値は公開されていません。被弾してもデプロイは成功表示のままで、理由はダッシュボードのお知らせ欄にしか出ず、CLI のログにも GitHub 側にも残りません。1 回のデプロイで 1〜2 件ずつしか消化されないキューで待たされている場合も、外からの見え方は同じです。
対処: 公開できたかどうかを、push の成否ではなく公開側の事実で確かめます。本リポジトリでは、間隔ゲートの入力である投稿トラッカー(docs/zenn-daily-post.md)の記録と、Zenn API が返す公開状態を突き合わせています。上限で弾かれたぶんは枠を消費しないので、窓が明けてから [ci skip] を付けない新しいコミットを 1 つ載せます。同じコミットを push し直しても、デプロイは再トリガーされません。
判定コマンド:
$ npm run audit:zenn
ZENN_VERIFY DRIFT checked=10 drifts=1 — トラッカーが実態と食い違っている
❌ 未公開なのに投稿済み記録: 1211-mcp-typescript-server-minimal-guide(記録日 2026-09-29)
→ トラッカー(docs/zenn-daily-post.md)を実態に合わせて是正すること。
間隔ゲートはこのファイルを入力にするため、放置すると誤判定が累積する。
採取した日の出力は、ちょうどこの症状の実例でした。2026-09-29 に公開成功として記録した記事が、2026-10-04 の時点で HTTP 404 を返しています。audit:zenn はトラッカーと Zenn API の公開リストの食い違いを検出するところまでで、HTTP は叩きません。個別の記事は npm run verify:zenn -- --slug 1211-mcp-typescript-server-minimal-guide が API に無い記事の HTTP ステータスを取り、404(記事が存在しない)と 403(公開後に非公開化された疑い)を区別します。同じ記事に実行すると ZENN_VERIFY NOT_PUBLISHED slug=1211-mcp-typescript-server-minimal-guide http=404 と返りました。公開まで到達していなかったと読めますが、上限被弾かキューの滞留かは、この出力だけでは決められません。DRIFT の終了コードは 10 です。なお、この記事の執筆中にトラッカーを実態に合わせて是正したため、読者が今 audit:zenn を打っても同じ DRIFT にはならず OK になります。
T-02 記事 1 本の title が長いだけで、デプロイ全体が止まる
症状: デプロイ履歴に「デプロイ中断」と出て、問題のファイル以降が反映されないまま終わります。無関係な記事も、本の更新も巻き添えになります。
原因: Zenn はデプロイのたびにリポジトリ全体を検証し、重大と判定する違反が 1 件でもあるとそこで止まります。代表例が title の 70 文字上限です。文字数は UTF-16 のコードユニット数で数えるため、絵文字や一部の漢字は 2 文字になります。published: false の下書きも検証されるので、Zenn に出さない Qiita 専用記事でも無関係ではありません。上限被弾と違って枠は消費しません。
対処: push の前に全件を検証します。本リポジトリの検証ロジック(scripts/lib/zenn-format.js)は zenn-editor の実装値を写したもので、Zenn 側で重大扱いの違反を error(push をブロック)、警告扱いの違反を warn(表示のみ)に分けています。push 時のフックにも組み込んであります。
判定コマンド:
$ npm run lint:zenn
⚠️ Zenn 警告(デプロイは止まらないが直したほうがよい・2 件):
articles/325-agent-skills-addy-osmani-ai-coding-guide.md: topic "claude-code" に記号かスペースが含まれます(C++ は cpp、C# は csharp と書く)
articles/576-nodejs26-vs22-removed-api-compare.md: topic "node.js" に記号かスペースが含まれます(C++ は cpp、C# は csharp と書く)
✅ Zenn フォーマット検証 OK(1457 件・記事/本/チャプターの slug・title・topics・type・emoji・published ほか / 警告 2 件)
記事・本・チャプター 1,457 件を全件見て、残っているのは topics の記号 2 件の警告だけでした。警告はデプロイを止めない区分なので、最終行の ✅ で通過です。全件を見る設計のため、既存記事に error が残っていると無関係な push まで止まります。
T-03 Qiita 連載の回が、Zenn 側にも出てしまった
症状: Qiita の連載として企画した記事のうち 1 回だけが Zenn にも公開され、Zenn 側から見ると前後の回が存在しない連載になります。
原因: 本リポジトリの Zenn は、公開制限の閾値を測るための 2 日に 1 本の試験投稿枠です。投稿先の振り分けが「Zenn の間隔が空いているか」だけを見ていたため、たまたま順番が回ってきた連載の第 1 回が試験枠に乗り、Zenn に公開されました。
対処: 連載定義(content/series/*.json)の zennTestPost(既定は Zenn に乗せない)を入力にして、連載記事の frontmatter が published: true になっていれば検出します。文章のルールだけにせず、push 時のフックからも呼んでいます。NG が出たら該当記事を published: false に戻します。取り下げはデプロイキューを経由せずすぐ反映されますが、Zenn に届けるためにそのコミットには [ci skip] を付けません。
判定コマンド:
$ npm run check:series-zenn
SERIES_ZENN_EXCLUSION OK series=4 checked=38
連載定義 4 本・38 件を見て、Zenn に公開されている連載記事はありませんでした。
Qiita に出ない
T-04 Qiita が 429 を返し、そのあと新規投稿が止まる
症状: 新規記事の POST が 429 で失敗し、以降のスロットでも新規投稿だけがスキップされます。既存記事の更新は通ります。
原因: Qiita API の公式の利用制限は、認証済みリクエストで 1 時間あたり 1,000 回です(Qiita API v2 ドキュメント)。記事の新規作成にはこれとは別系統の制限があり、値は公開されていません。この制限で返る 429 は、間隔を置いて叩き直しても窓が空くまで通りません。そこで本リポジトリでは、新規 POST の 429 はリトライせず、その実行で残っている新規投稿を打ち切り、scripts/qiita-cooldown.json に停止期限を書き込みます。期限は被弾の回数に応じて段階的に延びます。
対処: 基本は期限が明けるのを待ちます。ファイルの中身は期限 1 行だけで、期限が過去なら停止は効いていません。手で解除するなら、ファイルを削除するか期限を過去の日時にします。
判定コマンド:
$ cat scripts/qiita-cooldown.json 2>/dev/null || echo '(クールダウン中ではない)'
{
"newPostsPausedUntil": "2026-10-02T05:23:14.229Z"
}
期限は UTC 表記で、JST に直すと 2026-10-02 14:23 です。採取日の 2026-10-04 から見ると過去なので、ファイルは残っていても新規投稿は止まっていません。実際、T-06 の出力では同日 12:05 JST に新規公開が記録されています。
T-05 429 も出ていないのに、新規投稿が全部スキップされる
症状: 公開ログに weekly_cap_reached が記録され、新規 POST が 1 本も実行されません。更新だけが適用されます。
原因: 本リポジトリの Qiita 公開の律速は、日次上限ではなく直近 168 時間のローリング窓です。公開ログの全期間を突き合わせると、429 が返ったのはすべて窓の中に新規投稿が 14 本あったときで、成功した投稿はすべて 13 本以下のときでした。そこで窓内の本数をログから数え、14 本に達していれば 429 を叩きにいく前に自分でスキップします。失敗した試行は窓を消費しません。日次上限の 2 本は、この 14 本を 7 日へ均等に配った値です。
対処: 異常ではないので待ちます。窓の中で最も古い投稿が 168 時間の外へ出れば、次のスロットから再開します。窓の数え方が壊れていないことは、公開ロジックの自己テストで確かめます。
判定コマンド:
$ node scripts/lib/qiita-publish-logic.js --self-test 2>&1 | tail -2
qiita-publish-logic self-test: 121 passed
121 件が通っています。第 3 回で数えたとおり、窓のゲートを入れた翌日以降の公開ログには、429 の代わりに weekly_cap_reached が残るようになりました。
T-06 スロットは正常に動いているのに、何日も記事が出ていない
症状: スケジュール実行の履歴は成功で、エラーの通知も来ていないのに、最後の Qiita 公開から 2 日以上が経っています。
原因: 観測点が入力側にしか無いことです。スロットの完了記録を見るハートビート(npm run check:heartbeat)は「スロットが起動して最後まで走ったか」を確かめるもので、記事が出たかどうかは見ていません。2026-08-29 12:07 JST を最後に Qiita の公開が 2 日続けてゼロになったときも、ハートビートは正常のままで、指摘を受けるまで誰も気づきませんでした。
対処: 出力側の観測点として、最後の新規公開からの経過時間を判定するコマンドを全スロットの先頭で回します。判定は公開処理を止めません。既定の閾値は 30 時間で WARN、48 時間で STALL(終了コード 10)です。正常運用は 1 日 2 本で最終スロットが 20:00 JST なので、公開の間隔は長くても前日 20:00 から翌日 09:00 までの約 13 時間に収まる、という計算から決めています。
判定コマンド:
$ npm run check:heartbeat
DISPATCH_HEARTBEAT OK slot=9:00 date=2026-10-04
$ npm run check:publish-stall
PUBLISH_STALL OK last="2026-10-04 12:05:03 JST" ago=4.0h consecutiveZeroDays=0 — 直近 4.0h 以内に公開実績あり
入力側も出力側も正常です。正常時には STALL が出ないので、閾値をオプションで下げて意図的に出したのが次の 2 つです。--no-log は通知済みの記録を残さないための指定です。
$ node scripts/check-publish-stall.js --warn-hours 1 --stall-hours 3 --no-log; echo exit=$?
PUBLISH_STALL STALL last="2026-10-04 12:05:03 JST" ago=4.0h consecutiveZeroDays=0 — 最後の公開から 4.0h(2日以上ゼロ)
次の一手: logs/qiita-post-log.jsonl の直近 fail/cooldown_set を確認する
(429 継続中なら scripts/qiita-cooldown.json の期限まで待つ。それ以外の errorClass なら原因調査)
exit=10
$ node scripts/check-publish-stall.js --warn-hours 2 --stall-hours 10 --no-log; echo exit=$?
PUBLISH_STALL WARN last="2026-10-04 12:05:03 JST" ago=4.0h consecutiveZeroDays=0 — 最後の公開から 4.0h(1日ゼロが確定)
次の一手: logs/qiita-post-log.jsonl の直近 fail/cooldown_set を確認する
(429 継続中なら scripts/qiita-cooldown.json の期限まで待つ。それ以外の errorClass なら原因調査)
exit=0
括弧内の「2日以上ゼロ」「1日ゼロが確定」は既定の閾値に対応する固定の説明文で、閾値を下げたこの実行では実際の経過時間(4.0 時間)とは一致しません。STALL だけが終了コード 10 を返し、WARN は 0 で様子見です。
もう 1 つの正常系が CAPPED です。公開ゼロでも直近 168 時間の窓が満杯(T-05)で説明がつくときに返り、終了コードは 0 です。今回の採取では窓が満杯でなかったため実出力は採れていませんが、スクリプトの仕様として、STALL と CAPPED を分けることで「止めたから出ていない」と「壊れて出ていない」を区別しています。直近 20 時間以内に通知済みの停止は KNOWN、判定材料が無いときは UNKNOWN を返します。
T-07 特定の記事だけが、公開対象から静かに消える
症状: articles/ にも public/ にもファイルがあるのに、Qiita への公開、X の告知、反応の集計のどこにも現れません。警告もエラーも出ません。
原因: 記事番号の桁上がりです。2026-09 に番号が 3 桁から 4 桁(1001-)へ繰り上がったとき、ファイル名の連番判定が /^\d{3}-/ と 3 桁固定で書かれていました。この正規表現は先頭 3 桁の直後にハイフンを求めるので、1001- は 100 の次が 1 で一致しません。その結果、5 本が公開・告知・集計の対象から外れました。滞留を見つけるはずのスタック検出も同じ判定を使っていたため、この滞留そのものを検知できませんでした。
対処: 連番の判定を scripts/lib/article-number.js の 1 か所に集め、パターンを「3 桁以上・上限なし」(/^\d{3,}-/)にしました。番号の大小比較は文字列ではなく数値で行います。文字列で比べると '1001' < '473' になるためです。シェルで [0-9][0-9][0-9]- のようなグロブを書かないのも同じ理由です。
判定コマンド:
$ npm run test:article-number 2>&1 | tail -1
✅ article-number self-test: 21 件すべて PASS
自己テストには、scripts/ と tools/ に 3 桁固定のパターンが再び現れていないかを調べる回帰検査も含めています。
7 症状を並べて見えた設計上の穴
7 つを並べ直すと、本リポジトリのゲートは「出しすぎ」を止める方向に偏って作られていました。Zenn の間隔ゲートも、Qiita の日次・週次・1 スロット 1 本の上限も、取り消しの効かない外部公開を防ぐためのもので、上限を超えそうなら止めます。止める側はかなり作り込んだ一方で、「1 本も出ていない」状態を見るものは、T-06 の出力側の観測点を後から足すまで存在しませんでした。上限のゲートは本数が多いときにしか発火しないので、本数がゼロのときは全部が沈黙します。ハートビートも緑のままで、どこからも信号が出ません。この状態が 2 日続きました。
もう 1 つの穴は、観測する側が観測される側と同じ前提に乗っていたことです。T-07 ではスタック検出が公開処理と同じ 3 桁固定の判定を使っていたので、落ちた 5 本は監視からも同時に消えました。T-01 では、間隔ゲートが入力にするトラッカーが push の成否をもとに書かれていました。トラッカーが「投稿済み」と誤って記録すれば次の試験投稿を不要に遅らせ、公開された記事を記録し漏らせば間隔を詰めすぎる側に誤ります。今回の audit:zenn の DRIFT は前者でした。
ここから引き出した方針は 2 つです。上限で止めるゲートを置いたら、同じ対象に「出ていない」を見る観測点を対で置くこと。そしてその観測点は、被観測側とは別の経路の事実(公開 URL の応答、公開ログの成功記録)で判定すること。どちらも文章のルールではなく、マーカー文字列と終了コードを返すコマンドにしてあるので、スロットの先頭で毎回同じように確かめられます。自分の自動公開に同じ穴が無いかを見るなら、上限ゲートの一覧の横に「その対象がゼロになったら、何が知らせてくれるか」を 1 行ずつ書き足してみてください。空欄になった行が、次に静かに止まる場所です。
次回扱う症状
第 5 回は、同じ型で X への拡散(403 で告知が出ない・記事 URL のリプライだけが落ちる)と運用(承認待ちでスロットが空振りする・git push が 403 になる)の症状を並べます。
この記事と本の関係
この回は、Zenn 本『Claude Code で技術ブログを無人運用する』の第 18 章「トラブルシューティング逆引き」の T-01〜T-07 をもとに、判定コマンドの出力をすべて 2026-10-04 に採り直して書いたものです。第 18 章は無料章で、第 18 章 から読めます。続きにあたるのは有料章で、第 8 章が Qiita への公開設計を、第 9 章が Zenn の上限とデプロイキューとの付き合い方を、第 7 章が push の前に止める品質ゲートを扱います。本には、Claude Code に読ませると自分のリポジトリで同じ仕組みを組めるように書いた仕様パック(第 19 章)も入っています。
参照
本リポジトリ内のファイル(リポジトリは非公開のため、パスだけを示します):
-
scripts/verify-zenn-publish.js/docs/zenn-daily-post.md: Zenn の実公開とトラッカーの突き合わせ(T-01) -
scripts/lib/zenn-format.js/scripts/check-article-slug.js: Zenn フォーマットの全件検証(T-02) -
scripts/check-series-zenn-exclusion.js/content/series/*.json: 連載記事の掲載先チェック(T-03) -
scripts/publish-qiita.js/scripts/lib/qiita-publish-logic.js/scripts/qiita-cooldown.json/logs/qiita-post-log.jsonl: Qiita の上限・クールダウン・公開ログ(T-04 / T-05) -
scripts/check-publish-stall.js/scripts/dispatch-heartbeat.js: 出力側と入力側の観測点(T-06) -
scripts/lib/article-number.js: 記事連番の判定(T-07) -
docs/rules/posting-limits.md/docs/rules/zenn-deploy-rules.md: 投稿上限と Zenn デプロイ頻度のルール
外部の資料:
- Qiita API v2 ドキュメント: 利用制限(認証済み 1 時間 1,000 回)
シリーズの前後の記事
- ⬅️ 前の記事: 第 3 回 無人ブログの現在地を 3 か所から数える: 台帳・公開ログ・エンゲージメント実績
- ➡️ 次の記事: 第 5 回 X の 403・承認待ちでスロットが空振り・git push が 403: 無人運用の拡散と運用の症状
仕組み全体を自分のリポジトリで組み直したい方は、無料章の続き(記事の型・品質ゲート・Qiita/Zenn/X の公開設計・無人実行・計測)を全 20 章の手順書として書いた Zenn Book(有料 500 円・試し読みあり)へどうぞ。