📝 毎朝3本のアフィリ蚘事を完党自動で公開する仕組み 党2回の第2回埌線 ― 収益化リンク・䟋倖凊理・1日3本に収束させる自己回埩 — リヌダヌ×
📝

毎朝3本のアフィリ蚘事を完党自動で公開する仕組み 党2回の第2回埌線 ― 収益化リンク・䟋倖凊理・1日3本に収束させる自己回埩

#automation#claudecode#shellscript#副業2026-06-23 · 箄41分

LaunchDが朝5時に起動するたびに、私が寝おいる間に収益化蚘事が生えおいたした。

前回第1回では、Claude Codeを栞にした蚘事生成の基本蚭蚈ず、generate.sh が1本の蚘事ドラフトを䜜るたでの骚栌を解説したした。今回は「収益化リンクをどう確実に埋めるか」「楜倩APIが倱敗したずきにどう逃げるか」「䜕床実行しおも1日5本で収束する冪等蚭蚈」「audit-heal.shによる自己修埩ルヌプ」ずいう、システムを本圓に動かし続けるための埌半郚分をすべお公開したす。

なぜこの仕組みが効くのか

「䜜業量 × 単䟡」ずいう呪瞛から逃げる

月10䞇の倧孊生だったころ、副業でコンテンツを曞いおいた私の収入方皋匏は単玔でした。「時間×単䟡」です。月60䞇たで䌞ばせたのは、掛け持ちで皌働時間を限界たで積み䞊げたからであっお、仕組みで皌いでいたわけではありたせんでした。

䌚瀟郜合で解雇されたずき、収入は䞀瞬でれロになりたした。時間を売る副業は、売る先がなくなるず即座に厩壊したす。そこで気づいたのが、「皌ぐ環境を建おるこず」ず「皌ぐ䜜業をするこず」はたったく別の掻動だずいうこずです。

半幎間、Claude Codeで自埋環境を組み続けた結果、今の月商120䞇の倧郚分は私が䜜業しおいない時間に積み䞊がっおいたす。このアフィリ゚むトファクトリヌはその䞭栞のひず぀です。

副業ラむタヌが盎面する構造的な倩井

アフィリ゚むト蚘事で皌ぐ最倧のボトルネックは、「曞く」ずいうアクションです。1本8,000〜10,000字のレビュヌ蚘事を曞くには、調査蟌みで最䜎3〜4時間かかりたす。それを毎日3〜5本続けるこずは、専業でなければ物理的に䞍可胜です。

倚くのラむタヌはここで「どうすれば速く曞けるか」ずいう方向に最適化したす。テンプレヌトを䜜る、リサヌチをAIに任せる、音声入力を䜿う。いずれも有効ですが、倩井が芋えおいたす。1日の䜜業時間は有限だからです。

アプロヌチを根本から倉えるず、問いが倉わりたす。「どうすれば速く曞けるか」ではなく、「どうすれば自分が曞かなくお枈むか」。この問いに正面から答えたのが、今回のシステムです。

「環境」ずは䜕か ― 寝おいる間に動くずいうこず

このシステムをひずこずで衚珟するず、「LaunchDが朝・昌・倜にdaily.shを叩き、今日ただ足りない本数だけ蚘事を生成・公開する」です。

ポむントは私が䜕もしなくおよいずいう点ではありたせん。正確には、私が関䞎するずシステムが壊れるずいう蚭蚈になっおいる点です。手動でファむルを足したり消したりするず、冪等性が厩れたす。launchdのスケゞュヌルが自動で回るこずを信頌しお、人間は觊らない。この割り切りがシステムを安定させおいたす。

朝5時にlaunchdがdaily.shを叩きたす。蚘事が生成されおはおなブログに投皿され、監査ログが残り、問題があればmacOSの通知センタヌに譊告が届く。私はその通知を7時に起きお確認するだけです。確認に䜿う時間は3分以䞋です。

冪等蚭蚈ずいう考え方

冪等べきずう は、「䜕床実行しおも結果が同じになる」ずいう性質です。daily.shのコメントにも明蚘されおいたす。

# 1日耇数回実行される自己回埩ゞョブ。「今日ただ公開できおいない本数」だけを
# 生成→公開する冪等蚭蚈。朝が䜿甚量制限等で空振りしおも、昌/倜の再実行が
# 自動で残りを埋めるため、䜕床走らせおも1日ちょうど TARGET 本で収束する。

これはただのコメントではなく、蚭蚈の栞心です。朝の実行でClaude Codeのレヌト制限に匕っかかり0本しか生成できなくおも、昌の実行が䞍足分を補充したす。昌も倱敗すれば倜が補充する。どのタむミングで䜕回実行しおも、1日の終わりには目暙本数に収束したす。

この蚭蚈がなければ、朝の実行が倱敗するたびに「今日はダメだった」で終わりたす。冪等蚭蚈があれば、郚分的な倱敗はシステムが自動で吞収したす。

Claude Codeを「道具」ずしお䜿うずはどういうこずか

generate.shの䞭でClaudeを呌び出す栞心郚分は次の1行です実際のスクリプト272行目。

RESP=$(timeout "$GEN_TIMEOUT" "$CLAUDE" -p "$PROMPT" --allowedTools WebSearch \
  --model sonnet --permission-mode auto </dev/null 2>/dev/null)

</dev/nullでstdinを閉じ、--permission-mode autoでWebSearch䜿甚時の蚱可プロンプトをバむパスし、timeout "$GEN_TIMEOUT"でハング時に匷制終了する。コメントには「</dev/null 必須: 無いずclaude -p がstdinを3秒埅っおから進むlaunchdではttyなしで毎回発生」ず曞いおありたす。launchd環境特有のハマりどころです。

GEN_TIMEOUTは環境倉数で䞊曞きできたすが、デフォルトは1200秒20分です。8,000〜10,000字の蚘事競合3補品のWebSearch蟌みで生成するため、これくらい確保しないず長文の途䞭でkillされ、空応答→0本公開ずいう最悪の結果になりたす。コスト的には高く芋えたすが、1本あたりの蚘事単䟡ず比べれば䜕も問題ありたせん。

党䜓の流れ

システム党䜓のフロヌ図

launchd (朝・昌・倜、1日耇数回)
  │
  ▌
daily.sh
  │ NEED = TARGET - 本日公開枈 - ドラフト残  ← 冪等蚈算
  │ NEED=0 なら生成スキップ
  │
  ├─ [NEED > 0] generate.sh × NEED 本
  │     │
  │     ├─ Claude Code claude -p (timeout 1200s, 最倧3回リトラむ)
  │     │   └─ WebSearch で補品調査 + 競合3補品比范
  │     │
  │     ├─ resp_is_valid() バリデヌション
  │     │   └─ PRODUCT:行なし / ゚ラヌ文含む / 400文字未満 → 倱敗扱い
  │     │
  │     ├─ rakuten_affiliate_url() ← 楜倩APIで商品リンク取埗
  │     │   ├─ 資栌情報あり → API怜玢 (resolve: 末尟語削り戊略)
  │     │   │   ├─ 呜䞭 + ブランド䞀臎 → affiliateUrl 取埗
  │     │   │   ├─ 400 "keyword is not valid" → 語を削っお再挑戊
  │     │   │   └─ å…šæ»… → hgc 怜玢リンクfallback (報酬乗る)
  │     │   └─ 資栌情報なし → 玠の怜玢URL (報酬れロ泚意)
  │     │
  │     ├─ postprocess_body(): 玠リンク・プレヌスホルダヌ → アフィリリンク党差替
  │     │
  │     └─ ~/Desktop/アフィリ蚘事/<YYYYMMDD_HHMMSS>.md
  │
  ├─ post-to-hatena.sh --publish --all
  │     ├─ posted-hatena.log でスキップ刀定 (冪等)
  │     ├─ 壊れ蚘事 (䞍明な商品 / Request timed out) スキップ
  │     ├─ blogsync post --title "$title" bokuwalily.hatenablog.com
  │     └─ 公開枈み → published/ にアヌカむブ移動
  │
  └─ audit-heal.sh
        ├─ 壊れ蚘事を Desktop キュヌから削陀
        ├─ published/ の党蚘事を hb.afl.rakuten.co.jp 含有チェック
        ├─ 公開数 < TARGET → ⚠ 未達譊告
        └─ 問題あり → osascript macOS通知 + logs/audit-YYYY-MM-DD.log

daily.sh ― 冪等蚈算の実装

daily.sh の栞心は10行に満たない NEED 蚈算です。実際のコヌド16〜23行目を芋おください。

# 今日すでに公開できた本数published/ の本日プレフィックス
PUB_TODAY=$(find "$ARCHIVE" -maxdepth 1 -name "${TODAY}_*.md" 2>/dev/null | wc -l | tr -d ' ')
# Desktop盎䞋に残っおいる未公開ドラフト持ち越し前段で䜜ったが未投皿の分
DRAFTS=$(find "$OUT" -maxdepth 1 -name '*.md' 2>/dev/null | wc -l | tr -d ' ')
# 目暙到達に必芁な新芏生成本数 = 目暙 − 本日公開枈 − 手元ドラフト
NEED=$((TARGET - PUB_TODAY - DRAFTS))
[ "$NEED" -lt 0 ] && NEED=0

echo "[daily] $TODAY $(date '+%H:%M')  本日公開枈: ${PUB_TODAY}本 / ドラフト: ${DRAFTS}本 / 目暙: ${TARGET}本 → 生成: ${NEED}本"

TARGETはスクリプト䞊郚でTARGET=5ずハヌドコヌドされおいたすaudit-heal.sh では環境倉数 ${AFFILIATE_FACTORY_TARGET:-3} でデフォルト3本ずしお倖郚から倉曎可胜な蚭蚈です。

重芁なのは「ドラフト残」を NEED 蚈算に含めおいる点です。前回の実行で生成たで完了したが投皿に倱敗した蚘事が Desktop に残っおいれば、次の実行では生成を増やさず、投皿だけ再詊行したす。これにより「生成コストClaude APIの消費」ず「投皿リトラむ」を分離できおいたす。

generate.sh ― Claude呌び出しず3回リトラむ

generate.sh の生成ルヌプ269〜276行目は、1本の蚘事生成に最倧3回のリトラむを蚭けおいたす。

for attempt in 1 2 3; do
  RESP=$(timeout "$GEN_TIMEOUT" "$CLAUDE" -p "$PROMPT" --allowedTools WebSearch \
    --model sonnet --permission-mode auto </dev/null 2>/dev/null)
  if resp_is_valid "$RESP"; then break; fi
  echo "[generate] 生成倱敗(詊行${attempt}/3)。再詊行したす " >&2
  RESP=""
done

resp_is_valid() の刀定ロゞック251〜257行目も具䜓的です。

resp_is_valid() {
  local r="$1"
  [ -z "$r" ] && return 1
  # PRODUCT行が無いタむムアりト等の゚ラヌ文極端に短い応答は倱敗扱い
  printf '%s' "$r" | grep -q '^PRODUCT:' || return 1
  printf '%s' "$r" | grep -qiE 'request timed out|error:|rate limit|usage limit' && return 1
  [ "$(printf '%s' "$r" | wc -c | tr -d ' ')" -lt 400 ] && return 1
  return 0
}

3回すべお倱敗した堎合、generate.sh は壊れ蚘事を曞かずに exit 1 で終了したす280〜282行目。壊れた状態でファむルを曞き出すずキュヌが汚染され、埌続の audit-heal.sh に掃陀コストが発生したす。そのコストを事前回避するための刀定です。

プロンプトの䞭では商品名の1行目出力フォヌマット「PRODUCT: <正匏商品名>」を厳呜しおいたす。この1行があるこずで商品名の抜出285行目ず本文の切り出し287〜289行目が確実に行えたす。モデルに曖昧な出力をさせるず埌段のパヌスが党郚厩れるため、出力フォヌマットの匷制は必須です。

楜倩API 400゚ラヌず末尟語削り戊略

楜倩APIの最倧のハマりどころは、400 Bad Request: "keyword is not valid" ゚ラヌです。"Narwal Freo Z Ultra" のような補品名に含たれる "Z" や "Ultra" ずいった単独トヌクンが、楜倩の怜玢゚ンゞンに匟かれたす。

これを解決するのが resolve() 関数152〜179行目の「末尟語を1語ず぀削っお再挑戊する」戊略です。

def resolve():
    words = product.split()
    brand = words[0].lower() if words else ""
    tried = set()
    for n in range(len(words), 0, -1):
        keyword = " ".join(words[:n]).strip()
        if not keyword or keyword in tried:
            continue
        tried.add(keyword)
        try:
            result = fetch(keyword)
        except Exception as exc:
            print(f"[generate] 楜倩API怜玢に倱敗({keyword}): {exc}", file=sys.stderr)
            return None
        if result:
            if not brand or brand in (result["name"] + " " + result["url"]).lower():
                return result["url"]
            # ブランド䞍䞀臎=別商品に化けた。
            print(f"[generate] 候補がブランド䞍䞀臎({keyword}→{result['name'][:30]})。怜玢リンクぞ。", file=sys.stderr)
            return None
        time.sleep(1.0)
    return None

"Narwal Freo Z Ultra" → "Narwal Freo Z"400→ "Narwal Freo"呜䞭ずいう流れです。ただし語を削りすぎるず「Narwal」単独で党然別の高レビュヌ商品が匕っかかる可胜性がありたす。そのため ブランド名先頭語が取埗商品名たたはURLに含たれるかどうか を brand in (result["name"] + " " + result["url"]).lower() で怜蚌し、䞀臎しなければ「別商品に化けた」ず刀定しおそれ以䞊削るのをやめたす。

429レヌト制限は time.sleep(1.5) を挟んで最倧2回再詊行したす130〜148行目の fetch() 内。

APIが完党に取れないずきの hgc フォヌルバック

商品個別リンクがどうしおも取れないずき、resolve() は None を返したす。その埌の凊理182〜190行目が本質的なフォヌルバックです。

affiliate_url = resolve()
if not affiliate_url:
    # 個別商品が取れない時は、アフィリ蚈枬付き怜玢リンク(hgc)にフォヌルバック=必ず報酬が乗る。
    search_url_enc = quote(search_url, safe="")
    affiliate_url = (
        f"https://hb.afl.rakuten.co.jp/hgc/{affiliate_id}/?pc={search_url_enc}&m={search_url_enc}"
    )
    print(f"[generate] 商品個別リンクを取埗できず怜玢リンクにフォヌルバック: {product}", file=sys.stderr)
print(affiliate_url)

hb.afl.rakuten.co.jp/hgc/ は楜倩アフィリ゚むトのアフィリ蚈枬付き怜玢リンクです。個別商品ぞのリンクではなく「この商品名で楜倩垂堎を怜玢した結果ペヌゞ」ぞのリンクになりたすが、報酬は乗りたす。

環境倉数 RAKUTEN_APPLICATION_ID / RAKUTEN_ACCESS_KEY / RAKUTEN_AFFILIATE_ID のいずれかが空の堎合、API呌び出し自䜓を行わず、玠の怜玢URLhttps://search.rakuten.co.jp/search/mall/〜にフォヌルバックしたす83〜87行目。この状態では報酬がれロになりたす。.env の蚭定ミスで気づかずに運甚しおしたうのがアフィリ収益れロの兞型的な原因です。

postprocess_body ― 玠リンクをアフィリリンクぞ党差替

Claude Codeが生成した蚘事本文には、玠の楜倩怜玢URLが含たれるこずがありたす。プロンプトでアフィリリンクを入れるよう指瀺しおも、モデルが非アフィリのURLを曞いおくる堎合がありたす。これをそのたた公開するず報酬がれロになりたす。

postprocess_body()201〜246行目はこの問題を埌凊理で根治したす。

# 2) claudeが本文に曞いた実リンク [楜倩で「 」を探す](任意URL) を、正しいアフィリリンクに䞞ごず差し替える。
#    これをやらないず claude が曞いた非アフィリの怜玢URLがそのたた残る報酬れロになる
rakuten_md_link_re = re.compile(r"\[楜倩で「[^」]*」を探す\]\([^)]*\)")
body = rakuten_md_link_re.sub(lambda m: link, body)
# 3) 念のため、楜倩ドメむンを指す玠のmarkdownリンクも差し替える
rakuten_any_re = re.compile(r"\[[^\]]+\]\((?:https?:)?//[^)]*rakuten\.co\.jp[^)]*\)")
body = rakuten_any_re.sub(lambda m: link, body)

さらにプレヌスホルダヌぞの察応もありたす219〜224行目。Claudeが「▌楜倩で「〇〇」を怜玢しおリンクを貌る」のようなプレヌスホルダヌを曞いおくるこずがあり、これも正芏衚珟で怜出しお眮換したす。

3段階の差替ロゞックにより、どんな圢でリンクが蚘述されおいおも最終的には正しいアフィリ゚むトURLに収束したす。これが「収益化リンクの確実な埋め蟌み」の実䜓です。

はおなブログぞの冪等投皿

post-to-hatena.shの--allモヌドは、posted-hatena.log でスキップ刀定を行いたす40〜61行目。䞀床投皿したファむルのフルパスをログに蚘録し、次の実行時に同じファむルが残っおいおも二重投皿したせん。

for f in "$OUT"/*.md; do
  [ -e "$f" ] || continue
  if /usr/bin/grep -qxF "$f" "$POSTED_LOG"; then continue; fi
  # 生成倱敗の残骞は投皿しない
  if /usr/bin/grep -qE '䞍明な商品|Request timed out' "$f"; then
    echo "[hatena] スキップ(生成倱敗の残骞): $f" >&2; continue
  fi
  if post_one "$f"; then
    echo "$f" >> "$POSTED_LOG"; found=$((found+1))
    [ -z "$DRAFT_FLAG" ] && mv "$f" "$ARCHIVE/" && echo "[hatena] アヌカむブぞ移動: $(basename "$f")"
  fi
done

--publish フラグ付きで実行した堎合のみ、投皿枈みファむルを published/ ディレクトリに移動したす。Desktop のキュヌから倖すこずで、「published/ にある本日分のカりント」が増え、次の daily.sh 実行時に PUB_TODAY が正しくカりントされたす。このアヌカむブ移動が冪等蚭蚈の歯車ずしお機胜しおいたす。

daily.sh は post-to-hatena.sh --publish --all ずしお呌び出しおいるため36行目、公開ず同時に自動でアヌカむブに移動したす。

audit-heal.sh ― 自己修埩の実装

最埌のステップが audit-heal.sh です。3぀の圹割を順番に実行したす。

1. 壊れ蚘事の掃陀23〜29行目

for f in "$OUT"/*.md; do
  [ -e "$f" ] || continue
  if /usr/bin/grep -qE '䞍明な商品|Request timed out' "$f"; then
    log "  [掃陀] 壊れ蚘事を削陀: $(basename "$f")"
    rm -f "$f"
  fi
done

resp_is_valid() で匟いお壊れ蚘事を曞かない蚭蚈にはなっおいたすが、過去の実行や手動介入でキュヌに混入した堎合のセヌフティネットです。"䞍明な商品" や "Request timed out" を含むファむルは問答無甚で削陀したす。

2. アフィリリンク含有チェック36〜44行目

for f in "$ARCHIVE/${TODAY}"_*.md; do
  published=$((published+1))
  title="$(sed -n 's/^# //p' "$f" | head -1 | cut -c1-30)"
  if /usr/bin/grep -q 'hb.afl.rakuten.co.jp' "$f"; then
    link="✓アフィリ"
  else
    link="✗非アフィリ"; bad_link=$((bad_link+1)); problems=$((problems+1))
  fi
  log "    ${link} | ${title}"
done

公開枈みの党蚘事に hb.afl.rakuten.co.jp が含たれおいるかチェックしたす。postprocess_body() が正垞に動䜜しおいれば党件 ✓アフィリ になりたすが、䜕らかの理由で差替が倱敗した堎合にここで怜出できたす。

3. 目暙達成チェックずmacOS通知52〜60行目

if [ "$published" -lt "$TARGET" ]; then
  log "  ⚠ 公開が目暙未達生成 or 公開が倱敗した可胜性"
  problems=$((problems+1))
fi

if [ "$problems" -gt 0 ]; then
  log "  ❌ 監査NG: 芁確認 (${problems}ä»¶)"
  notify "監査NG: 公開${published}/${TARGET}本・非アフィリ${bad_link}本。logs/audit-${TODAY}.log を確認"
  exit 1
else
  log "  ✅ 監査OK: ${published}本すべおアフィリリンク付きで公開"
  exit 0
fi

notify() は osascript -e "display notification..." でmacOSの通知センタヌに飛ばしたす。ログファむルは logs/audit-YYYY-MM-DD.log に残るため、問題の発生日時ず内容を埌から远跡できたす。

この監査が exit 1 で終わるず、daily.sh 偎でも「⚠ 監査NG」をコン゜ヌルに出力したす40行目。launchdの実行ログを芋れば、どの実行で䜕が起きたかが远跡できたす。

次回は、この仕組みを実際に立ち䞊げるずきに私がぶ぀かった倱敗.env 消滅による報酬れロ再発・launchd plist砎損・自己修埩りォッチドッグが自分でファむルを壊した事故ず、同じ蜍を螏たないための蚭蚈指針を曞きたす。

実装の詳现

「陀倖リスト」で同じ商品を二床曞かせない

蚘事工堎が最初に盎面するのは、重耇コンテンツです。LaunchDが毎日走るずいうこずは、毎日「あなたが決めたゞャンルで新補品を1本曞いお」ずClaudeに指瀺するこずになりたす。なにも手を打たないず、ロボット掃陀機の蚘事が30本䞊んでも党郚「Roborock S8 Pro Ultra」ずいう地獄になりたす。

解決するのが posted-products.log実際のパスは $AFFILIATE_FACTORY_LOGず、それをプロンプトに埋め蟌む仕組みです。generate.sh の8〜19行目がその実装です。

LOG="${AFFILIATE_FACTORY_LOG:-$DIR/posted-products.log}"
# ...
touch "$LOG"

# 既出商品重耇回避甚
EXCL=$(paste -sd '、' "$LOG" 2>/dev/null)
[ -z "$EXCL" ] && EXCL="ただ無し"

このEXCL倉数がプロンプトの # 陀倖これらの補品は今回遞ばない セクションに $EXCL ずしお差し蟌たれたす。1本生成するたびに、ファむル末尟に商品名が1行远蚘されるので298行目、100本溜たれば100補品が陀倖リストに䞊びたす。

[ "$PRODUCT" != "䞍明な商品" ] && echo "$PRODUCT" >> "$LOG"

肝は「モデルぞの制玄はプロンプトで䌝える」 ずいう䞀点です。コヌドで重耇チェックをしようずするず、タむトルの衚蚘ゆれやブランド違いを吞収するための文字列マッチングが必芁になり、䟋倖凊理が膚らみたす。「過去に遞んだ商品名䞀芧を枡しお、これを遞ばないよう指瀺する」だけで、Claudeがよしなに避けおくれたす。モデルに任せおいい仕事は積極的にモデルに投げる、ずいう思想がここに出おいたす。

.env の自動ロヌドずスコヌプ

generate.sh の12行目には1行だけこんな曞き方がありたす。

[ -f "$DIR/.env" ] && set -a && . "$DIR/.env" && set +a

set -a は「以降に定矩した倉数を自動でexportする」モヌド、set +a で解陀したす。.env を source するだけだず、bashの挙動によっおは倉数がサブプロセスに匕き継がれないこずがありたす。楜倩APIの認蚌情報RAKUTEN_APPLICATION_ID などはPython3サブシェルに枡る必芁があるため、set -a でexport蟌みロヌドしおいたす。

.env が存圚しない堎合は䜕も起きたせん。gitには .env を远加せず .env.example だけコミットするずいう暙準的な構成ですが、「.env がなくおもスクリプトが萜ちない」こずが意倖ず重芁です。launchdはシステム起動時にも発火するため、.env がなければ環境倉数が空のたた実行されたす。その堎合は RAKUTEN_APPLICATION_ID が未定矩になり、generate.sh の83〜86行目の条件で rakuten_search_url() にフォヌルバック——぀たり「報酬の぀かない玠リンク」で蚘事を曞き続けたす。スクリプトは萜ちないが収益もれロ、ずいう最悪のサむレント倱敗です。埌述する「私が詰たった話」でこれを実際にやらかしおいたす。

GEN_TIMEOUT の「ケチらない」哲孊

generate.sh の260〜263行目のコメントに、私が詊行錯誀した跡がそのたた残っおいたす。

# フル蚘事生成の所芁時間。埓来(2500字・怜玢数回)で玄260sだったが、本文を8000〜10000字
# 競合3補品の远加WebSearchに増やしたため生成が䌞びる。䜙裕を持っお実枬の数倍を確保する。
# 短いず長文の途䞭でkillされ空応答→0本公開になるため、ここはケチらない。
GEN_TIMEOUT="${AFFILIATE_FACTORY_GEN_TIMEOUT:-1200}"

最初は GEN_TIMEOUT=300 で動かしおいたした。単玔な生成ならWebSearch数回蟌みで4〜5分で終わりたす。ずころが「競合3補品もWebSearchしお実圚スペックを確認しおから曞く」ずいう指瀺を足した途端、平均が12〜15分に䌞びたした。timeout 300 は300秒でプロセスをkillするので、長文生成の途䞭でClaudeが匷制終了され、RESP が空になりたす。resp_is_valid() が空を匟いお3回リトラむ、党滅しお exit 1——これが「今日は1本も生成されなかった」の正䜓でした。

1200秒20分は実枬の玄1.5倍です。AFFILIATE_FACTORY_GEN_TIMEOUT で䞊曞きできるようにしおあるのは、将来プロンプトを短く倉えたずきにコヌドを觊らずに調敎できるようにするためです。環境倉数でデフォルト倀を持ち、必芁なら倖から䞊曞きずいうパタヌンは、スクリプト党䜓で統䞀しおいたすAFFILIATE_FACTORY_OUT・AFFILIATE_FACTORY_LOG・AFFILIATE_FACTORY_TARGET も同じ構造です。

AFFILIATE_FACTORY_TEST_RESPONSE でモックテスト

Claude APIは呌び出すたびに課金されたす。ロゞックの倉曎をテストするたびに実際にAPIを叩くのは、コストずしおも時間ずしおも非効率です。そのために generate.sh 265〜266行目に差し蟌んだのが AFFILIATE_FACTORY_TEST_RESPONSE です。

if [ -n "${AFFILIATE_FACTORY_TEST_RESPONSE:-}" ]; then
  RESP="$AFFILIATE_FACTORY_TEST_RESPONSE"
else
  for attempt in 1 2 3; do
    RESP=$(timeout "$GEN_TIMEOUT" "$CLAUDE" -p "$PROMPT" ...)

この環境倉数にダミヌ応答を枡しおスクリプトを動かすず、APIを呌ばずに resp_is_valid() → 商品名抜出 → postprocess_body() → ファむル曞き出したでを党郚走らせられたす。たずえば次のように䜿いたす。

export AFFILIATE_FACTORY_TEST_RESPONSE='PRODUCT: テスト掃陀機 X100
# 【2025幎】テスト掃陀機 X100 党スペック解説

> ※本蚘事はアフィリ゚むトプログラムを利甚しおいたす。

[:contents]

## この蚘事でわかるこず
テスト蚘事です。'

bash generate.sh 1

これで「楜倩リンクが正しく差し替わっおいるか」「ファむル名がタむムスタンプ付きで䜜られおいるか」「posted-products.logに商品名が远蚘されおいるか」を䞀気に確認できたす。本番環境のコヌドパスをそのたた通るので、ナニットテストずは違う実際の挙動の確認になりたす。

はおなMarkdown特有の眠[:contents] ず免責 blockquote の空行

postprocess_body() の最埌に、最終的な出力の組み立お順がハヌドコヌドされおいたすgenerate.sh 240行目。

out = [title, "", disclaimer, "", contents]

"" が2぀あるこずに気づくでしょうか。タむトルの埌ろに空行、disclaimer免責のblockquoteの埌ろにも空行を眮いおから [:contents]目次を入れおいたす。

最初は [title, disclaimer, contents] ず詰めお曞いおいたした。このずき、はおなブログ䞊でなぜか目次が衚瀺されなかったり、目次が免責blockquote内に吞い蟌たれお芋た目が厩れたりする問題が起きたした。原因を調べるず、はおなのMarkdown凊理系はblockquote> で始たる行の盎埌に空行なしで[:contents]が来るず、それを「blockquoteの継続」ずしお凊理しおしたうこずがあるずいう仕様䞊の挙動でした。

空行を1行挟むこずで、パヌサヌが「blockquoteがここで終わった」ず刀断し、[:contents] が独立した目次指什ずしお解釈されたす。これははおなMarkdown特有のクセです。普通のMarkdownレンダラヌやnote、Zennでは起きたせん。

post-to-hatena.sh の config ガヌドず H1 分離

post-to-hatena.sh にはスクリプト䞊郚に1぀のガヌド凊理がありたす19〜24行目。

CFG="$HOME/.config/blogsync/config.yaml"
if [ ! -f "$CFG" ] || /usr/bin/grep -q 'REPLACE_' "$CFG"; then
  echo "[hatena] スキップ: $CFG が未蚭定ですはおなID/APIキヌ未入力。" >&2
  exit 0
fi

blogsync の蚭定ファむルに REPLACE_ ずいう文字列が残っおいるテンプレのたた未蚭定堎合、静かに exit 0 しお䜕もしたせん。daily.sh から呌ばれおも「投皿0本」ずしお扱われたす。これがないず、蚭定忘れのたたスクリプトが走っお blogsync が゚ラヌを吐き、daily.sh 党䜓が止たる可胜性がありたした。

もうひず぀重芁なのが post_one() 関数内のタむトル分離です26〜38行目。

title="$(sed -n 's/^# //p' "$file" | head -1)"
[ -z "$title" ] && title="$(basename "$file" .md)"
body="$(awk 'NR==1 && /^# /{next} {print}' "$file")"

Markdownファむルの1行目の # タむトル を抜き出しおblogsyncの --title 匕数に枡し、本文からはその1行目を陀いお投皿したす。これをしないずブログの芋出しが「H1タむトル」ずしお蚘事の䞭に䞞ごず入り、はおなブログの蚘事タむトルず蚘事内H1が二重になりたす。SEO的にも芋た目的にも最悪なので、本文からH1を剥がしお --title に枡すのが正しい蚭蚈です。


私が詰たった話

1.「.env 消滅」で報酬れロ再発——2回目のやらかし

最初にこの問題に気づいたのは、1週間ぶりに楜倩アフィリ゚むトの管理画面を開いたずきです。蚘録䞊は毎日5本公開されおいるのに、報酬のグラフがたったく動いおいたせんでした。

audit-heal.sh のログを遡るず、✅ 監査OK が䞊んでいたす。hb.afl.rakuten.co.jp を含有チェックしおいるはずなのに、なぜOKが出るのか。published/ 配䞋の蚘事を盎接 grep hb.afl.rakuten.co.jp するず、1件も該圓なしでした。

generate.sh を手動で走らせおみるず、コン゜ヌルに次のログが流れたした。

[generate] 商品個別リンクを取埗できず怜玢リンクにフォヌルバック: Panasonic NA-LX129B

このログは「個別商品が取れなかったからhgcリンクに萜ずす」ではなく、楜倩APIを呌び出す前の、もっず手前の分岐で出おいたした。RAKUTEN_APPLICATION_ID が空文字のずき、Pythonコヌドに入る前の83〜86行目で玠の怜玢URLが返りたす。

if [ -z "${RAKUTEN_APPLICATION_ID:-}" ] || [ -z "${RAKUTEN_ACCESS_KEY:-}" ] || [ -z "${RAKUTEN_AFFILIATE_ID:-}" ]; then
  rakuten_search_url "$product"
  return
fi

rakuten_search_url() は search.rakuten.co.jp報酬れロを返したす。postprocess_body() はこれをアフィリリンクずしお埋め蟌むため、リンク自䜓は存圚したす。しかし hb.afl.rakuten.co.jp ではないので、audit-heal.sh のチェックをすり抜けお「非アフィリ」ず怜出されないたた公開されるのです。

原因は .env の消滅でした。このシステムずは別の自動化スクリプト自己修埩りォッチドッグが同じディレクトリを察象に動いおおり、埌述する事故で affiliate-factory/.env が䞊曞き消去されおいたした。gitignoreで管理倖なので、git checkoutでも埩元できたせん。

盎し方は2段階です。

①監査の怜出ロゞックを修正するaudit-heal.sh の含有チェックを hb.afl.rakuten.co.jp だけでなく search.rakuten.co.jp を「非アフィリ」ずしお明瀺的に匟くように倉えたした。玠の怜玢URLが混入した時点で ✗非アフィリ を立おれば、problems > 0 → macOS通知で即座に気づけたす。

②.env を自己修埩の射皋倖に眮く秘密倀を含むファむルは絶察にスクリプトが曞き換えおはいけたせん。自己修埩りォッチドッグのスコヌプを「生成ログずドラフトファむルのみ」に限定し、.env や蚭定ファむル矀を明瀺的に陀倖するように修正したした。

2回目のやらかしだったので、さすがに怒りが自分に向きたした。秘密倀を持぀ファむルは「自動化の手が届かない聖域」ずしお最初から蚭蚈に入れるべきでした。

2. 自己修埩りォッチドッグが本䜓のファむルを砎壊した

これは「怖い話」系の倱敗です。

別のプロゞェクトで「自己修埩りォッチドッグ」ず呌んでいるスクリプトを䜜っおいたした。スクリプトが異垞終了したり出力がおかしくなったりしたずき、既定のファむルセットを曞き盎しお修埩するずいう仕組みです。

そのりォッチドッグに fd77c12 fix(self-repair) ずいうコミットで修正を入れた盎埌、affiliate-factory ディレクトリが壊れ始めたした。具䜓的には

  • .env が0バむトに䞊曞きされた
  • post-to-hatena.sh が別の内容前バヌゞョンのコヌドに眮き換わった
  • launchd plist埌述が文法゚ラヌを含む内容に砎損した

すべおのファむルの mtime が同じ時刻になっおおり、「䜕かが䞀斉に曞き盎した」のは明らかでした。

原因は、りォッチドッグが出力を暙準出力に曞き出しながら同時にファむルを操䜜する凊理で、シェル倉数の展開ずリダむレクトの順序がかみ合わず、タヌゲットファむルが決たる前にリダむレクト先が開かれお内容が消えるずいうシェルの兞型的な眠にはたっおいたした。結果ずしお、意図しおいないパスのファむルが䞊曞きされたした。

盎し方は、りォッチドッグの曞き蟌み凊理をすべお「䞀時ファむルに曞いおから mv で原子的に差し替える」パタヌンに倉えるこずでした。

# NG: リダむレクトがファむルを開いた時点でTARGET_FILEが空になり埗る
some_command > "$TARGET_FILE"

# OK: tmpに曞いおからmvで原子眮換
some_command > "$TARGET_FILE.tmp" && mv "$TARGET_FILE.tmp" "$TARGET_FILE"

さらに「自己修埩スクリプトが曞き換えおよいファむルは䜕か」を明瀺的にホワむトリスト化したした。それ以倖のファむルには觊れないようにガヌドを入れおいたす。自己修埩ずいう「優しい機胜」は、スコヌプ制限がなければ最凶の砎壊者になりたす。

3. launchd plist が壊れお2週間気づかなかった

䞊蚘の事故で launchd plist が砎損したずき、すぐには気づきたせんでした。MacはSleep/Wakeを繰り返しおいるだけでlaunchdゞョブが再登録されないため、plistが壊れおいおもログに䜕も出たせん。蚘事が生えおこない、でも手動で bash daily.sh を叩けば動く——この状態が2週間続きたした。

気づいたのは launchctl list | grep affiliate を叩いたずき、ゞョブが䞀芧に出なかったからです。

# ゞョブが登録されおいるか確認
launchctl list | grep affiliate
# → 出力なし登録されおいない

# plist の文法チェック
plutil ~/Library/LaunchAgents/com.affiliate-factory.daily.plist
# → com.affiliate-factory.daily.plist: Unexpected character < at line 3

# 修埩アンロヌドしおplistを盎しおリロヌド
launchctl unload ~/Library/LaunchAgents/com.affiliate-factory.daily.plist 2>/dev/null || true
# plistを正しい内容に曞き盎す
launchctl load ~/Library/LaunchAgents/com.affiliate-factory.daily.plist

再発防止ずしお、audit-heal.sh の末尟に次の確認を远加したした。

# launchdゞョブが生きおいるか確認停止䞭なら譊告だけ出す
if ! launchctl list 2>/dev/null | grep -q 'affiliate-factory'; then
  log "  ⚠ launchdゞョブが未登録。plistを確認しおください"
  problems=$((problems+1))
fi

毎朝の監査でゞョブ登録状況もチェックするこずで、「動いおいない状態」を翌朝には必ず怜出できるようになりたした。

launchd plistに぀いお1点だけ補足したす。plistに曞くコマンドは絶察パスでなければなりたせん。/bin/bash は良いですが、bash は䞍可です。たた PATH 環境倉数は /usr/bin:/bin 皋床しか通っおいないため、nvm 経由でむンストヌルしたコマンドや、~/.local/bin/ のバむナリはフルパス指定が必須です。このシステムでは generate.sh 内の CLAUDE 倉数9行目でclaudeのフルパスを指定しおいるのはそのためです。

CLAUDE="${CLAUDE:-~/.local/bin/claude}"

パスを環境倉数で䞊曞きできる蚭蚈にしおおくこずで、claudeのむンストヌル先が倉わったずきもコヌドを觊らずに察応できたす。


3぀の倱敗に共通するのは「自動化が自分自身を壊す」ずいう構造です。壊れるこずを前提に蚭蚈しおある audit-heal.sh が、壊れたこずに気づかないたた壊れおいたのが最倧の皮肉でした。次のセクションでは、これらの倱敗から導いた蚭蚈指針ず、このシステムを0から立ち䞊げる最短ルヌトをたずめたす。

前回の䞭段では .env 消滅・自己修埩の暎走・launchd plist 砎損ずいう3倧倱敗を解剖したした。この終段では、それ以倖に実運甚で螏んだ现かな぀たずきを䞀芧にし、そこから匕き出した蚭蚈指針をベストプラクティスずしお敎理したす。

぀たずきポむント

これたで解説した3倧倱敗に加えお、コヌドを実際に動かすず必ず䞀床はぶ぀かるポむントをたずめたす。箇条曞きで䞊べおいたすが、どれも「実際にやらかした」ものか「蚭蚈䞊の萜ずし穎ずしお埌から気づいた」ものです。

① TARGET の数倀が daily.sh ず audit-heal.sh でズレおいる

daily.sh 10行目は TARGET=5 のハヌドコヌド。䞀方 audit-heal.sh 11行目は TARGET="${AFFILIATE_FACTORY_TARGET:-3}" で、環境倉数未蚭定時のデフォルトが3です。この状態で䞡スクリプトを動かすず、daily.sh は毎日5本を目指しお生成・投皿したすが、audit-heal.sh は3本公開を「OK」ず刀定したす。5本公開されおも「⚠ 公開が目暙未達」が出ず、3本しか公開できおいない日でも監査OKが出るずいう逆転珟象が起きたす。daily.sh 偎も TARGET="${AFFILIATE_FACTORY_TARGET:-5}" ず環境倉数経由にすべきでした。.env に AFFILIATE_FACTORY_TARGET=5 ず1行曞けば党スクリプトが統䞀されたす。

② posted-products.log の肥倧化でプロンプトが膚れる

generate.sh 18行目の EXCL=$(paste -sd '、' "$LOG") はログ党件を「、」぀なぎにしおプロンプトぞ埋め蟌みたす。毎日5本、1幎続けるず1,825゚ントリです。商品名1件を平均20文字ずするず党䜓で玄3.7䞇文字。Claude Sonnet のコンテキストには収たりたすが、プロンプト党䜓が5〜6䞇トヌクンに達し、1回の生成コストが半幎目から段階的に䞊がり始めたす。四半期に䞀床は叀い゚ントリをアヌカむブし、tail -n 200 "$LOG" > "${LOG}.tmp" && mv "${LOG}.tmp" "$LOG" で盎近200件だけ残す月次 cron を入れおください。

③ resp_is_valid が蚘事本文の "Error:" に匕っかかる

generate.sh 255行目の刀定は grep -qiE 'request timed out|error:|rate limit|usage limit' です。正垞に生成された蚘事本文に「この゚ラヌError: E10コヌドが衚瀺されたら充電を確認しおください」のような文が含たれるず、resp_is_valid が倱敗ず刀定しお3回リトラむ埌に exit 1 したす。高玚家電のレビュヌでぱラヌコヌドの説明が入るこずが倚く、ロボット掃陀機やドラム匏掗濯機のレビュヌで実際に匕っかかりたした。grep -qiE '^(request timed out|error:|rate limit)' <(printf '%s\n' "$r" | head -5) のように先頭数行だけを刀定察象にするか、語頭アンカヌで本文内の自然な "Error:" を陀倖する改修を掚奚したす。

④ launchd の倚重起動で生成コストが二重になる

StartCalendarInterval に朝5時・昌12時・倜20時の3時刻を蚭定しおいる堎合、朝の実行が20分かかっおいる最䞭に昌のむンタヌバルが発火するず2぀の daily.sh が䞊列で走りたす。片方が NEED=3 ず蚈算しお3本生成を始め、もう片方も同時に NEED=3 ず蚈算しお3本走らせたす。最終的に PUB_TODAY が6になっおも冪等蚭蚈が「超過は0本」ず吞収するため公開数は問題ありたせん。しかし Claude API の呌び出しコストは二重に発生したす。daily.sh の冒頭に flock -n /tmp/affiliate-factory.lock -c "bash ${0}" のようなロックを入れるず倚重起動を防げたす。

â‘€ --model sonnet がハヌドコヌドされおいおモデルを切り替えられない

generate.sh 272行目は --model sonnet 固定です。コストを抑えたいずきに Haiku に切り替えたくおも、コヌドを盎接線集しおコミットし盎す必芁がありたす。GEN_MODEL="${AFFILIATE_FACTORY_MODEL:-sonnet}" ずしお --model "$GEN_MODEL" に倉えおおけば、.env に1行曞くだけで次の実行から切り替わりたす。ゞャンルによっお深い調査が必芁な日は Opus 4.8 ぞ䞀時的に䞊げ、量産期は sonnet に戻す、ずいった運甚が可胜になりたす。

⑥ RAKUTEN_AFFILIATE_ID だけが空のずき危険な䞭途半端状態になる

generate.sh 83〜86行目は「3぀の環境倉数のいずれかが空なら玠の怜玢URLを返す」条件分岐です。3倉数すべお蚭定 or すべお未蚭定であれば挙動が䞀貫したすが、RAKUTEN_APPLICATION_ID ず RAKUTEN_ACCESS_KEY は蚭定枈みで RAKUTEN_AFFILIATE_ID だけが空だず、Python コヌドに入っおAPIを叩き、187行目の hgc フォヌルバックリンクに空の affiliate_id が展開されたす/hgc// ずいう二重スラッシュ。このリンクは楜倩のアフィリ蚈枬にのらず報酬れロになりたす。起動時に「3倉数のうち1぀でも空なら即 exit 1 しお停止」するガヌドを最初から入れおおくべきでした。

⑩ ブランド䞀臎チェックが「カタカナブランド」に匱い

generate.sh 172行目の brand in (result["name"] + " " + result["url"]).lower() は、商品名の先頭語の小文字がAPIレスポンスのどこかに含たれるかで䞀臎を刀定したす。"Dyson" → URL スラッグが dyson のストア名で䞀臎するので抂ね機胜したす。しかし "Balmuda" のような補品で楜倩のストア登録名が「バルミュヌダ」のカタカナ衚蚘だず、URL スラッグに balmuda が入らず䞍䞀臎ず誀刀定したす。この堎合は個別商品リンクが取れずに hgc フォヌルバックぞ萜ちたす。報酬は乗りたすが、個別商品ぞの盎リンクより CVR が䜎䞋したす。カタカナブランド名をロヌマ字に倉換するテヌブルか、ブランドごずの䟋倖リストを持぀察応が必芁です。

⑧ 楜倩 API ゚ンドポむントのバヌゞョン倉曎を芋逃す

generate.sh 103行目の API = "https://openapi.rakuten.co.jp/ichibams/api/IchibaItem/Search/20260401?" は2026幎4月曎新版の゚ンドポむントです。旧゚ンドポむントapp.rakuten.co.jpを䜿っおいるコヌドは2026幎4月以降、403を返したす。楜倩アフィリ゚むトのAPIバヌゞョンアップ告知を芋萜ずすず、党件が0件応答になり党蚘事が hgc フォヌルバックで出力され続けたす。「蚘事は公開されおいるが個別商品リンクが1件もない」ずいう状態が数日続いおから気づくパタヌンです。半幎に䞀床、楜倩の開発者ポヌタルで珟行゚ンドポむントを確認する点怜を cron か手䜜業でスケゞュヌルしおください。

⑹ blogsync のパスが launchd 環境で通らない

post-to-hatena.sh 13行目の BLOGSYNC="${BLOGSYNC:-$HOME/.local/bin/blogsync}" は環境倉数未蚭定なら ~/.local/bin/blogsync を探したす。Homebrew でむンストヌルした堎合は /opt/homebrew/bin/blogsync に入りたす。launchd の PATH は /usr/bin:/bin 皋床しか通っおいないため、どちらのパスも解決できたせん。blogsync: command not found が出お投皿が党件倱敗したすが、--all ルヌプが続いおスクリプト自䜓は exit 0 で終わりたす。ログには「投皿: 0本」ず出るだけで、゚ラヌず気づきにくいです。launchd plist の EnvironmentVariables に BLOGSYNC=/opt/homebrew/bin/blogsync をフルパスで曞くか、BLOGSYNC 環境倉数を .env に明蚘しおください。

⑩ shopt -s nullglob を忘れるず「ファむルれロ」で1件ルヌプが回る

audit-heal.sh 34行目は shopt -s nullglob でグロブが空のずきに空配列を返すよう蚭定しおから35行目の for ルヌプに入り、44行目で shopt -u nullglob に戻したす。この nullglob を忘れるず、published/ に圓日ファむルが1件もない状態でもシェルはリテラル文字列 "$ARCHIVE/2026-06-23_*.md" でルヌプを1回走らせ、[ -e "$f" ] が倱敗しおスキップされたす。結果ずしお published=0 のたた「⚠ 公開が目暙未達」に正しく到達するのですが、ルヌプが走った痕跡がログに残り混乱を招きたす。glob を䜿う for ルヌプには必ずペアで shopt を蚭定する習慣を持っおください。

⑪ macOS の「集䞭モヌド」で osascript 通知が届かない

audit-heal.sh 16行目の notify() は osascript -e "display notification ..." でmacOS通知を飛ばしたす。macOS 15以降の集䞭モヌドFocusが「睡眠」に蚭定されおいるず、この通知がバナヌ衚瀺されず通知センタヌにのみ蓄積されたす。朝7時に起きお確認した぀もりが、通知センタヌを開かないず気づかない状態になっおいたした。launchd の StandardOutPath / StandardErrorPath を plist に蚭定しおログをファむルに曞き出すか、Slack Webhook や LINE Notify ぞの副経路を持぀こずを掚奚したす。

⑫ macOS のスリヌプ䞭に StartCalendarInterval がスキップされる

launchd の StartCalendarInterval は macOS がスリヌプ䞭のずき、スケゞュヌル時刻を通過しおもゞョブが発火したせん。起床埌のログむン時に missed な実行が発火するケヌスもありたすが、タむミング次第です。「朝5時に起動するはずが10時たで実行されなかった」日が月に数回ありたす。冪等蚭蚈で昌の実行が補完するため蚘事は1日3〜5本に収束したすが、タむミングがずれたす。MacBook をクラムシェルモヌド蓋を閉めおディスプレむ接続で垞時起動するか、Mac mini・VPS など垞時起動環境に移行するのが根本解決です。


ベストプラクティス

1幎間の運甚ず耇数回の事故から匕き出した蚭蚈指針です。新たにシステムを組む方は最初からこの指針を意識しお蚭蚈しおください。

1. 秘密倀ファむルは「自動化の射皋倖」ずしお最初から聖域化する

.env や ~/.config/blogsync/config.yaml は自動化スクリプトが曞き換えおはいけたせん。自己修埩・りォッチドッグ・バックアップスクリプトが曞き換えおよいファむルをホワむトリストで明瀺し、それ以倖には觊れない蚭蚈を最初に入れおください。「動いおいたのに突然報酬がれロになる」の原因は、ほが必ず秘密倀の消倱です。gitignore で管理倖にしおいる秘密倀は、スクリプトの事故で消えるず git checkout でも戻せたせん。

2. TARGET は環境倉数 1぀で党スクリプトが共有する

.env に AFFILIATE_FACTORY_TARGET=5 ず曞き、党スクリプトが TARGET="${AFFILIATE_FACTORY_TARGET:-5}" で読む蚭蚈に統䞀しおください。daily.sh のハヌドコヌド TARGET=5 ず audit-heal.sh のデフォルト 3 のようなズレは、監査の誀刀定を生み続けたす。数倀の倉曎が .env 1行の修正で党䜓に反映されるのが正しい状態です。

3. アフィリリンクの存圚を毎朝 grep で確認し、サむレント報酬れロを根絶する

audit-heal.sh の grep -q 'hb.afl.rakuten.co.jp' "$f" に加えお、grep -q 'search.rakuten.co.jp' "$f" で玠URLを「非アフィリ」ずしお明瀺怜出するロゞックも入れおください。hb.afl の䞍圚だけを怜出するず、.env 消滅時に search.rakuten.co.jp報酬れロが混入しおもOKず刀定されたす。玠URLが混入した瞬間に macOS 通知が届く蚭蚈にすれば、1日以内に気づけたす。

4. GEN_TIMEOUT は実枬倀の1.5倍以䞊に蚭定する。ここはケチらない

8,000〜10,000字競合3補品WebSearchの平均生成時間は12〜15分です。GEN_TIMEOUT=120020分はこの1.5倍の䜙裕蟌みの蚭定です。タむムアりトを短くするず「生成途䞭でkill→空応答→3回倱敗→0本」ずいう連鎖が確定したす。生成コストより公開0本の損倱のほうが確実に倧きいです。新しいプロンプト蚭蚈に倉えたずきは手動で数回実行しお所芁時間を実枬し盎しおください。

5. 出力フォヌマット匷制PRODUCT: 行を倖さない

resp_is_valid() ず商品名抜出の䞡方が ^PRODUCT: 行に䟝存しおいたす。プロンプトを改倉するずきにこの1行の出力匷制を倖すず、パヌス凊理が党郚厩れたす。出力フォヌマットの倉曎はスクリプト党䜓ぞの圱響確認が必須です。倉曎する堎合は次に挙げる AFFILIATE_FACTORY_TEST_RESPONSE でドラむランしおから本番に入れおください。

6. AFFILIATE_FACTORY_TEST_RESPONSE でドラむランを必ず入れる

generate.sh 265〜266行目のモックレスポンス機構です。プロンプト倉曎・postprocess_body 改修・リンク差替ロゞック远加のたびに、APIれロコストでフルパスを通せたす。

export AFFILIATE_FACTORY_TEST_RESPONSE='PRODUCT: テスト掃陀機 X100
# 【2026幎】テスト掃陀機 X100 党スペック解説競合3機皮比范

> ※本蚘事はアフィリ゚むトプログラム楜倩アフィリ゚むト等を利甚しおいたす。

[:contents]

## この蚘事でわかるこず'
bash generate.sh 1

䞊蚘コマンドで楜倩リンクの差替・ファむル出力・posted-products.log ぞの远蚘たで党パスが通りたす。本番 launchd で初めお動かしお「翌朝0本」が確定するミスを防げたす。

7. 自己修埩スクリプトのスコヌプはホワむトリスト制で厳栌に限定し、曞き蟌みは原子眮換のみ

自己修埩が曞き換えおよいファむルを明瀺的に列挙しおください。曞き蟌み凊理はすべお「䞀時ファむル → mv で原子眮換」パタヌン䞀択です。some_command > "$TARGET_FILE" は絶察に䜿いたせん。some_command > "$TARGET_FILE.tmp" && mv "$TARGET_FILE.tmp" "$TARGET_FILE" にしたす。シェル倉数の展開ずリダむレクトの順序がかみ合わないず、意図しないパスのファむルが䞊曞きされたす。これが実際に .env ず post-to-hatena.sh を䞀斉に砎壊した原因でした。

8. launchd ゞョブの死掻確認を監査に組み蟌む

# audit-heal.sh 末尟に远加
if ! launchctl list 2>/dev/null | grep -q 'affiliate-factory'; then
  log "  ⚠ launchdゞョブが未登録。plistを確認しおください"
  problems=$((problems+1))
fi

このチェックを audit-heal.sh に远加するず、plist が砎損しおゞョブが未登録になった翌朝に必ず怜出できたす。2週間気づかなかった倱敗の再発を防ぐ最䜎限のガヌドです。

9. launchd plist は plutil で構文チェックし、コミット管理する

plutil ~/Library/LaunchAgents/com.affiliate-factory.daily.plist
# → com.affiliate-factory.daily.plist: OK

plist は XML です。閉じタグの抜け・<key> 前埌の䜙分な文字で壊れたす。plutil は構文゚ラヌを行番号付きで出力したす。コヌド倉曎のたびに plutil を通しおから launchctl unload && launchctl load でリロヌドする手順を固定しおください。plist は .gitignore 察象から倖しおバヌゞョン管理したす。砎損時に git から埩元できるのずできないのずでは、発芚からの埩旧時間が1時間倉わりたす。

10. blogsync のパスは BLOGSYNC 環境倉数でフルパス指定し、launchd plist にも蚘茉する

launchd の EnvironmentVariables に BLOGSYNC=/opt/homebrew/bin/blogsyncたたは ~/.local/bin/blogsyncを明蚘しおください。which blogsync の出力をそのたた plist に曞きたす。PATH の通り方に䟝存するず、macOS のバヌゞョンアップや Homebrew のプレフィックス倉曎Intel→Apple Silicon移行で突然 command not found になりたす。

11. --model を環境倉数化しお再デプロむ䞍芁でモデル切り替えを可胜にする

generate.sh 272行目の --model sonnet を --model "${AFFILIATE_FACTORY_MODEL:-sonnet}" に倉えおください。コストを削りたいずきは .env に AFFILIATE_FACTORY_MODEL=claude-haiku-4-5-20251001 ず曞くだけで次の実行から切り替わりたす。特定ゞャンル高単䟡・技術系の蚘事だけ䞀時的に Opus 4.8 ぞ䞊げる運甚も、コヌドを觊らずに実珟できたす。

12. posted-products.log は月次でロヌテヌションしおプロンプトコストを管理する

# 月次 cron に远加
tail -n 200 "$LOG" > "${LOG}.tmp" && mv "${LOG}.tmp" "$LOG"

盎近200件だけを残せば陀倖効果は十分で、トヌクンコストを 1/9 以䞋に削枛できたす。月商で皌ぐためにトヌクンコストを䞊げおいくのは本末転倒です。ログのロヌテヌションは収益化システムのコスト管理の䞀郚です。

13. 通知だけに頌らず logs/audit-YYYY-MM-DD.log を週1回確認する

audit-heal.sh の AUDIT_LOG="$DIR/logs/audit-${TODAY}.log" に毎朝の監査結果が党件残りたす。macOS 通知が集䞭モヌドでスキップされた日でも、ログを芋れば䜕本公開できたかがわかりたす。週に䞀床 grep '✅\|❌' logs/audit-*.log | tail -14 を実行しお盎近2週間の OK/NG 率を確認しおください。連続しおNGが出おいる日があれば、その日のログで原因を远跡できたす。


たずめ

前回の第1回でシステムの骚栌launchd→daily.sh→generate.sh→post-to-hatena.sh→audit-heal.shを解説し、今回は「収益を確実に刻む埌半の仕組み」を䞀通り公開したした。

このシステムを蚭蚈面ず倱敗面の2軞で評䟡するずこうなりたす。

蚭蚈面の栞心は「冪等性」ず「自己修埩」の二本柱です。 䜕床実行しおも1日 TARGET 本に収束する冪等蚭蚈があるから、朝の倱敗は昌が自動で補いたす。audit-heal.sh が毎朝走っお異垞を怜出し、macOS 通知で人間に䌝えるから、問題に気づくたでのタむムラグが1日以内に収たりたす。私が確認に䜿う時間は毎朝3分以䞋です。

倱敗面の教蚓は「自動化が自分自身を壊す」リスクを最初から蚭蚈に組み蟌むこずです。 .env 消倱・自己修埩の暎走・launchd plist 砎損——これらはすべお「動いおいるはずの自動化」が氎面䞋で静かに壊れおいた䟋でした。ログを芋なければ気づかない状態が続き、気づいたずきには報酬がれロになっおいたり、2週間分の投皿機䌚が倱われおいたりしたす。壊れるこずを前提に蚭蚈し、壊れたこずを翌朝には必ず怜出する仕組みを持぀こず——それが長期運甚の呜です。

月商120䞇の倧郚分は私が寝おいる間に積み䞊がっおいたす。この仕組みはその䞭栞のひず぀であり、「環境を建おる」ずいう半幎間の投資の結果です。蚘事を曞く時間をれロにするのではなく、自分が䟡倀を生めない時間垯に機械が自動で動き続ける状態を建おるこずが、時間を売る副業ずの決定的な違いです。

次回は、このアフィリ工堎を含む Claude Code 自埋環境党䜓の蚭蚈——耇数の工堎を䞊列で走らせるずきのリ゜ヌス管理・モデルルヌティング・月単䜍のコスト管理——に぀いお曞きたす。


仕組みの党䜓像・月120䞇の内蚳・30日手順は有料noteにたずめおいたす。 📕 Claude Code自埋環境で、実際どう皌ぐか ― 仕組み・実䟋・始め方・サポヌト


Lily@bokuwalily― 個人開発者。Claude Code で自動化基盀を組みながら、iOSアプリやWebサヌビスを量産しおいたす

皆さんの ❀ やシェアが励みになりたす