#DO_Something
What's up with flip floppy?
I lean to the right with Republican buddies.

Oh no wait I am still a Democrat?

#Enough_Already
#DO_Something
September 13, 2026 at 9:37 PM
Awww another flop for the orange flip flopping orange #TACO_Trump.

The weather Divines create electrical glitches and rumor has it Grinder may have blipped out.

World Citizens are watching as America sinks lower into their self-created and self- sustained swamp.
#DO_Something
September 11, 2026 at 12:25 AM
bsky.app/profile/pino...

Via @dbmalloy.bsky.social

Just more of #Deplorable_Don Good grief two more years of him?

Humanity is in trouble.
#DO_Something
#VotingIS_OurVoice
“40,000 🇨🇦 Canadian Soldiers, 153 of whom died, fought with us after 911. They weren’t attacked. WE were attacked, but they sent their troops to go fight with us.”

The way the Felon has treated our Canadian friends, we are not worthy of their friendship. His actions are disgusting and disgraceful.
September 9, 2026 at 7:06 AM
This is how #Repulsive_Republicans govern.
Don't expect us to be inspired leaders through our example.

Our way of governing is to DO as we say not as we DO. Our ways are just for elitist corrup Republicans."
#Fracture_Facism
#DO_Something
September 8, 2026 at 10:08 PM
bsky.app/profile/altn...

Oh for crying out loud. #Enough_Already with stupid renaming!
#DO_Something
#For_THE_People
September 6, 2026 at 9:01 PM
Stating the obvious is beginning to feel like a, waste if time. Every body talks about it.. and tge bears goes in. Will the midterms signal a #DO_Something shift?
August 31, 2026 at 8:49 PM
Broken Retries: Why 'Just Try Again' Kept 5 Automation Lanes Dead for 63 Minutes
Five automation lanes went down on the same morning. Every log said the same thing: "Failed, so I ran it again." Then again. Then again. Sixty-three minutes after the first failure, all I had was the exact same failure — three more times over. ## Why this design works When I first started writing scripts, I thought retries looked like this: for attempt in range(3): try: result = do_something() break except Exception as e: time.sleep(2) And that's correct — as far as it goes. A network blip, a server that got temporarily slow: that kind of failure really does get fixed by this. The problem is that it applies the assumption "the next attempt will succeed" uniformly to _every_ failure. As the number of running jobs grows, failures that break this assumption will inevitably show up. In the SNS automation environment I operate, retry fixes came in from five separate lanes at once over two days, August 8–9, 2026. The symptoms all looked alike. The causes were completely different. **note-autolike produced zero rewrites for three days.** Last success: 2026-08-06T09:03:20Z. There was a 58-character limit validation on article titles, but the generation prompt said nothing at all about a character limit, so the model returned a 63-character title every single time. Three attempts, three 63-character titles. A fourth was never tried — it went straight into the give-up path. **ai-portraits spun its wheels for 63 minutes on image generation.** When a request got rejected by moderation, I had implemented a three-stage fallback that progressively softened the prompt and retried. Up to 21 minutes per concept; up to 63 minutes for three concepts. In practice, when the target content itself is what trips moderation, lowering the tier doesn't move the wall. Trying all three stages produced the same result as stage one. **browser-slot.sh sees 18–34 launch attempts per hour against 3 global slots.** A single run takes 14–62 minutes, so the slots are always full. The pre-fix implementation printed `SKIP: global limit reached` and exited 0 the instant acquire failed. The numbers left in the log: 9 runs in the 9 o'clock hour, 8 in the 11 o'clock hour, and 12 in the 12 o'clock hour never even started. One account hit skip on both of its runs that day, ending with 0 likes and 0 follows. All three had "just try again" implemented. None of them were fixed by it. Because the _kinds_ of failure were different. A 63-character title: throw the same prompt at the same model and you get the same 63 characters back. That's a deterministic failure. A moderation block, when the content is the cause, doesn't change no matter how many stages you try unless the conditions change. A slot shortage can only be solved by waiting for someone to free a slot. In a retry implementation with no such classification, all three kinds of failure get sucked into the same "loop 3 times, then exit" handling. The result: fixable failures and unfixable failures are counted identically and come out the same exit. There's a failure mode in the opposite direction too — cases logged as "failed" that were actually transient. My `watch-arb` script recorded only `extract_failed` when it couldn't fetch a product page. Look at the dashboard the next morning and it reads as "there are 16 shops we can't fetch." In reality it was just HTTP 429 coming back, and a prober that hit the same shops slowly, one at a time, fetched them correctly. Lining up the similar incidents I confirmed over the same period: **the same 429 was recorded under seven different names in the logs.** Lane | Name in the log | Actual root cause ---|---|--- watch-arb product page | `extract_failed` | HTTP 429 watch-arb bootstrap | `FATAL` | undici's 10-second ConnectTimeout social-autolike ig-3 | `coverage 0/11 → all sources failed → exit 1` | 429 from IG private API social-autolike ig-sug | `following map fetch failed → run halted` | The same 429 outreach-multi enrich | all records `error` → all dropped at qualify → 0 messages total | `web_profile_info` returning 429 constantly outreach-multi igJson | `unparsable json` | Response was 512,558 chars but sliced at 400,000 If it's recorded as `extract_failed`, you read it as "this shop's structure is unusual." In fact the problem was "I hit it too fast," and hitting it slowly the next day works. Because the name was wrong, I kept fixing in the wrong direction. Cleaning this up, correct retry design converges on answering three questions. **(1) Will waiting fix it?** **(2) When do you cut it off?** **(3) What do you change before the next attempt?** "Failed, so try again" is an implementation that omits all three. On the morning those five lanes were down together, none of the three had an answer written down. An automation environment doesn't keep running because I write code every day. It works because the environment self-corrects and keeps running while I'm asleep. From that premise, the requirements for retry design change. The requirement isn't "the next attempt will succeed after a failure" — it's "decide the next action per kind of failure." Between an implementation that has this design and one that doesn't, whether I have to be personally involved in incident recovery changes fundamentally. ## The overall flow Here are the three questions in one diagram. A failure occurred │ ▼ ┌─────────────────────────────────────┐ │ (1) Will waiting fix it? │ │ │ │ Slot shortage → wait for one to free│ │ Network drop → back off and retry │ │ 429 → wait, preferring Retry-After │ │ Expired login → waiting won't fix it│ │ Validation error → waiting won't fix│ │ Moderation → depends on stage (below)│ └─────────────────────────────────────┘ │ waiting won't fix it ▼ ┌─────────────────────────────────────┐ │ (2) When do you cut it off? │ │ │ │ Attempt cap or >3h since intake → stop│ │ Still attempts left → go to (3) │ └─────────────────────────────────────┘ │ still attempts left ▼ ┌─────────────────────────────────────┐ │ (3) What do you change? │ │ │ │ Same input → same result if deterministic│ │ Attach the error → recoverable failures recover│ │ Tier downgrade (vertical) → soften and retry│ │ Swap the task (horizontal) → change the task itself│ └─────────────────────────────────────┘ The three sections aren't processed in order — the combination that applies changes with the kind of failure. For a network drop, (1) alone is enough. A deterministic validation error skips (1) and enters at (3). A moderation block involves all of it: skipping (1), the threshold in (2), and the way you change things in (3). ### (1) Wait — the skeleton of the implementation Here's the core I added in the `browser-slot.sh` fix. BROWSER_SLOT_WAIT_SEC="${BROWSER_SLOT_WAIT_SEC:-600}" wait_for_slot() { local waited=0 while ! try_acquire_slot; do local jitter=$(( RANDOM % 31 + 15 )) # 15〜45秒ランダム sleep "$jitter" waited=$(( waited + jitter )) if [[ $waited -ge $BROWSER_SLOT_WAIT_SEC ]]; then log "RESULT waited=${waited}s SKIP: slot timeout" return 1 fi done return 0 } **Make the retry interval a random 15–45 seconds.** With a fixed interval, every job waiting for a slot piles in at the same instant (thundering herd). Adding random jitter offsets jobs from each other so they spread out naturally. I made the change only after confirming by measurement that a fixed interval serialized all the jobs and made congestion worse. **Don't hold the guard for the whole wait.** Hold the lock only for the instant of the acquire check, and always release it afterward. Get this wrong and every waiting job holds a lock against every other, and they all stall. **Leave`waited=600s` on the RESULT line in the log.** "Gave up after waiting 600 seconds" and "discarded immediately" are different events, and if you can't distinguish them you can't evaluate anything after the fact. `BROWSER_SLOT_WAIT_SEC=0` matches the pre-fix immediate-skip behavior exactly. More than 30 launchd jobs share this script, so I made it possible to switch over gradually while preserving backward compatibility. On the other hand, there are cases that look transient and are in fact fixed by an immediate retry. I had a job that failed three days running with Cloudflare's `Authentication error [code: 10000]`, which looked like "OAuth expired → a human has to intervene." But when I actually ran five probes in both a shell and a minimal launchd environment, all of them exited 0. Re-reading the logs from the failure days, 0.5 seconds after an access token reissue, a different endpoint returned 200 with that same token. It was a **transient 401 right after token reissue** , and the real root cause was that there was not a single retry. Failure rate: 3 days out of 9 (33%). Snap-judging "it's an auth error, so it's a human task" piles machine-fixable things onto a human queue. ### (2) Give up — always place a terminator Take `ai-portraits`' tier ladder. Moderation block errors have a `moderation_stage` field. Ones that stopped at `output` have a track record of getting through with a tier downgrade. Ones that started at `input` failed all 9 attempts over the same period. for tier in [1, 2, 3]: result = generate(concept, tier=tier) if result.get("moderation_stage") == "input": # input段 → Tierを変えても壁は動かない、残りをbreak log(f"input-stage block, skip remaining tiers for this concept") break if result.get("moderation_blocked"): # output段 → Tier降格で通る可能性がある continue if result.get("success"): return result **Decide from the first-stage error body whether climbing down the ladder is worth anything.** Just breaking on `moderation_stage == "input"` turns 63 minutes of spinning into 0. In the same spirit, `gsheets_retry.py` is designed to raise immediately on 401/403/404. RETRYABLE_HTTP = {429, 500, 502, 503, 504} def sheets_call_with_retry(fn, max_attempts=3): delay = 2 for attempt in range(max_attempts): try: return fn() except HttpError as e: if e.resp.status not in RETRYABLE_HTTP: raise # 401/403/404 は即 raise — リトライで隠さない if attempt == max_attempts - 1: raise time.sleep(delay) delay *= 3 # 2秒 → 6秒(合計最大8秒) except (socket.timeout, ConnectionError, TimeoutError): if attempt == max_attempts - 1: raise time.sleep(delay) delay *= 3 **Decide "never exceed 8 seconds total" first.** If you set exponential backoff by attempt count, it quietly eats the caller's slot time. Fixing an upper bound in seconds first and back-calculating the attempt count is the safe order. If you implement "give up" using attempt count alone, you can't handle failures on the time axis. In `kotonoha`'s job queue I had an incident where a job sat **pending for 39 hours**. Even when it broke off at the cap of 6 attempts, the requeue succeeded, so a failure was never raised, monitoring never fired, and the user's screen stayed on "generating." The post-fix predicate looks like this. function shouldGiveUpForQuota({ quotaHits, createdAtMs, resumeAtMs }) { const THREE_HOURS_MS = 3 * 60 * 60 * 1000; return ( quotaHits >= 2 || // 2回目の上限 resumeAtMs > createdAtMs + THREE_HOURS_MS // 受付から3時間超 ); } **Place the cutoff on both "attempt count" and "absolute time since intake."** Putting the actual values from the incident day (intake `2026-08-19T00:36:57Z`, resume `06:37:00Z`) into the tests as a boundary case gives you a machine guarantee that the same incident can never get through again. ### (3) Feed back — pass what you changed into the next attempt Here's the `note-autolike` case. The reason rewrites were zero for three days was an implementation that just threw the identical prompt three times. Make the error machine-readable and attach correction instructions to attempts from the second onward. // エラーに code / actual / limit を載せる throw new ValidationError(`タイトルは1〜58字にしてください(実際: ${actual}字)`, { code: "title-too-long", actual, limit: 58, }); // 2回目以降は修正指示を付けて投げる function rewriteRetryInstruction(error) { if (error.code === "title-too-long") { return [ `前回の出力は次の理由で不採用でした:`, `タイトルが${error.actual}字あり、上限の${error.limit}字を超えています。`, `同じ内容・同じ構成のまま、その点だけを満たす形へ直して、生JSONのみを再出力してください。`, `タイトルは絵文字込みで${error.limit}字以内(前回は${error.actual}字)に必ず収めてください。`, ].join("\n"); } } async function askClaudeWithRetry(prompt, retries = 3) { let currentPrompt = prompt; for (let i = 0; i < retries; i++) { try { const result = await callClaude(currentPrompt); validateRewrite(result); return result; } catch (e) { if (e instanceof ValidationError) { // 決定論的な失敗 — スリープなし、修正指示を付けて即再試行 currentPrompt = prompt + "\n\n" + rewriteRetryInstruction(e); } else { // プロセス系の失敗 — 30秒待つ await sleep(30_000); } } } } **Retry deterministic failures immediately, with no sleep.** Waiting 2 seconds on a validation error just throws 2 seconds away. If you mix "wait" and "fix and resubmit" into the same retry, you pay wait time even on fixable failures. Branch on the kind of failure and choose where the sleep goes. The correction instructions you return to the LLM should be "human-language instructions plus the actual strings," not "machine identifiers." Passing `"title-too-long"` through as-is loses three times in a row. Passing "the title is 63 characters, exceeding the 58-character limit; keep it within 58 characters" recovers. This is a fact I had already demonstrated in a different script. I'll stop the full picture of the three sections here. Next I'll break down how each section breaks and how to fix it, case by case, drawn from the five-lane simultaneous outage. ## Implementation details ### "I wrote the retry" isn't done — put fault injection in the same commit The three sections designed above are, at the moment you write them, nothing but "code that should work." Intermittent failures have no reproduction method, so by default there's no way to confirm whether the retry actually traverses that path. My first four implementations ended there. I looked at `git diff`, felt like I'd confirmed it, and called it done. What actually worked was bundling fault injection into the same commit as the implementation itself. // watch-arb/collector/lib/api.mjs let _failOnceRemaining = parseInt(process.env.WA_API_FAIL_ONCE ?? '0', 10); async function req(path, opts = {}) { if (_failOnceRemaining > 0) { _failOnceRemaining--; throw new TypeError('fetch failed'); // 合成の障害、本番では絶対に発火しない } // ... 本来のfetch処理 } The counter is module-scoped and shared across all `req` calls. Only for the first N calls after process start, it throws a synthetic `TypeError` before calling fetch. The completion criteria are a set of two, and I always confirm both. * **① Launch with`WA_API_FAIL_ONCE=2`** and see `api retry attempt=1/3` and `2/3`, then a full run to exit 0 with no FATAL * **② With the env var unset, not a single retry log line appears** Having ② means you can demonstrate "the fault injection hasn't leaked into production" with the same command. Here's the log I actually got. [INFO] api retry attempt=1/3 path=/api/collector/shops reason=fetch failed [INFO] api retry attempt=2/3 path=/api/collector/shops reason=fetch failed [INFO] 32件を ingest して exit 0 I rolled this pattern out from `watch-arb` to `zaiko-radar`, `lily-line-funnel`, and `social-autolike` — four in total. The rule changed to: "I wrote the retry" doesn't count as done unless it's "I watched it run with fault injection." ### Confine the retry target to a single function When you write a retry, the first thing to decide isn't "how many attempts" but "what gets retried." // 判定を1関数に集約する function isTransientStatus(status) { return status === 429 || (status >= 500 && status <= 504); } async function req(path, opts = {}) { for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) { try { const res = await fetchWithTimeout(path, opts); if (!res.ok) { if (!isTransientStatus(res.status)) throw new HttpError(res.status); if (attempt === MAX_ATTEMPTS) throw new HttpError(res.status); await sleep(backoff(attempt)); continue; } return await res.json(); } catch (e) { if (!isNetworkError(e)) throw e; // ネットワーク例外以外は即throw if (attempt === MAX_ATTEMPTS) throw e; await sleep(backoff(attempt)); } } } **Throwing immediately on 401/403/404** isn't a shortcut — it's design that prevents a different kind of incident. Wrap a permission error in a retry and you throw the same request three times and get the same failure three times. The log records only "failed 3 times," and the real cause — "no permission" — gets buried. This is the inverse of the "429 recorded under seven names" I documented in `transient-failure-recorded-as-permanent.md`. Same structure as a 429 masquerading as `extract_failed`: 401/403 masquerades as "sometimes slow." The reason to pull `isTransientStatus` out into a single function is that when the judgment is scattered across multiple call sites, each site grows a subtly different definition. The constant set `RETRYABLE_HTTP = {429, 500, 502, 503, 504}` in `gsheets_retry.py` comes from the same idea: it confines the knowledge of "which statuses are retryable" to one place. ### Adding retries changes how exceptions propagate In the commit right after I implemented `withRetry` in `kotonoha`, three side-effect fixes went in. I thought I had "added a feature"; what I had actually done was "change how exceptions propagate." Before the change, `purge()` continued on to the subsequent "clean local work directories older than 30 days" step even when a 5xx came back, because the implementation moved to the next line even when `res.ok` was false. Once `withRetry` was in and it threw the instant a 5xx appeared, the cleanup code became unreachable. The real damage was a quiet degradation of the kind you only notice if you manually clean periodically: "work directories accumulate 30 days' worth." async function purge(jobId) { try { await uploadResult(jobId); } catch (e) { log(`purge upload failed: ${e.message}`); // ここで throw しない — 置き場の失敗は記録して続行する } await cleanWorkDir(); // 例外の有無にかかわらず必ず走らせる } The other one was `setContentSafely`, where the gap between the first and second retry was 0 seconds. Retrying immediately under the same conditions right after a failure caused by resource pressure gives the same result as long as the pressure remains. Earlier I wrote "retry deterministic failures immediately with no sleep" — this is the opposite side of that. **For failures caused by resource pressure, a retry is meaningless unless you wait.** Attempt counts are set asymmetrically according to "what happens when it fails." `report()` (result reporting) leaves the user's screen stuck on "running" forever if it fails, so `attempts=5`; `claim()` (reservation acquisition) gets `attempts=3`. A uniform attempt count ignores the asymmetry of cost and damage. Timeouts are also split by the weight of the operation: `putObject` and `getStream` get 120 seconds, `getText` and `listKeys` get 30 seconds. A fetch with no timeout configured creates the worst un-retryable state of all: hanging forever. ### You can't retry when you pass a stream There's a constraint I wasn't conscious of until I implemented this. To make something retryable, the input has to be re-executable. // NG: ストリームは一度消費したら再生できない async function retryWithStream(stream) { for (let i = 0; i < 3; i++) { await fetch(url, { body: stream }); // 2回目はストリームが空 } } // OK: Buffer か string に限定する async function retryWithBuffer(buffer) { for (let i = 0; i < 3; i++) { await fetch(url, { body: buffer }); // 何度でも同じデータを送れる } } I restricted the body passed to `putObject` to Buffer or string. If you don't leave the type constraint in a comment, a caller later passes a stream and you get a bug where "the retry is working but it fails every time." Pass a stream into a retry and every attempt after the first sends an empty body. No error surfaces; it just fails every time. ## Where I got stuck ### A constant I placed for protection had become a 0%-success-rate wall In `outreach-multi`'s DM inbox parsing, an `unparsable json` error appeared every day. I suspected a broken selector or a changed JSON structure and kept fixing for three days. The reality: the inbox-fetching code had a line reading `text.slice(0, 400_000)`. It was an upper bound written to protect memory when a huge response came in. When I measured the actual size of the IG DM inbox, it was **512,558 characters**. Since the limit is 400,000, it gets cut off every single time. Truncated JSON fails `JSON.parse` every single time. Success rate: 0%. The reason I stared at the same failure for three days is that I had forgotten about the `text.slice` line and read it as "parsing is failing → the structure broke." Nor had the symptom started "today." It quietly started failing on the day the inbox size crossed 400,000 characters, and one day I noticed the running total was 0. The fix was to complete `JSON.parse` inside `page.evaluate` and return only the first 300 characters for diagnostics when it fails. The design rule I took from this: "**if you place a limit, log the fact that the limit was hit.** " `text.slice` silently truncates and tells no one. Even when a protective constant has turned into a permanent failure the moment its value was exceeded, nothing is left in the log. ### The quarantine had become an infinite loop The unfollow processing in `unfollow-core.js` had a mechanism where three failures put an account into a 7-day hold (quarantine). The problem is "what happens when it comes out of quarantine." A revived account goes back into the candidate list. Fail three more times, and it goes into another 7-day hold. This spins forever. There were 2,605 candidates, and I puzzled over the numbers `scanned: 8 / unfollowed: 2` for two weeks. Almost none of the candidates were being processed. When I investigated "why isn't this progressing," the answer was "the same accounts keep entering and leaving quarantine, and nothing moves forward." The log said `circuit-break`. It's contradictory for circuit-break to be functioning while the execution count doesn't rise. I could have noticed much sooner, but I was reading it as "circuit-break is working = it's protected," so I never questioned it. The fix was to physically delete from the ledger any account that reached a cumulative failure count (`UNFOLLOW_GIVEUP_FAILS = 6`, twice the quarantine failure threshold). **A "hold" only accumulates; it never drains.** Unless you place a terminator that discards, the ledger keeps filling up with accounts that can't be processed. That said, before discarding, you first have to confirm "is my own processing working correctly?" There's an inverse incident where the session had merely expired and you discard all the healthy accounts. The principle I wrote on the `kotonoha` page — "check your own health before discarding" — applies here too. ### Monitoring didn't fire for 39 hours A `kotonoha` job sat stuck on "generating" for 39 hours. Monitoring never fired once. The job was never recorded as a failure. The cause: "a retry that doesn't hold the attempt count in state can't have a cutoff placed on it." When a job hits a quota limit, `requeue` puts it back to pending. The returned job carried no counter for "which attempt is this." A 7th-attempt job and a 1st-attempt job are indistinguishable from the implementation's point of view. Since `requeue` succeeds, no failure is raised. It never hits a monitoring threshold. The user's screen keeps displaying "generating" indefinitely. The fix came in two stages. First, "hold the attempt count in state." Then, "place the cutoff on both attempt count and absolute time since intake." When I put the `shouldGiveUpForQuota` function shown earlier through tests with boundary cases, I included the actual values from the incident day (intake `2026-08-19T00:36:57Z`, scheduled resume `06:37:00Z`) as one case. That gives a machine guarantee that the same incident can never get through again. With a cutoff on attempt count alone, you let through "only the 1st attempt, but resume is 12 hours away." With time alone, you let through "resume is soon, but the limit hits back to back." This incident got caught on the first attempt, on the time-condition side. It's a case where the value of doubling up was measurable in real data. "The worker waits" and "give up on that job" are separate decisions. A quota is a failure that waiting fixes, so the worker waits. But that isn't grounds for making a user wait more than three hours. ### I snap-judged "auth error" and left it in a human queue for three days `com.lily.line-pdca` stopped three days running with Cloudflare's `Authentication error [code: 10000]`. Since it was an auth error, I judged "OAuth expired, a manual `wrangler login` is needed." I also suspected a missing environment variable. Classifying it as "a case requiring human GUI operation" before verifying was the mistake. When I actually ran `wrangler whoami`, `kv key list`, and `deploy --dry-run` in both a shell and a minimal launchd environment, all exited 0. Even the output byte counts were identical (1,516 bytes). Re-reading the logs from the failure days, KV keys returned 401 **0.5 seconds after** an access token was reissued. With that same token, `/user` 0.5 seconds later returned 200. It was a **transient 401 right after token reissue** , and the real root cause was that there was not a single retry. The failure rate was 3 days out of 9 (about 33%). It doesn't happen every day, but it does happen once every three days. That frequency was itself part of why it "looked like a human task." The criterion that changed my judgment was actually hitting the endpoint to confirm "would the same procedure pass if I ran it again right now?" Don't classify something as "unfixable, period" based only on the kind of error (401, Authentication error). Same pattern as the "429 recorded under seven names" documented in `transient-failure-recorded-as-permanent.md` — this time the name "auth error" made a transient failure look like a human task. Since this incident, the rule changed to: when an auth error appears, run one probe before deciding. The premise behind "don't pile machine-fixable things onto a human work queue" is "I hit it with a machine and checked." Snap-judging without checking is no different from declaring "it won't be fixed" without ever trying. ## Pitfalls Here are the structural patterns behind the individual cases above. Only the "name of the place I got stuck" differs; the root is the same pattern. **Compressing a failure into "a single name" when recording it** `watch-arb`'s product pages were recorded as `extract_failed`, the bootstrap run as `FATAL`, and the DM inbox as `unparsable json`. All three had HTTP 429 as the actual root cause. Organizing this phenomenon, recorded in `transient-failure-recorded-as-permanent.md`, the same 429 was left under 7 different names within the same period. When the HTTP status disappears at the naming step, you can no longer judge whether it's "waiting will fix it" or "the structure needs fixing." The fix is one line. Before recording, classify: `429/503 → rate_limited` / `401/403 → auth_error` / `everything else → extract_failed`. This three-way branch is the precondition sitting in front of the wait/give-up/feed-back branch. **Throwing away the error body** Because `ai-portraits`' `generate.py` discarded codex exec's stdout/stderr, the reason no image was produced was recorded only as `missing`. If you can see neither `moderation_blocked` nor `safety_violations: ["sexual"]`, you can't judge whether a tier downgrade will recover it or whether you should cut it off immediately at the input stage. In an implementation that doesn't retain errors, the three sections of "wait / give up / feed back" can't exist in the first place, so error retention is a design precondition, not a feature addition. **Trying every tier without checking`moderation_stage`** Concepts that stopped with `moderation_stage: input` were tried through all stages from T1 to T3 and lost all 9 attempts. Ones that stopped with `moderation_stage: output` have a track record of passing with a tier downgrade (`partial 01_feet_up_window` at T2, `03_legs_on_bed_topdown` at T3). A single word in T1's `moderation_stage` field determines whether climbing down the ladder is worth anything, yet trying every stage without reading the field wastes up to 21 minutes per concept — up to 63 minutes for three. Judge "is it worth climbing down based on the first-stage error body" before "how many safe-side fallback stages are there." **Wiring a fallback without measuring the destination's capacity** Before adding a fallback in `social-autolike`'s `follow-source-followers`, I first measured that the UI modal's following side grows to 408 entries in 63 seconds while the followers side stops at 12. That's why I adopted only the side with capacity as the fallback. Wire it without measuring and all the traffic flows to a destination with no capacity, producing "I added a fallback but the success rate didn't change." `outreach-multi`'s pk resolution likewise builds in the known wall of IG cutting off around 70 entries, limiting to 30 per run. **Counting a permanent failure against the "one more try" budget** In `outreach-multi`'s DM sending, when `no メッセージ button` (DM-disabled setting) came back, it rode the `exhausted after 3 attempts` path and consumed 3 attempts' worth of send budget per recipient. The other party's account settings don't change no matter how much we retry. Read the `reason` field in `sent.jsonl`, put recipients containing `no メッセージ button` / `profile gone` / `このページは存在しません` into a `permanentlyFailed` Set, and place the `shouldSkip()` check ahead of `exhausted`. This shares a root with the "inverse incident of recording a transient failure as permanent" mentioned earlier: **the design of failure classification is needed before the design of retry counts.** **Continuing to hit at the same rate after a 429** `social-autolike`'s ig-3 fired 17 sources within a few seconds at 0.3–0.5 second intervals and produced zero results over 14 hours. If you don't change the interval after receiving a 429, you pile up the same requests during the limit and extend your own throttling. The fix recorded in `retry-and-giveup-design.md` includes a latch that cuts off that process's API calls after 3 consecutive 429s, plus a design that drops `concurrency` to 1 for any shop that has seen a 429 even once. Slowing down the rate itself comes as a set with waiting before retrying — not instead of it. **Retries stacked in two layers** `watch-arb`'s `ingestShop` had its own 3-attempt loop, and `req()` had a 3-attempt loop as well. Worst case 3×3 = 9 attempts, with "retry attempt 3/3" appearing twice in the log, making it unreadable where the limit is and which attempt you're on. Even with fault injection in place, you can no longer tell "which layer is running." The fix was to delete `ingestShop`'s own loop and consolidate onto the single layer in `req()`. The caller just throws immediately. **Counting fallback successes toward the cutoff counter** The latch that "cuts off API calls after 3 consecutive 429s" was also counting cases where the UI fallback succeeded. Even with an escape route, it stopped at 3 items. The cutoff counter should increment only "when every path failed." Cases that succeeded via the fallback shouldn't be logged against the main failure count. **Judging "couldn't fetch (abnormal)" and "had no time to fetch (a normal cutoff)" by the same zero count** When `follow-source-followers` uses up its time budget on source fetching alone and ends with zero candidates, it falls into the existing "all sources failed → halt to prevent mis-operation" path. "Couldn't fetch" and "just ran out of time" are separate events. Judge them by the same zero count and you get false alarms every day and lose the ability to tell real failures apart. Prepare a separate exit called `time-budget` to distinguish them. **Hardcoding the give-up predicate inside the`catch` block** When the cutoff conditions are written inside a `catch`, you can't put them into boundary-case tests. Pull them into a pure function and you can add one case with the actual values from the incident day, giving a machine guarantee that the same condition never gets through again. As long as the logic lives inside the `catch`, the test passes in a state where "the case just happened not to be exercised." **Error messages carrying no machine-readable information** Throw a `ValidationError` with only a string and you can't extract "what was wrong" from code at retry time. The reason throwing the same prompt from the second attempt onward gives the same result is that "feed back" isn't implemented; put on a structure like `code: "title-too-long"`, `actual: 63`, `limit: 58` and you can assemble correction instructions dynamically. When returning to the LLM, pass a human-language instruction and the measured values, not the identifier (`"title-too-long"`). Passing the identifier through as-is loses three times in a row. ## Best practices Here are the rules distilled from the five-lane simultaneous outage. There are a lot of them, but every one is something I added because I actually got stuck. **① Classify failures by HTTP status and exception type before recording them** `429/503 → rate_limited` / `401/403 → auth_error` / everything else → processing error. Just placing this three-way branch first changes the meaning of the "list of failing shops" shown on the dashboard. After adding this classification, `watch-arb` also split the wording of shops.last_error into "couldn't fetch due to rate limiting" and "can't extract the structure." **② When you write a retry, put fault injection in the same commit** let _failOnceRemaining = parseInt(process.env.WA_API_FAIL_ONCE ?? '0', 10); // 起動後の最初のN回だけ合成のエラーを投げる The completion criteria are a set of two. With `=2`, `retry attempt=1/3` and `2/3` appear and the run completes at exit 0; with it unset, not a single retry log line appears. Without ②, you can't confirm with the same command that "the fault injection hasn't leaked into production." **③ Place the cutoff on both "attempt count" and "absolute time since intake"** Attempt count alone lets through "only the 1st attempt, but resume is 12 hours away." Absolute time alone lets through "resume is soon, but the limit hits back to back." The `kotonoha` incident got caught on the first attempt, on the time-condition side (intake `2026-08-19T00:36:57Z`, resume `06:37:00Z`, outside intake+3 hours). A case where the value of doubling up was measurable in real data. **④ Decide the total wait ceiling in seconds first, then back-calculate the attempt count** For `gsheets_retry.py`, I fixed "never exceed 8 seconds total" in the request text first, then back-calculated 2s → 6s (3 attempts). If you fix exponential backoff by attempt count first, the caller's slot time gets quietly eroded. **⑤ Throw immediately on 401/403/404 — don't hide them in a retry** if e.resp.status not in RETRYABLE_HTTP: raise # 権限エラーは即 raise Wrap a permission error in a retry and only "failed 3 times" remains, burying the real cause of "no permission." Same structure as the "429 masquerading as `extract_failed`" recorded in `transient-failure-recorded-as-permanent.md` — this time a 401 masquerades as "sometimes slow." **⑥ Use`moderation_stage` to judge whether climbing down the ladder is worth it before trying tiers** if result.get("moderation_stage") == "input": log("input段の拒否。残りTierをbreak") break Adding one line turns 63 minutes of spinning into 0. Ones that stopped at output are worth running the ladder for. Ones that stopped at input get discarded concept and all, and you move on. **⑦ Consolidate retry layers into one place** If the caller has its own loop and the library also retries, you get up to N×M attempts. When you find duplication, delete the outer loop and consolidate onto the single layer in `req()`. **⑧ Measure the destination's capacity before wiring it up** Before adding a fallback, confirm by measurement "how many entries this destination returns" and "where it jams." Routing all traffic to a destination with no capacity won't change the success rate. **⑨ Record permanent failures in a persistent list once and exclude them** Failures rooted in the other party's settings, like `no メッセージ button` / `profile gone`, should be excluded before they consume retry attempts. Place the `shouldSkip()` check ahead of `exhausted`. **⑩ Actually hit it to check "would it pass if I ran it again right now?" before classifying it as a human task** I snap-judged Cloudflare's `Authentication error` as "OAuth expired" and left it in a human queue for three days. Running probes in both a shell and a minimal launchd environment, all exited 0. Don't classify something as "unfixable, period" from the name of the error alone. Check, then judge. **⑪ Log the fact that a limit constant was hit** `text.slice(0, 400_000)` just silently truncated. Even when a protective constant becomes the trigger for a permanent failure, nothing is left in the log. Complete `JSON.parse` internally and return the first 300 characters for diagnostics only when it fails, and the fact that "it got cut off" survives in the record. **⑫ After introducing retries, check how the range of exception propagation changed** Adding `withRetry` can make required downstream processing unreachable where the behavior changed from "swallow" to "throw." A retry isn't a feature addition; it's a change to how exceptions propagate. Where there's something downstream that "must happen even on failure," wrap it individually. **⑬ For failures caused by resource pressure, wait before retrying** With `setContentSafely`'s retry interval at zero seconds, it fails again under the same conditions right after failing under high load. Immediate retry with no sleep is correct for deterministic failures, but on the flip side, a retry is meaningless unless you wait for resources. Choose where the sleep goes based on the kind of failure. **⑭ Pull the give-up predicate into a pure function and put the incident day's actual values into the tests** Extract it into `shouldGiveUpForQuota({quotaHits, createdAtMs, resumeAtMs})` and add intake `2026-08-19T00:36:57Z` / resume `06:37:00Z` as one case, and the same incident can never get through again. A predicate hardcoded inside a `catch` block can't have its boundary cases tested. **⑮ Leave the fact that you waited, gave up, or cut off on a single log line** `waited=600s SKIP: slot timeout` and `SKIP: global limit reached` (immediate discard) are different events. If both look like the same "SKIP," you can't distinguish afterward whether "slot contention increased" or "it was discarding immediately all along." Emit the wait time you invested as a number in the log. ## Summary When you write "failed, so try again," that code has no answers to three questions. Will waiting fix it? When do you cut it off? What do you change before the next attempt? An implementation missing these three burns resources in front of a deterministic failure and stalls. What I noticed when I lined them up on the morning five lanes went down together: whether I have to be personally involved in incident recovery is almost entirely determined by whether this design is present. With it, the script decides its own next action per kind of failure. Without it, the same failure just gets recorded three times, and the next morning it's stuck in the same place. An automation environment runs every day not because I write code every day. It works because failures are classified correctly and the machine recovers on its own. From that premise, retry design isn't "a feature you add later" — it belongs in the first implementation. "I wrote the retry" counts as done when it's "I watched it run with fault injection." What's the failure in your own automation that's been recorded under the wrong name the longest? _Written by **Lily_ * — I ship iOS apps and automate my content stack with Claude Code. Follow along: Portfolio · X · GitHub*
dev.to
August 30, 2026 at 11:11 AM
bsky.app/profile/iwil....

What are mainstream media networks NOT reporting about #Goolog_Goon Squads and #ICE_Demesticterrorists in America.

The videos come from citizens recording their human rights abuses.

#DO_Something‼️ ❣️
PROTECT your NEIGHBORS and
COMMUNITIES
August 23, 2026 at 8:36 PM
In Pascal, the 'if' needs to have the full "begin … end;" if it isn't a single line. Read:

// Single line
if SomeBoolean then
DoSomething;

// Multi-line
if SomeBoolean then
begin
DoSomething;
DoOtherThing;
end;

Ada's more concise:

if Some_Boolean then
Do_Something;
Do_Other_Thing;
end if […]
Original post on ruby.social
ruby.social
August 20, 2026 at 1:09 AM
bsky.app/profile/demc...

Talk is useless without actions to back it up.
#Leadership_Matters
#VotingIS_OurVoice
Don't try.. Just #DO_Something
Politicians love screaming "support our troops" until actual troops and families ask for basic empathy. Calling military families liars when the proof is sitting on their phones isn't leadership, it's cowardice. Try taking accountability for once.
August 17, 2026 at 4:48 PM
See it, know it anf for the sake of democratic freedoms..
#DO_Something
August 13, 2026 at 2:21 AM
Pathetic truism about humanity's reluctance or inability to source facts then act upon them. Its a whole lot of smoke and mirrors.
#Seek_SpeakTruth
Then #DO_Something
August 3, 2026 at 2:01 AM
Stuck between two walls is typical #TrumpleDUMBskin leadership. Why us he still in office.

#LookToSeeIT
#DO_Something
July 31, 2026 at 10:00 PM
On full global view #Despicable_Don. He's an international embarrassment with no shame. Ditto to all who contune to allow him to ruin lives, sanction #ICE_Demesticterrorists to murder innocent Americans in the streets, break every pre election promises & so much more.
#DO_Something and
#Dump_Trump
July 30, 2026 at 3:09 AM
c23's typeof goes so fucking hard

void* GetProcAddress(HModule dll, const char* name);
\#define load_func(module, func) ((typeof(func)*)GetProcAddress(module, \#func))

// from some header
int do_something(int a);

// type checked!
int (*imported)(int some_param) = load_func(module, do_something);
July 24, 2026 at 3:47 PM
VIA Lloyd Miller

Here we go with another insane recap. Recap with ICE killings around the country, and the Iran War back in force! These cartoons represent just some of the craziness.

#StopThisInsaniry
#DO_Something
#Resist_Persist
July 16, 2026 at 7:14 PM
… and you, the USA people, vote for him. #DO_SOMETHING
July 11, 2026 at 1:45 PM
Election are choices & have consequences.
Voting or not an individual responsibility.

Whatever the reasons #VotingIS_OurVoice.

This is the consequences of voting for #DJT_JustAMan not a Savior sent by Divine Source. Own it. Then #DO_Something ⁉️👇👇👇👇👇👇👇👇👇👇👇💩 💩 💩 🪠 🪠 🪠 🤡🎯🐷🐖
July 7, 2026 at 2:00 AM
I can also get an iteration step at different parts of code, in different ways, or different loops, for example:
```
while my_iterator.can_forward():
do_something(my_iterator.iteration)
while my_iterator.can_backward():
# Now we want to backtrack
do_something(my_iterator.iteration)
```
July 3, 2026 at 6:21 AM
Ahhh yes here he is again and here it is, again. Another irrational knee jerk #Man_Child_47 choice with rippling global reactions. Stupid is as stupid does...again.

#TrumpleDUMBskin
#StopThisInsaniry
#Demented_Donald
#DO_Something
June 24, 2026 at 12:05 AM
The straight talk reality check.
#Truth_Bombs
#Corrupt_Congress....#DO_Something
June 20, 2026 at 10:59 PM
We Need To "Claw" Back All Of
Our Money, From Fraud Felonious !
June 15, 2026 at 10:52 PM
Then you'll recognize that writing
My . prototype . do_something = ...function def...
is correct but unusual, and you'll allow to replace it with

class My:
function do_something() ... function def ...

and let the piler/preter rewrite it automatically.
May 27, 2026 at 7:50 PM
Then you could reinvent the prototypes of JavaScript. That's a record containing functions; other records know it under the special key "prototype"; and when linking the expression

someone . do_something(...)

the piler/preter looks for do_something in someone and in someone.prototype.
May 27, 2026 at 7:50 PM