{"slug": "4-days-3-wasted-calls-per-run-my-retry-loop-mistook-a-quota-limit-message-for-a", "title": "4 Days, 3 Wasted Calls Per Run: My Retry Loop Mistook a Quota-Limit Message for a 'Too Short' Article", "summary": "A developer fixed a retry-loop bug in note2-daily-stock.sh, a shell script that auto-generates product review articles via claude -p, after the script spent three wasted Claude calls per run for four consecutive days (Sept 13–16). The script was misreading the Max plan's one-line quota-limit message as an ordinary \"too short\" article failure and retrying a condition no rewrite could fix; a grep-based limit-message check now aborts after a single call, deletes partial output, and sends a Discord notification. The same missing detection pattern was found in three sibling scripts: article-daily-stock.sh, maker-daily-stock.sh, and series-daily-stock.sh.", "body_md": "Last time I wrote about [using a probe to decide which jobs to stop first when the quota runs dry](https://zenn.dev/bokuwalily/articles/quota-circuit-priority-probe). This post is about the problem sitting right next to it: my script *could* tell when the quota was exhausted, but it treated the limit message as if it were real article body text, then kept retrying a failure that no rewrite could ever fix. From September 13 to 16, every run burned 3 `claude -p` calls to produce nothing. After the fix, it burns 1 and bails out.\n\n`note2-daily-stock.sh` auto-generates product review articles for note2 (bokumolily) using `claude -p`. For each product it calls `claude -p` up to 3 passes to produce the body. Each pass is judged by a quality check (`article_quality_ok`) with criteria like \"does the character count exceed MIN_CHARS (7,000)?\" and \"are there at least 2 affiliate links?\"\n\nFor 4 days, 9/13 through 9/16, this script failed 3 times in a row with the exact same symptom. The cause: when `claude -p` hits the Max plan's limit, it returns a **one-line limit-reached message** (no `TITLE:` line), and the script treated that as \"an ordinary generation failure where the body is too short.\" The comment in the actual code records exactly what happened:\n\n```\n  # 2026-09-16: claude -pがクォータ上限メッセージ(1行・TITLE:行なし)を返すと\n  # 本文が空のまま「文字数不足」として3回とも空振りし、上限中に3回分のclaude呼び出しを浪費していた\n  # (note2-daily 9/13-9/16 4日連続で同一症状を実測)。上限メッセージは即検出して1回で中断する。\n```\n\n(note2-daily-stock.sh:565-567)\n\nWhile the limit is in effect, no amount of rewriting will produce a body. Yet `article_quality_ok` only ever said \"quality NG reason: not enough characters,\" so the script concluded \"maybe one more pass will fix it\" and threw away 2 more pointless `claude -p` calls during the limit window.\n\n**Note:** A retry loop needs more than the monolithic fact that \"it failed.\" **If you don't distinguish \"a failure a rewrite can fix\" from \"a failure a rewrite cannot fix,\" you'll keep walking into the same wall.**\n\nThe fix is to grep for the limit-message pattern immediately after each pass's generation, and if it matches, abort right there instead of spending the remaining passes.\n\n```\n  for pass in 1 2 3; do\n    RAW=$(generate_article_raw \"$PRODUCT_JSON\" \"$IMG_COUNT\")\n    if printf '%s' \"$RAW\" | grep -qiE \"hit your (weekly |5-hour |usage |session )?limit|usage limit reached|rate limit|resets? (at |in |on )?[0-9]{1,2}(:[0-9]{2})? *[ap]m\"; then\n      QUOTA_HIT=1\n      log \"ABORT: quota-message検出のため中断 (pass $pass) $SLUG raw先頭=[$(printf '%s' \"$RAW\" | tr '\\n' ' ' | cut -c1-160)]\"\n      break\n    fi\n    write_article_file \"$RAW\" \"$OUT\"\n    ...\n  done\n```\n\n(note2-daily-stock.sh:563-572, excerpt)\n\nWhen `QUOTA_HIT` is set, the script deletes the half-written output file and any images downloaded along the way, notifies Discord, and exits immediately. Instead of 3 calls, it consumes exactly 1 at that point and retreats.\n\n```\n  if [ \"$QUOTA_HIT\" -eq 1 ]; then\n    [ -e \"$OUT\" ] && rm -f \"$OUT\"\n    rm -f \"$ROOT/${IMG_BASENAME}-p\"*.jpg\n    discord_notify \"⚠️ note2-daily: claudeクォータ上限のため中断（3回消費せず中止）: $ITEM_NAME\"\n    exit 1\n  fi\n```\n\n(note2-daily-stock.sh:590-595)\n\n\"Not enough characters\" and \"limit reached\" look alike on the surface (the body is nearly empty), but the correct response is completely different. The former might be fixed by a rewrite; the latter won't be fixed no matter how many times you retry until the reset time. Separating those two cases up front with a single grep is the entire fix.\n\nThere are 3 other scripts that share the same shape as note2-daily-stock.sh — \"have `claude -p` write the body, retry if the quality check fails\": `article-daily-stock.sh` (the script that writes this very Zenn article series), `maker-daily-stock.sh`, and `series-daily-stock.sh`. I grepped all 3, and none of them contained the limit-message detection pattern.\n\n``` bash\n$ grep -n \"hit your\\|usage limit\\|QUOTA_HIT\\|rate limit\" \\\n    article-daily-stock.sh maker-daily-stock.sh series-daily-stock.sh\n(該当なし)\n```\n\nIn particular, `run_pass()` in `series-daily-stock.sh` has almost exactly the same structure that note2 tripped over.\n\n``` php\nrun_pass(){ # $1=prompt $2=outfile $3=label\n  local prompt=\"$1\" outfile=\"$2\" label=\"$3\" attempt wait\n  for attempt in 1 2 3; do\n    rm -f \"$outfile\"\n    timeout 600 \"$CLAUDE\" ... -p \"$prompt\" ... > \"$outfile\" ...\n    if [ -s \"$outfile\" ]; then\n      ...\n    fi\n    log \"WARN: pass $label 空出力(attempt $attempt/3)\"\n    if [ \"$attempt\" -lt 3 ]; then\n      wait=$((attempt * 30))\n      sleep \"$wait\"\n    fi\n  done\n```\n\n(series-daily-stock.sh:254-279, excerpt)\n\nWaiting 30 seconds, then 60 seconds, on \"empty output (attempt N/3)\" and **hammering the same call 3 times for the same reason** is essentially the same structure note2 hit on 9/13–9/16. If this script runs during a limit window, it should waste calls in exactly the same way — and it hasn't been fixed yet.\n\n`maker-daily-stock.sh` only has a single generation pass, so it doesn't fail 3 in a row, but the same dates show up in its comments too.\n\n```\n  # 失敗時もキューを1件進める。進めないと同じ題材が先頭に居座り続け、翌日以降も\n  # 同じ理由で落ち続ける（2026-09-13〜16 に同一題材が7回連続で失敗した実測）。\n```\n\n(maker-daily-stock.sh:412-413)\n\nIn other words, the Max plan limit on 9/13–9/16 didn't just hit note2 — it dragged the maker pipeline down with it. On the maker side, the underlying problem of \"misdiagnosing the limit message as an incomplete body\" is still there, exactly as in note2; it's being held together by a different band-aid: \"even on failure, at least advance the topic queue.\" **The same root cause has a different emergency patch in each script, applied whenever someone happened to notice.**\n\n`claude -p` calls during the limit window`maker-daily-stock.sh` also failed 7 times in a row on the same topic — the limit hit multiple pipelines simultaneously. But maker's fix is only \"advance the queue,\" and the underlying misdiagnosis remains unfixed there as well as in `article-daily-stock.sh` and `series-daily-stock.sh`\nNext time I plan to write about whether that propagation itself can be automated — a mechanism to mechanically carry a fix found in one script over to its \"structurally similar siblings.\"\n\nHow do your retry loops tell a fixable failure from one that no retry will ever fix?\n\n*Written by **Lily** — I ship iOS apps and automate my content stack with Claude Code.\n\nFollow along: [Portfolio](https://bokuwalily.com) · [X](https://x.com/bokuwalily) · [GitHub](https://github.com/bokuwalily)*", "url": "https://wpnews.pro/news/4-days-3-wasted-calls-per-run-my-retry-loop-mistook-a-quota-limit-message-for-a", "canonical_source": "https://dev.to/bokuwalily/4-days-3-wasted-calls-per-run-my-retry-loop-mistook-a-quota-limit-message-for-a-too-short-4l23", "published_at": "2026-09-21 05:00:05+00:00", "updated_at": "2026-09-21 05:22:59.806506+00:00", "lang": "en", "topics": ["ai-tools", "developer-tools", "large-language-models"], "entities": ["claude -p", "Claude", "note2", "Discord"], "alternates": {"html": "https://wpnews.pro/news/4-days-3-wasted-calls-per-run-my-retry-loop-mistook-a-quota-limit-message-for-a", "markdown": "https://wpnews.pro/news/4-days-3-wasted-calls-per-run-my-retry-loop-mistook-a-quota-limit-message-for-a.md", "text": "https://wpnews.pro/news/4-days-3-wasted-calls-per-run-my-retry-loop-mistook-a-quota-limit-message-for-a.txt", "jsonld": "https://wpnews.pro/news/4-days-3-wasted-calls-per-run-my-retry-loop-mistook-a-quota-limit-message-for-a.jsonld"}}