← back to Slack Idea Board
Idea-board: auto-retry throttled launches with live countdown (429 self-heals)
502e68120cfd7e09bbd7cb8c5cc2d4f179451004 · 2026-08-06 16:53:31 -0700 · Steve Abrams
Files touched
Diff
commit 502e68120cfd7e09bbd7cb8c5cc2d4f179451004
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Aug 6 16:53:31 2026 -0700
Idea-board: auto-retry throttled launches with live countdown (429 self-heals)
---
public/index.html | 26 ++++++++++++++++++++++----
1 file changed, 22 insertions(+), 4 deletions(-)
diff --git a/public/index.html b/public/index.html
index edaa6b7..89ee279 100644
--- a/public/index.html
+++ b/public/index.html
@@ -134,17 +134,35 @@ async function copyAction(btn){
// survives re-render (the per-button `disabled` flag does not — render() replaces the node); (3) the
// POST carries confirm:true, which the server now requires before it will spawn anything.
const INFLIGHT = new Set();
+const sleep = ms => new Promise(r=>setTimeout(r,ms));
+async function postBuild(id, step){
+ const r=await fetch('/api/build',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({id,step,confirm:true})});
+ return r.json();
+}
async function launchAction(id, step, label, btn){
const key=(id||'')+'|'+step;
if(INFLIGHT.has(key)) return; // a launch for this id+step is already running
if(!confirm(`Launch a REAL Claude Code session in a new iTerm2 tab?\n\n${label||step}`)) return;
INFLIGHT.add(key);
- if(btn){ btn.classList.add('copied'); var prev=btn.textContent; btn.textContent='launching…'; btn.disabled=true; }
+ var prev = btn ? btn.textContent : '';
+ if(btn){ btn.classList.add('copied'); btn.textContent='launching…'; btn.disabled=true; }
+ const setLbl = t => { if(btn && btn.isConnected) btn.textContent=t; };
try{
- const r=await fetch('/api/build',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({id,step,confirm:true})});
- const j=await r.json();
+ // Self-heal the server's 429 circuit-breaker: a throttled POST consumes no slot, and the
+ // 30s rolling window frees a slot as old spawns age out — so we just re-POST the SAME launch
+ // on a fixed cadence (with a visible countdown) until it clears or we hit the hard deadline.
+ const MAX_WAIT_MS = 40000, RETRY_MS = 4000; // one full window + buffer
+ const deadline = Date.now() + MAX_WAIT_MS;
+ let j;
+ while(true){
+ j = await postBuild(id, step);
+ if(!j.throttled) break; // launched / failed / needs-confirm → done
+ if(Date.now() + RETRY_MS > deadline) break; // give up → falls through to the throttle toast
+ for(let s=Math.round(RETRY_MS/1000); s>0; s--){ setLbl(`throttled · retry ${s}s`); await sleep(1000); }
+ }
if(j.launched){ toast(`🚀 <b>${esc(label||step)}</b> launched in a new iTerm2 tab<br><code>${esc((j.prompt||'').slice(0,160))}…</code>`); }
- else if(j.throttled){ toast('⏳ Throttled — '+esc(j.reason||'too many launches, try again shortly')); }
+ else if(j.throttled){ toast('⏳ Still throttled after retries — '+esc(j.reason||'try again shortly')); }
+ else if(j.needsConfirm){ toast('Launch blocked — server requires confirm'); }
else { toast('Launch failed: '+esc(j.error||'unknown')); }
}catch(err){ toast('Launch failed: '+esc(String(err.message||err))); }
finally{ INFLIGHT.delete(key); if(btn && btn.isConnected){ btn.classList.remove('copied'); btn.textContent=prev; btn.disabled=false; } }
← 5e09083 Loosen idea-board build throttle: 20/30s cap, 2s cooldown
·
back to Slack Idea Board
·
Idea-board: launch in a NEW iTerm2 window (not a tab); lift 0638b1f →