System Status
Every moving part - refresh every 10s. Click any bot card for its detail page.Overall: 57 online · 11 degraded · 34 offline · 0 error / 102 checks
As of 2026-06-14 19:52:37 UTC
Core Infrastructure
5🟢 · 1🔴 · DB, cache, external feeds, proxy poolPostgres 🟢 ONLINE
connectable · 0s ago
primary store (paper_account, paper_positions, copy_bot_signals, etc.)
Redis (truth_api cache) 🟢 ONLINE
0 keys, 0% hit rate · 0s ago
hot-path cache for wallet state, leaderboard, market state
Polymarket CLOB WebSocket 🟢 ONLINE
3 subscribed, 38,207 msgs · 0s ago
real-time orderbook + market_resolved events
Coinbase BTC feed 🟢 ONLINE
1m ago · 1m ago
5s BTC spot polls into the 5m up/down bot
Polymarket gamma API 🟢 ONLINE
trade 59s ago · 59s ago
market discovery + resolution data feed
Proxy pool 🔴 OFFLINE
0/0 hot · 0s ago
no proxies registered
Daemons (systemd services)
4🟢 · 2🔴 · inferred from DB writes - if a daemon stops, you see it here firstpolybot-bot (wallet poller) 🟢 ONLINE
1m ago · 1m ago
polls 1,250 wallets across T1/T2/T3 tiers for new trades
polybot-copybot (user-side bots) 🟢 ONLINE
43s ago · 43s ago
runs 20 user-side bots (24 heartbeat rows)
polybot-claude-agents (Claude-side) 🔴 OFFLINE
2d ago · 2d ago
runs 20-22 Claude-side bots (21 heartbeat rows) - DEAD, daemon may be stopped or crashed
polybot-claude-btc (BTC 5m bot) 🔴 OFFLINE
21h ago · 21h ago
places BTC up/down bets on the 'default' paper account every 5m - DEAD, daemon may be stopped or crashed
polybot-claude-mart (martingale variants) 🟢 ONLINE
0s ago · 0s ago
5 martingale ladder variants on the BTC 5m signal
polybot-dashboard (this page) 🟢 ONLINE
serving · 0s ago
Flask web UI on :80
Bots (45 agents)
24🟢 · 10🟡 · 11🔴 · heartbeat freshness · green <180s · yellow <450s · red ≥450s or paused43s ago · 732,308 evals, 0 copies
43s ago · 732,333 evals, 0 copies
43s ago · 732,333 evals, 0 copies
43s ago · 732,333 evals, 0 copies
43s ago · 732,333 evals, 0 copies
43s ago · 732,333 evals, 0 copies
43s ago · 732,333 evals, 0 copies
43s ago · 732,333 evals, 0 copies
43s ago · 732,308 evals, 0 copies
43s ago · 732,333 evals, 0 copies
43s ago · 732,333 evals, 2 copies
43s ago · 732,333 evals, 0 copies
43s ago · 732,333 evals, 0 copies
43s ago · 732,333 evals, 1 copies
43s ago · 732,333 evals, 0 copies
43s ago · 732,333 evals, 0 copies
43s ago · 732,333 evals, 0 copies
43s ago · 732,333 evals, 0 copies
43s ago · 732,333 evals, 0 copies
43s ago · 732,333 evals, 2 copies
43s ago · 732,333 evals, 0 copies
43s ago · 732,333 evals, 0 copies
43s ago · 732,333 evals, 0 copies
43s ago · 732,333 evals, 0 copies
7d ago · Phase 1 bleeder triage 2026-06-04 — lost 30%+ since 06-03 re
7d ago · no recent heartbeat
7d ago · Phase 1 bleeder triage 2026-06-04 — lost 30%+ since 06-03 re
7d ago · no recent heartbeat
7d ago · Phase 1 bleeder triage 2026-06-04 — lost 30%+ since 06-03 re
7d ago · Phase 1 bleeder triage 2026-06-04 — lost 30%+ since 06-03 re
7d ago · no recent heartbeat
7d ago · no recent heartbeat
7d ago · no recent heartbeat
7d ago · Phase 1 bleeder triage 2026-06-04 — lost 30%+ since 06-03 re
7d ago · Phase 1 bleeder triage 2026-06-04 — lost 30%+ since 06-03 re
7d ago · Phase 1 bleeder triage 2026-06-04 — lost 30%+ since 06-03 re
7d ago · no recent heartbeat
7d ago · no recent heartbeat
7d ago · Phase 1 bleeder triage 2026-06-04 — lost 30%+ since 06-03 re
7d ago · Phase 1 bleeder triage 2026-06-04 — lost 30%+ since 06-03 re
7d ago · no recent heartbeat
7d ago · Phase 1 bleeder triage 2026-06-04 — lost 30%+ since 06-03 re
7d ago · no recent heartbeat
2d ago · no recent heartbeat
7d ago · no recent heartbeat
Signal flow (last hour)
24🟢 · 1🟡 · 20🔴 · acted/total signals per bot · 0 = scanner silent · all-skipped = config too tightAGENT_EDGE_NET_KELLY 🟢 ONLINE
0/2067 1h · 0s ago
2067 signals 1h, all skipped - bot alive and filtering correctly (e.g. strict consensus / edge-net thresholds)
AGENT_HOLD_TO_SETTLE 🟢 ONLINE
0/85 1h · 11m ago
85 signals 1h, all skipped - bot alive and filtering correctly (e.g. strict consensus / edge-net thresholds)
AGENT_LAMBDA_TAIL 🟢 ONLINE
0/190 1h · 11m ago
190 signals 1h, all skipped - bot alive and filtering correctly (e.g. strict consensus / edge-net thresholds)
AGENT_NOTHING_HAPPENS 🟢 ONLINE
0/0 1h · -
0 signals 1h but bot heartbeat-fresh - idle (scanner-driven or strict filter); not an outage
AGENT_SPORTS_FLB 🟢 ONLINE
0/0 1h · -
0 signals 1h but bot heartbeat-fresh - idle (scanner-driven or strict filter); not an outage
CLAUDE_CONSENSUS_PROB 🔴 OFFLINE
0/0 1h · -
0 signals last hour AND heartbeat stale - real outage; check the daemon
CLAUDE_DEEP_TAIL 🔴 OFFLINE
0/0 1h · -
0 signals last hour AND heartbeat stale - real outage; check the daemon
CLAUDE_ENSEMBLE 🔴 OFFLINE
0/0 1h · -
0 signals last hour AND heartbeat stale - real outage; check the daemon
CLAUDE_FAST_REACT 🔴 OFFLINE
0/0 1h · -
0 signals last hour AND heartbeat stale - real outage; check the daemon
CLAUDE_FRESH_FLIP 🔴 OFFLINE
0/0 1h · -
0 signals last hour AND heartbeat stale - real outage; check the daemon
CLAUDE_HIGH_TAIL 🔴 OFFLINE
0/0 1h · -
0 signals last hour AND heartbeat stale - real outage; check the daemon
CLAUDE_MAKER 🔴 OFFLINE
0/0 1h · -
0 signals last hour AND heartbeat stale - real outage; check the daemon
CLAUDE_MID_VOLUME 🔴 OFFLINE
0/0 1h · -
0 signals last hour AND heartbeat stale - real outage; check the daemon
CLAUDE_PMXT_ARB 🔴 OFFLINE
0/0 1h · -
0 signals last hour AND heartbeat stale - real outage; check the daemon
CLAUDE_PRE_RESOLVE 🟡 DEGRADED
mothballed · -
MOTHBALLED (starting_balance=0) - by design, e.g. OPT-58 symmetry mothball; not a real outage
CLAUDE_QUALITY_KELLY 🔴 OFFLINE
0/0 1h · -
0 signals last hour AND heartbeat stale - real outage; check the daemon
CLAUDE_RESOLUTION_SNIPER 🔴 OFFLINE
0/0 1h · -
0 signals last hour AND heartbeat stale - real outage; check the daemon
CLAUDE_REVERSAL_HUNTER 🔴 OFFLINE
0/0 1h · -
0 signals last hour AND heartbeat stale - real outage; check the daemon
CLAUDE_SHARP_CONVERGENCE 🔴 OFFLINE
0/0 1h · -
0 signals last hour AND heartbeat stale - real outage; check the daemon
CLAUDE_SHARP_FOLLOW 🔴 OFFLINE
0/0 1h · -
0 signals last hour AND heartbeat stale - real outage; check the daemon
CLAUDE_SHARP_PROVEN 🔴 OFFLINE
0/0 1h · -
0 signals last hour AND heartbeat stale - real outage; check the daemon
CLAUDE_SPORTS_MIRROR 🔴 OFFLINE
0/0 1h · -
0 signals last hour AND heartbeat stale - real outage; check the daemon
CLAUDE_TAIL_DISCIPLINED 🔴 OFFLINE
0/0 1h · -
0 signals last hour AND heartbeat stale - real outage; check the daemon
CLAUDE_VOL_FADE 🔴 OFFLINE
0/0 1h · -
0 signals last hour AND heartbeat stale - real outage; check the daemon
CLAUDE_WHALE_LOOSE 🔴 OFFLINE
0/0 1h · -
0 signals last hour AND heartbeat stale - real outage; check the daemon
CLAUDE_WHALE_PRECISION 🔴 OFFLINE
0/0 1h · -
0 signals last hour AND heartbeat stale - real outage; check the daemon
COPY_BIG_TICKET 🟢 ONLINE
0/198 1h · 36m ago
198 signals 1h, all skipped - bot alive and filtering correctly (e.g. strict consensus / edge-net thresholds)
COPY_CONSENSUS 🟢 ONLINE
0/1905 1h · 0s ago
1905 signals 1h, all skipped - bot alive and filtering correctly (e.g. strict consensus / edge-net thresholds)
COPY_CRYPTO_ELITE 🟢 ONLINE
0/32 1h · 49m ago
32 signals 1h, all skipped - bot alive and filtering correctly (e.g. strict consensus / edge-net thresholds)
COPY_CRYPTO_QUALITY 🟢 ONLINE
0/2067 1h · 0s ago
2067 signals 1h, all skipped - bot alive and filtering correctly (e.g. strict consensus / edge-net thresholds)
COPY_FADE 🟢 ONLINE
0/1 1h · 53m ago
1 signals 1h, all skipped - bot alive and filtering correctly (e.g. strict consensus / edge-net thresholds)
COPY_FAST_QUALITY 🟢 ONLINE
0/2067 1h · 0s ago
2067 signals 1h, all skipped - bot alive and filtering correctly (e.g. strict consensus / edge-net thresholds)
COPY_HIGH_PRICE 🟢 ONLINE
0/2067 1h · 0s ago
2067 signals 1h, all skipped - bot alive and filtering correctly (e.g. strict consensus / edge-net thresholds)
COPY_HOTHAND 🟢 ONLINE
0/0 1h · -
0 signals 1h but bot heartbeat-fresh - idle (scanner-driven or strict filter); not an outage
COPY_HOTHAND_FAST 🟢 ONLINE
0/1 1h · 38m ago
1 signals 1h, all skipped - bot alive and filtering correctly (e.g. strict consensus / edge-net thresholds)
COPY_LATE_RESOLVE 🟢 ONLINE
0/2067 1h · 0s ago
2067 signals 1h, all skipped - bot alive and filtering correctly (e.g. strict consensus / edge-net thresholds)
COPY_LOW_PRICE 🟢 ONLINE
0/2067 1h · 0s ago
2067 signals 1h, all skipped - bot alive and filtering correctly (e.g. strict consensus / edge-net thresholds)
COPY_NEW_LISTING 🟢 ONLINE
0/19 1h · 51m ago
19 signals 1h, all skipped - bot alive and filtering correctly (e.g. strict consensus / edge-net thresholds)
COPY_RWO_BTC 🟢 ONLINE
0/0 1h · -
0 signals 1h but bot heartbeat-fresh - idle (scanner-driven or strict filter); not an outage
COPY_SHARP 🟢 ONLINE
0/0 1h · -
0 signals 1h but bot heartbeat-fresh - idle (scanner-driven or strict filter); not an outage
COPY_SPORTS_QUALITY 🟢 ONLINE
0/2067 1h · 0s ago
2067 signals 1h, all skipped - bot alive and filtering correctly (e.g. strict consensus / edge-net thresholds)
COPY_VOLUME_1K 🟢 ONLINE
0/116 1h · 36m ago
116 signals 1h, all skipped - bot alive and filtering correctly (e.g. strict consensus / edge-net thresholds)
COPY_WHALE 🟢 ONLINE
0/68 1h · 54m ago
68 signals 1h, all skipped - bot alive and filtering correctly (e.g. strict consensus / edge-net thresholds)
INVERSE_0X_0C16 🟢 ONLINE
0/0 1h · -
0 signals 1h but bot heartbeat-fresh - idle (scanner-driven or strict filter); not an outage
INVERSE_SHARKY_BTC 🟢 ONLINE
0/28 1h · 54m ago
28 signals 1h, all skipped - bot alive and filtering correctly (e.g. strict consensus / edge-net thresholds)
Recent auto-killer events
Rules A1-A6 reset, Rule C unpause, manual events - last 15| When | Bot | Action | Reason |
|---|---|---|---|
| 20m ago | REVERSE_FADE_KELLY | 🟡 auto_reset | wr=0.0% < 35% after 217 bets |
| 20m ago | STRONGEST_SIGNAL_FOLLOWER | 🟡 auto_reset | wr=2.5% < 35% after 510 bets |
| 20m ago | MART_BUST_TEST | 🟡 auto_reset | wr=0.0% < 35% after 422 bets |
| 20m ago | REGIME_AWARE_BTC | 🟡 auto_reset | wr=8.3% < 35% after 133 bets |
| 20m ago | STREAK_FOLLOW_K4 | 🟡 auto_reset | wr=0.0% < 35% after 55 bets |
| 20m ago | STREAK_REV_KELLY | 🟡 auto_reset | wr=4.1% < 35% after 123 bets |
| 20m ago | INVERSE_WHALE_FOLLOW_1TO1 | 🟡 auto_reset | wr=1.6% < 35% after 448 bets |
| 20m ago | INVERSE_WHALE_FOLLOW_V2 | 🟡 auto_reset | wr=2.8% < 35% after 142 bets |
| 20m ago | MTF_RSI_BB_WIDE_DOWN | 🟡 auto_reset | wr=0.0% < 35% after 30 bets |
| 20m ago | QUANTUM_BRAIN | 🟡 auto_reset | wr=2.3% < 35% after 435 bets |
| 20m ago | STREAK_FADE_K4 | 🟡 auto_reset | wr=8.9% < 35% after 56 bets |
| 20m ago | STREAK_FOLLOW_K3 | 🟡 auto_reset | wr=0.0% < 35% after 121 bets |
| 20m ago | STREAK_FOLLOW_K4_MART | 🟡 auto_reset | wr=0.0% < 35% after 59 bets |
| 20m ago | STREAK_REV_3PLUS | 🟡 auto_reset | wr=4.0% < 35% after 124 bets |
| 20m ago | ULT1_TIERED_PAROLI | 🟡 auto_reset | wr=4.0% < 35% after 126 bets |
Recent trades
Last 15 paper_trades rows - BUY/SELL/RESET activity across the fleet| When | Account | Side | USD | PnL | Note |
|---|---|---|---|---|---|
| 59s ago | INVERSE_WHALE_FOLLOW_1TO1 | 🔵 BUY | $40.00 | $+0.00 | |
| 59s ago | INVERSE_WHALE_FOLLOW | 🔵 BUY | $40.00 | $+0.00 | |
| 1m ago | WHALE_FOLLOW | 🔵 BUY | $40.00 | $+0.00 | |
| 1m ago | INVERSE_BOOK_FAKE_FADE | 🔵 BUY | $15.00 | $+0.00 | |
| 1m ago | BOOK_FAKE_FADE | 🔵 BUY | $15.00 | $+0.00 | |
| 1m ago | WHALE_FOLLOW_CAP55 | 🔵 BUY | $40.00 | $+0.00 | |
| 1m ago | ENSEMBLE_VOTE_4_V2 | 🔵 BUY | $30.00 | $+0.00 | |
| 1m ago | STRONGEST_SIGNAL_FOLLOWER | 🔵 BUY | $30.00 | $+0.00 | |
| 2m ago | QUANTUM_BRAIN | 🔵 BUY | $112.14 | $+0.00 | |
| 2m ago | MART_BUST_TEST | 🔵 BUY | $5.00 | $+0.00 | |
| 2m ago | MART_PURE | 🔵 BUY | $10.00 | $+0.00 | |
| 2m ago | INVERSE_BINANCE_FRONT_RUN_CAP55 | 🔵 BUY | $40.00 | $+0.00 | |
| 2m ago | BINANCE_FRONT_RUN_CAP55 | 🔵 BUY | $40.00 | $+0.00 | |
| 5m ago | WHALE_FOLLOW | 🔵 BUY | $40.00 | $+0.00 | |
| 6m ago | STRONGEST_SIGNAL_FOLLOWER | 🔵 BUY | $30.00 | $+0.00 |
Open positions - honest breakdown
Total 16 open paper_positions split by where they are in the resolution lifecycle. FUTURE = market still running (normal). AWAITING = past end_date, Polymarket finalizing. STUCK = past_due >24h - real attention item.16 FUTURE - market still running
0 AWAITING - PM oracle pending
0 STUCK >24h - needs attention
Bug-fixes log (167 deployed · ★ = G0-readiness blocker · 51 ★)
audit trail of bug fixes applied across all 42 bots/agents · most recent first| ID | Round | Fix | Scope | G0? |
|---|---|---|---|---|
| BUG-21 | R3 | Closed positions LIMIT raised 50 → 200 | copy + claude detail pages | |
| BUG-22 | R3 | Per-worker cache divergence - documented + auto-bust via last_reset_at | agent detail caches | |
| BUG-23 | R3 | Per-bot threading.Lock on cache rebuild - eliminates thundering herd | agent detail rebuild path | |
| BUG-24 | R3 | Removed wasted calendar_seed/breakdown_seed compute | snapshot helper | |
| BUG-25 | R3 | Cache key includes last_reset_at - auto-busts on OPT-129 cross-process | copy + claude detail | ★ |
| BUG-26 | R3 | Heatmap n filtered to closed statuses (no open/refunded inflation) | copy + claude detail | |
| BUG-27 | R3 | Heatmap wins include exit-rule statuses (OPT-54b class miss) | copy + claude detail | |
| BUG-28 | R3 | agg query: ties_or_refunds surfaced explicitly | copy + claude detail | |
| BUG-29 | R3 | Refunded vs won/lost: explicit ties_or_refunds field | copy + claude detail | |
| BUG-30 | R3 | avg_position_usd uses closed signals only (matches avg_hold_time) | fact_sheet | |
| BUG-31 | R3 | Sharpe requires ≥5 daily samples - undersampled shows '- (Nd)' | fact_sheet | |
| BUG-32 | R3 | Profit factor labeled as daily-aggregate (was misleadingly per-trade-implied) | fact_sheet | |
| BUG-33 | R3 | Refund-like closed rows render '-' instead of fake '0.00c' | pos table | |
| BUG-34 | R3 | All timestamps now show 'UTC' suffix | pos table | |
| BUG-35 | R3 | Every numeric cell carries data-sort - sort works correctly | pos table | |
| BUG-36 | R3 | thesis + name + bot_id HTML-escaped in header | detail header | |
| BUG-37 | R3 | brain_mini telemetry tokens have hover tooltips | pos table | |
| BUG-38 | R3 | Calendar/breakdown JS has try/catch with in-UI error display | detail JS | |
| BUG-39 | R3 | Mothballed bot detection documented + tag surfacing intact | _agent_known | |
| BUG-40 | R3 | Heatmap title clarifies 'decides' (decided_at), not fill-time | detail render | |
| BUG-11 | R2 | Breakdown WR uses wins/(wins+losses), not wins/events | copy + claude breakdown | ★ |
| BUG-12 | R2 | Calendar + Breakdown both key on resolved_at (consistent period filter) | copy + claude detail | ★ |
| BUG-13 | R2 | Breakdown unrealized fetched live via ws_book (was hardcoded $0) | copy + claude breakdown | ★ |
| BUG-14 | R2 | Source-wallet top/bottom lists no longer overlap | copy + claude detail | |
| BUG-15 | R2 | Stale-price position cells render '-' (not entry-price) | copy + claude detail | ★ |
| BUG-16 | R2 | Alpha score uses honest daily-PnL series (not dishonest cached ROI) | fact_sheet | ★ |
| BUG-17 | R2 | Strategy tag derived from cfg.pool_builder (not blanket 'Sharp Follower') | fact_sheet tags | |
| BUG-18 | R2 | fmtUsd emits '-$100' (sign before dollar) | detail JS | |
| BUG-19 | R2 | HTML-escape + URL-encode position table data | pos table | |
| BUG-20 | R2 | '8s' stale hint replaced with accurate cadence info | detail render | |
| BUG-1 | R1 | Bankroll + ROI subtract OPT-129 reset_credits (math invariant honest) | leaderboard + detail | ★ |
| BUG-2 | R1 | Detail page price fetch uses ws_book Redis fast path | detail snapshot | |
| BUG-3 | R1 | Failed price fetch shows '-' (not fabricated breakeven) | detail snapshot | ★ |
| BUG-4 | R1 | Peer rank uses honest ROI (paper_positions, not cached state) | peer_rank | ★ |
| BUG-5 | R1 | Stale '10 copy/brain + 1 BTC' subtitle replaced with dynamic count | peer_rank header | |
| BUG-6 | R1 | Entry timing N/A for price-pinned bots (HIGH_PRICE, LOW_PRICE) | behavioral | |
| BUG-7 | R1 | Long/Short bias only counts binary markets (yes/no) | behavioral | |
| BUG-8 | R1 | Source-wallet W/L includes exit-rule wins (OPT-54b class) | source_wallet_lb | |
| BUG-9 | R1 | True median for even-N samples (peer rank + position size) | behavioral + peer_rank | |
| BUG-10 | R1 | Close-method table split: exit-rule vs natural-resolution | exit_rules render | |
| BUG-LB-21 | leaderboard | COPYBOT_JS wrapped in <script> tags (was leaking as text) | /copy-bots | ★ |
| BUG-LB-22 | leaderboard | Realized/Unrealized cells gain color via td-level class | /copy-bots | |
| BUG-C-1 | claude-parity | Claude detail: Bankroll/ROI subtract reset_credits | /claude-bots/<b> | ★ |
| BUG-C-2 | claude-parity | Claude detail: ws_book Redis fast path + stale-price tracking | /claude-bots/<b> | ★ |
| BUG-C-3 | claude-parity | Claude detail: stale-price banner + zero-PnL guard | /claude-bots/<b> | ★ |
| BUG-C-4 | claude-parity | Claude peer rank uses honest paper_positions ROI (not cached) | /claude-bots/<b> | ★ |
| BUG-C-6 | claude-parity | Claude behavioral: entry_timing N/A for price-pinned bots | /claude-bots/<b> | |
| BUG-C-7 | claude-parity | Claude behavioral: long_pct only over binary markets | /claude-bots/<b> | |
| BUG-C-8 | claude-parity | Claude source-wallet W/L includes exit-rule wins | /claude-bots/<b> | |
| BUG-C-9 | claude-parity | Claude peer rank + behavioral: true median for even-N | /claude-bots/<b> | |
| BUG-C-11 | claude-parity | Claude breakdown WR = wins/(wins+losses) | /claude-bots/<b> | ★ |
| BUG-C-14 | claude-parity | Claude source-wallet top/bottom lists no longer overlap | /claude-bots/<b> | |
| BUG-C-16 | claude-parity | Claude tags use honest alpha_score (not cached ROI) | /claude-bots/<b> | ★ |
| BUG-C-21 | claude-parity | Claude detail: closed positions LIMIT raised 50 → 200 | /claude-bots/<b> | |
| BUG-C-26 | claude-parity | Claude heatmap n filtered to closed statuses | /claude-bots/<b> | |
| BUG-C-27 | claude-parity | Claude heatmap wins include exit-rule statuses | /claude-bots/<b> | |
| BUG-C-28 | claude-parity | Claude agg query: ties_or_refunds surfaced explicitly | /claude-bots/<b> | |
| BUG-C-30 | claude-parity | Claude fact-sheet avg_position_usd: closed signals only | /claude-bots/<b> | |
| BUG-REG-1 | regression-fix | Postgres LIKE '%' literal: parameterize so psycopg shim parses cleanly | /claude-bots/<b>/peer_rank | ★ |
| BUG-BTC-1 | btc-updown | BTC account WR uses won/(won+lost), not won/n_closed (ties broke the formula) | /btc-updown scoreboard | ★ |
| BUG-BTC-2 | btc-updown | BTC account ties_or_refunds surfaced (separate from won/lost) | /btc-updown scoreboard | |
| BUG-BTC-3 | btc-updown | BTC last-bets timestamps now have explicit 'UTC' label | /btc-updown track | |
| BUG-C-LB-1 | claude-leaderboard | data-sort on every numeric cell (was mis-sorting '$-50' as +50) | /claude-bots | ★ |
| BUG-C-LB-2 | claude-leaderboard | Bankroll/Cash/Open$ unified to .2f precision (was .0f, broke reconciliation) | /claude-bots | |
| BUG-C-LB-3 | claude-leaderboard | Dot tooltip 'never seen' for ls=0 (was '1.76e9s ago') | /claude-bots | |
| BUG-C-LB-4 | claude-leaderboard | Stubbed counter now uses claude_agents.is_stubbed() (heartbeat had no 'stubbed' column) | /claude-bots health bar | ★ |
| BUG-C-LB-5 | claude-leaderboard | Table + <th> gain 'sortable' class for /copy-bots parity | /claude-bots | |
| BUG-C-LB-6 | claude-leaderboard | stub_overlay env_var HTML-escaped (defense-in-depth) | /claude-bots | |
| BUG-C-LB-7 | claude-leaderboard | Paused dot tooltip surfaces last-seen info (was just 'paused') | /claude-bots | |
| BUG-C-LB-8 | claude-leaderboard | Mothballed bots no longer inflate 'dead' counter in health bar | /claude-bots health bar | ★ |
| BUG-C-LB-9 | claude-leaderboard | W/L column data-sort=n_resolved (was lexical '10/0' < '2/0') | /claude-bots | |
| BUG-C-LB-10 | claude-leaderboard | Mothballed detection threshold simplified - starting_bankroll only (was also requiring tv≤0.0001 which residual cash broke) | /claude-bots | ★ |
| BUG-POPOVER-CLIP | cross-page | (?) popover position:fixed + JS-positioned so it escapes .card overflow clipping (was: bottom half hidden behind table frame) | /copy-bots + /claude-bots leaderboards | |
| BUG-C-LB-11 | claude-leaderboard-r2 | Hero card monetary values unified to .2f precision (was .0f - broke reconciliation with .2f rows) | /claude-bots hero | |
| BUG-C-LB-12 | claude-leaderboard-r2 | cb_total_agents queries paper_account.starting_balance instead of nonexistent cfg field (was inflating denominator to 22) | /claude-bots hero | ★ |
| BUG-C-LB-13 | claude-leaderboard-r2 | stale_sec_c clamped at 0 with skew tooltip (was rendering -43s as green pos) | /claude-bots | |
| BUG-C-LB-14 | claude-leaderboard-r2 | Hero 'Active agents' pill annotated with what's excluded (paused/mothballed/stubbed) | /claude-bots hero | |
| BUG-C-LB-15 | claude-leaderboard-r2 | _live_mtm_for_bots returns live realized_pnl_usd from paper_positions - hero + leaderboard agree to the cent | leaderboard + hero | ★ |
| BUG-C-LB-16 | claude-leaderboard-r2 | Agent name HTML-escaped on leaderboard rows + mothballed branch | /claude-bots | |
| BUG-C-LB-17 | claude-leaderboard-r2 | Dropped dead pointer_html construction (built but not returned) | /claude-bots | |
| BUG-C-LB-18 | claude-leaderboard-r2 | Dropped dead thesis_html='' placeholder | /claude-bots | |
| BUG-C-LB-20 | claude-leaderboard-r2 | Dropped dead feed_columns + skips_html builds (~330+ wasted string ops per snapshot) | /claude-bots | |
| BUG-C-LB-19 | claude-leaderboard-r2 | [partial] _strategy_meta_string is_arb check broken on Claude (cfg lacks bot_id) - documented; structural fix deferred since it requires API signature change | /claude-bots popover | |
| BUG-C-LB-21 | claude-leaderboard-r3 | Hero winner requires ≥0.05pp gap (was flipping on 0.001% noise) | /claude-bots hero | |
| BUG-C-LB-22 | claude-leaderboard-r3 | threading.Lock on _CLAUDE_SNAP_CACHE rebuild - eliminates thundering herd | snapshot cache | |
| BUG-C-LB-23 | claude-leaderboard-r3 | Per-bot brain_stats consolidated to single closed-only query (~88 fewer queries per rebuild) | snapshot rebuild | |
| BUG-C-LB-24 | claude-leaderboard-r3 | Calibration threshold ±1.0pp named CAL_EDGE_SIGNIFICANT_PP + MIN_SAMPLES=10 documented | /claude-bots | |
| BUG-C-LB-25 | claude-leaderboard-r3 | bs.n_closed includes ties_or_refunds field consistent with detail page closed_total | brain_stats | |
| BUG-C-LB-26 | claude-leaderboard-r3 | avg_edge_net filters to CLOSED signals only (no open/refunded contamination) | brain_stats | ★ |
| BUG-C-LB-27 | claude-leaderboard-r3 | avg_paid=None defers cal_edge (was defaulting to 0.5 → wrong for price-stratified bots) | brain_stats | ★ |
| BUG-C-LB-28 | claude-leaderboard-r3 | CLAUDE_BTC_5M synthetic heartbeat surfaces real eval count from btcup_bets | /claude-bots health bar | |
| BUG-C-LB-29 | claude-leaderboard-r3 | CLAUDE_BTC_5M last_seen=0 when no activity (was 'now' fallback hiding dead state) | /claude-bots health bar | ★ |
| BUG-C-LB-30 | claude-leaderboard-r3 | Synthetic CLAUDE_BTC_5M heartbeat re-injects when real one is >2h stale | /claude-bots | |
| BUG-C-LB-31 | claude-leaderboard-r3 | Dropped unused bucket_dist + category_dist + skip_reasons per-bot queries (rolled into #C-LB-23) | snapshot | |
| BUG-C-LB-32 | claude-leaderboard-r3 | pool_sizes cached separately with 5-min TTL (was scanning every 60s rebuild) | snapshot | |
| BUG-C-LB-33 | claude-leaderboard-r3 | Hero default user_total / cb_total use registry len() not stale '10' | /claude-bots hero | |
| BUG-C-LB-34 | claude-leaderboard-r3 | Symmetric pause exclusion - cb_active now also filters paused Claude agents | /claude-bots hero | ★ |
| BUG-C-LB-35 | claude-leaderboard-r3 | Hero hint clarified - 'paused + mothballed agents excluded from BOTH sides' | /claude-bots hero | |
| BUG-C-LB-36 | claude-leaderboard-r3 | Hero 'Positions / W-L' (not 'Trades') - clarifies that count includes opens, W-L only closed | /claude-bots hero | |
| BUG-C-LB-37 | claude-leaderboard-r3 | Dropped unused agg.n COUNT(*) (rolled into #C-LB-23 consolidation) | brain_stats | |
| BUG-C-LB-38 | claude-leaderboard-r3 | is_stubbed() set up to evaluate once per row pass (memoization deferred to runtime) | /claude-bots | |
| BUG-C-LB-39 | claude-leaderboard-r3 | Defensive b.get('bot_id') (KeyError-skip instead of 500 on malformed row) | /claude-bots | |
| BUG-C-LB-40 | claude-leaderboard-r3 | now_ts staleness during long render documented in code (compounds with 60s cache) | /claude-bots | |
| BUG-BTC-4 | btc-updown-r2 | Defensive recommendation lookup - unknown rec falls back to HOLD styling instead of KeyError 500 | /btc-updown hero | ★ |
| BUG-BTC-5 | btc-updown-r2 | Defensive market dict .get() - partial/malformed gamma-api responses no longer 500 the page | /btc-updown hero | ★ |
| BUG-BTC-6 | btc-updown-r2 | Market question HTML-escaped - quote/'<' in title no longer breaks layout (defense-in-depth) | /btc-updown hero | |
| BUG-BTC-7 | btc-updown-r2 | Resolution timestamp includes date when >1h out (was 'HH:MM:SS' only, ambiguous across days) | /btc-updown hero | |
| BUG-BTC-8 | btc-updown-r2 | Holders table: name HTML-escaped; full name in title attr for hover | /btc-updown holders | |
| BUG-BTC-9 | btc-updown-r2 | Holders table: data-sort attrs on Shares/Avg/Value/PnL - numeric sort works | /btc-updown holders | |
| BUG-BTC-10 | btc-updown-r2 | Holders PnL precision .0f → .2f - cent-level resolution restored | /btc-updown holders | |
| BUG-BTC-11 | btc-updown-r2 | Holders long names truncate with ellipsis (was silently cut at 18 chars) | /btc-updown holders | |
| BUG-BTC-12 | btc-updown-r2 | Track-record row: defensive .get() on side/entry_price/bet_usd - legacy rows no longer 500 | /btc-updown track | ★ |
| BUG-BTC-13 | btc-updown-r2 | Track-record numeric cells gain data-sort attrs - When/Stake/Score/Conf/PnL all sort correctly | /btc-updown track | |
| BUG-BTC-14 | btc-updown-r3 | _btcup_snapshot wraps every gamma-api call in try/except - upstream timeouts serve stale cache instead of 500-ing the page | /btc-updown snapshot | ★ |
| BUG-BTC-15 | btc-updown-r3 | threading.Lock on _BTCUP_SNAP_CACHE rebuild - eliminates duplicate gamma-api fan-out under gunicorn fork | /btc-updown snapshot | |
| BUG-BTC-16 | btc-updown-r3 | btc_updown_page route handler wraps each sub-render in try/except - one section's failure degrades to error card instead of 500-ing the entire page | /btc-updown route | ★ |
| BUG-BTC-17 | btc-updown-r3 | Scoreboard bare except now logs to stderr - silent account-vanish bug surfaces in journald | /btc-updown scoreboard | |
| BUG-BTC-18 | btc-updown-r3 | Per-account bankroll precision unified (.2f vs .0f mismatch) - cent-level reconciliation with leaderboard | /btc-updown scoreboard | |
| BUG-BTC-19 | btc-updown-r3 | Per-account rPnL pill .0f → .2f - drops <$1 rounding error vs leaderboard sum | /btc-updown scoreboard | |
| BUG-BTC-20 | btc-updown-r3 | Strip title attr escapes side+status (DB-sourced) - quote in either no longer breaks the attribute | /btc-updown scoreboard | |
| BUG-BTC-21 | btc-updown-r3 | Brain recent-bets `when` column adds 'UTC' suffix - matches track-record table above | /btc-updown brain | |
| BUG-BTC-22 | btc-updown-r3 | Brain recent-bets data-sort attrs on When/Size/Signal/BTC Δ/PnL - numeric sort no longer lexical | /btc-updown brain | |
| BUG-BTC-23 | btc-updown-r3 | Brain recent-bets HTML-escape side/acct_name/outcome - defense-in-depth + unknown-status badge renders literally instead of falsely tagged 'open' | /btc-updown brain | |
| BUG-SORT-1 | fleet-sort | Universal table sort: data-sort attribute is now SOURCE OF TRUTH - was being ignored because _detectNumericColumn's regex didn't match '$+123.45' (sign between $ and digits from :+,.2f), so PnL/ROI/Realized columns fell back to text sort and ended up sorting alphabetically by name across every page | BASE_JS / all sortable tables | ★ |
| BUG-BTC-24 | btc-updown-r4 | _render_btcup_patterns: defensive .get() on n_up/n_down/pct_up/current_streak/max_up_streak/max_down_streak - partial pattern dicts no longer 500 the section | /btc-updown patterns | ★ |
| BUG-BTC-25 | btc-updown-r4 | _render_btcup_patterns: p['seq'][-100:] now defensively defaults to [] when missing or None | /btc-updown patterns | ★ |
| BUG-BTC-26 | btc-updown-r4 | _render_btcup_patterns: run_dist/conditional/hour_dist .get() guard - AttributeError on missing parent keys eliminated | /btc-updown patterns | ★ |
| BUG-BTC-27 | btc-updown-r4 | cur_outcome else-branch no longer defaults to 🔴 - neutral '•' for unknown direction (was misleadingly red) | /btc-updown patterns | |
| BUG-BTC-28 | btc-updown-r4 | Sequence dot title attribute HTML-escapes the outcome value (defense-in-depth) | /btc-updown patterns | |
| BUG-BTC-29 | btc-updown-r4 | Run-length distribution table capped at 20 rows - last row aggregates all longer streaks (was unbounded; outlier N=50 streak rendered a 50-row table) | /btc-updown patterns | |
| BUG-BTC-30 | btc-updown-r4 | _bias_cls hoisted out of the K loop - was being redefined 6× per render | /btc-updown patterns | |
| BUG-BTC-31 | btc-updown-r4 | _render_btcup_brain: defensive .get() on won/lost/total_pnl/roi_pct/avg_pnl_per_bet/stake/open - partial brain dicts (e.g. mid-warm-up) no longer 500 | /btc-updown brain | ★ |
| BUG-BTC-32 | btc-updown-r4 | Brain breakdown row helpers (_bk_row callers) - every bucket dict accessed via .get() with safe defaults; single bad row no longer 500s the whole brain section | /btc-updown brain | ★ |
| BUG-BTC-33 | btc-updown-r4 | Hourly heatmap cells: defensive .get() on wr/n/pnl/hour + isinstance guard - missing keys/wrong types no longer 500 | /btc-updown brain | ★ |
| BUG-BTC-34 | btc-updown-r4 | streak_str streak_n==1 branch - was emitting trailing ' ·' with no value ('win ·'); now reads '1W (last bet)' / '1L (last bet)' / '-' | /btc-updown brain | |
| BUG-BTC-35 | btc-updown-r4 | Brain recent-bets Outcome col data-sort attribute - Up/Down groups correctly under our sort fix | /btc-updown brain | |
| BUG-BTC-36 | btc-updown-r4 | Bet strip: rz==0 (tie/refund/breakeven) now renders as 'bt tie' neutral state - was being shown red as 'bt lost' (false-loss visual) | /btc-updown scoreboard | |
| BUG-BTC-37 | btc-updown-r4 | Bet strip size precision: ${int(size)} dropped cents - $5.50 displayed as $5; now uses $5 for whole-dollar, $5.50 otherwise | /btc-updown scoreboard | |
| BUG-BTC-38 | btc-updown-r4 | _fetch_btc_account_summary LEFT JOIN dup-row fix: pp <-> btcup_bets relation no longer multiplies when multiple bets reference one paper_position (correlated MAX(bb.id) subquery) | /btc-updown scoreboard | ★ |
| BUG-BTC-39 | btc-updown-r4 | _render_mart_ladder: label now annotates 'CAPPED' when state.step exceeds the ladder length - operator can see terminal-exposure state | /btc-updown scoreboard | |
| BUG-BTC-40 | btc-updown-r4 | Hero 'Bet eligible?' sub-line: 'in_time_window=None · in_price_window=None' replaced with ✓/✗/- glyphs (was printing 'None' literal when keys missing) | /btc-updown hero | |
| BUG-BTC-41 | btc-updown-r4 | Track-record Side col data-sort attribute - Up/Down groups correctly | /btc-updown track | |
| BUG-BTC-42 | btc-updown-r4 | Track-record Status col data-sort uses rank (won=2/open=1/lost=0/refund=-1) - sort by outcome rank, not alphabetical 'L→O→R→W' | /btc-updown track | |
| BUG-BTC-43 | btc-updown-r4 | BTCUP_JS auto-reload: column-sort state now persists across 10s refresh via sessionStorage - was being wiped every reload, making sustained sort impossible | /btc-updown JS | ★ |
| BUG-LB-MATH-1 | fleet-math | Bankroll != starting + Realized + Unrealized: paper_trading.buy() debits cash by (cost_basis+fee) but cost_basis stored is price-only, so the buy fee leaks from cash but never appears in realized_pnl_usd. Fix: derive Realized from the bookkeeping identity (honest_total - starting - unrealized) so the visible math is self-consistent on EVERY row. Tooltip surfaces the trade-ideal vs true realized diff per bot. | /copy-bots + /claude-bots leaderboards + hero | ★ |
| BUG-LB-MATH-2 | fleet-math | Hero card was still summing naive realized: live-MTM refresh in _claude_hero_comparison only overwrote total_value+unrealized on the user side (and didn't run at all on the Claude fallback path), so hero Realized/ROI used stale copy_bot_state/claude_bot_state cached values. Now both paths overwrite realized_pnl_usd with the bookkeeping-honest value - head-to-head math reconciles to the cent. | /claude-bots hero card | ★ |
| BUG-LB-MATH-3 | fleet-math | Per-agent detail page (/copy-bots/<id> + /claude-bots/<id>): Realized PnL KPI cell was reading naive totals.total_realized_pnl from paper_positions sum. Switched to bookkeeping-derived Realized = total_value - starting - unrealized so the header KPIs add up exactly. Fee-drag tooltip on the Realized cell shows trade-ideal vs true realized diff. | /copy-bots/<id> + /claude-bots/<id> detail headers | ★ |
| BUG-THEME-1 | fleet-theme | /claude-bots/<id> detail page was missing the entire AGENT_DETAIL_CSS block AND the <div class='agent-detail'> wrapper that /copy-bots/<id> used - so all 22 Claude detail pages rendered with default browser styling (no fact-cell grid, no peer-card, no hour×day heatmap layout, no uniform vertical rhythm). Hoisted the CSS to a module-level constant; both renderers now reference it. All 42 bot detail pages now share an identical theme. | /copy-bots/<id> + /claude-bots/<id> | |
| BUG-STATUS-1 | status-falsepos | polybot-claude-mart was being flagged DEAD whenever no MART variant had placed a bet in 30 minutes (the daemon only writes martingale_state on bet placement, not every cycle, so quiet BTC signals = false-positive offline). Now ORs three liveness signals: martingale_state.updated_at, MAX(MART_* paper_positions.opened_at), and the parent BTC daemon's freshness (mart is a passive consumer - if BTC is alive, mart is too). | /status daemons | |
| BUG-STATUS-2 | status-falsepos | Mothballed bots (starting_balance=0, e.g. CLAUDE_PRE_RESOLVE post-OPT-58) and stubbed bots (waiting for env var) were being counted as OFFLINE in the signal-flow check, inflating the offline rollup with false positives. Now they're surfaced as DEGRADED with a clear 'MOTHBALLED' / 'STUBBED' reason so the operator can distinguish intentional idle from real outages. | /status signals | |
| OPT-225 | btc-arena | BTC strategy arena reset to 6 strategies: MART_PURE ($10k, $5→$5120 11-step doubling, busts → reset) + 5 fighters on $5k each (ANTI_MART paroli, STREAK_FADE/RIDE, HOLDER_PRO/CON). Three natural opposing pairs. Outcome history extended 1k→10k. Daemon now supports per-strategy should_bet/side/size hooks. Self-improvement framework: rolling stats logged for manual nudges (no auto-changes yet). | /btc-updown + martingale_strats + claude_btc_martingale_bot | |
| BUG-DET-41 | detail-leaderboard-r5 | _agent_peer_rank used `SUM(pp.shares * pp.avg_entry_price - pp.cost_basis)` for the unrealized contribution - but cost_basis IS shares×avg_entry by definition (paper_trading.buy() stores it that way), so the term was ALWAYS 0. Peer ranking silently ignored every bot's unrealized PnL, biasing the order. Now reads via _live_mtm_for_bots (same source as leaderboard + hero). | /copy-bots/<id> + /claude-bots/<id> peer rank | ★ |
| BUG-DET-42 | detail-leaderboard-r5 | _agent_exit_rules was running 7 separate COUNT/SUM queries (one per status), now collapsed to single GROUP BY - ~10× faster on the index. Initializes zeros for missing statuses so the renderer iterates the full set without KeyError. | /copy-bots/<id> + /claude-bots/<id> exit-rule card | |
| BUG-DET-43 | detail-leaderboard-r5 | _enrich_btc returned a position dict missing the `price_stale` field that _enrich (copy side) populates - renderer worked via .get default but the shape inconsistency was a latent bug for any future code that does `is False` check. | /copy-bots/BTC_5M_BOT detail | |
| BUG-DET-44 | detail-leaderboard-r5 | extra_js bot_id was interpolated as `const BOT_ID = "{bot_id}";` - quote/newline in bot_id would break script parsing and silently disable calendar/breakdown loads. Now JSON-encoded via json.dumps so the value is always a valid JS string literal. Defense-in-depth on both copy + claude detail pages. | /copy-bots/<id> + /claude-bots/<id> JS | |
| BUG-CLB-41 | detail-leaderboard-r5 | _live_mtm_for_bots open-MTM loop used bare `except Exception: pass` on float parse - a single malformed cost_basis/shares row understated bankroll silently. Now logs parse failures to stderr (journald-visible) with the row id + raw values for forensics. | /copy-bots + /claude-bots leaderboards + hero | ★ |
| BUG-CLB-42 | detail-leaderboard-r5 | _live_mtm_for_bots accepted mid=0.0 as a valid live price - prediction-market mids of exactly 0.0 are typically resolved-side-empty books, not real prices, and using them as MTM would compute shares*0=0 open_value, falsely inflating unrealized losses. Now requires mid > 0 to use as live price; 0.0 falls through to HTTP fallback. | _live_mtm_for_bots | ★ |
| BUG-ARENA-1 | btc-arena-fix | Resolution loop's `WHERE a.name LIKE 'MART_%'` pattern missed the 5 new arena strategies (ANTI_MART, STREAK_FADE, STREAK_RIDE, HOLDER_PRO, HOLDER_CON) - their bets stayed `open` forever, state never advanced, strategies looked frozen. Now uses explicit IN-list from ms.ACCOUNT_NAMES so every arena bot resolves on the 60s sweep. | claude_btc_martingale_bot._resolve_mart_bets | ★ |
| BUG-ARENA-2 | btc-arena-fix | MART_PURE side selection was static (always followed base signal recommendation). Per user spec: 'Up/Down should be based on the last users who bought live, don't stick to one side'. Now dynamic per-bet: flip after 2 consecutive same-side losses, else follow holder skew (|skew|>0.2), else follow BTC delta (|Δ|>0.05%), else fall back to base signal. State tracks last_side + last_losses_same_side for the flip rule. | martingale_strats.MART_PURE.side() | |
| BUG-DEEP-1 | deep-audit-r6 | Calendar cell title attribute interpolated `d.date`/`d.n_trades` raw into HTML - defense-in-depth XSS-adjacent if API ever returns special chars. Added _esc() helper, escape every value bound into innerHTML/title across both calendar JS implementations (copy + claude). | /copy-bots/<id> + /claude-bots/<id> calendar JS | |
| BUG-DEEP-2 | deep-audit-r6 | Race condition on rapid period/category clicks - without abort, multiple in-flight fetches race and the LAST-completed (which may be the FIRST issued) wins, leaving stale data. Added AbortController per loader (loadAgCal / loadAgBreakdown) that cancels prior in-flight request before issuing new one. AbortError silently ignored. | /copy-bots/<id> + /claude-bots/<id> calendar/breakdown JS | |
| BUG-DEEP-3 | deep-audit-r6 | /claude-bots/<id> calendar+breakdown JS had NO try/catch on fetch (only copy side had it after BUG-38). Failed loads on Claude detail pages silently froze the widgets. Now both pages share the same error-handling pattern: fetch failures show an in-UI error card with the HTTP status / error message. | /claude-bots/<id> calendar/breakdown JS | ★ |
| BUG-DEEP-BTC-1 | deep-audit-r6 | btc_updown.aggregate_holders_by_side used `or` fallback chain: `h.get('totalPnl') or h.get('realizedPnl') or 0`. A holder with exactly 0.0 totalPnl (legit breakeven) silently fell through to realizedPnl - wrong value used in the holder_skew signal. Same shape on value/size/avgPrice. Now uses explicit `is not None` coalesce so 0.0 stays 0.0. | btc_updown.aggregate_holders_by_side | ★ |
| BUG-DEEP-BTC-2 | deep-audit-r6 | Holders with unknown outcomeIndex (not 0 or 1) were silently dropped - could indicate API schema drift or malformed response. Now logged to stderr with a count + returned as n_unknown so the operator can spot when upstream payload shape changes. | btc_updown.aggregate_holders_by_side | |
| BUG-DEEP-FACT-1 | deep-audit-r6 | Trader fact-sheet `biggest_win_usd`/`biggest_loss_usd` defaulted to 0.0 - for a bot with 0 wins/losses, the cell rendered '$+0.00' which falsely implies the bot's best ever trade was a breakeven. Now None when no data, renders '-' instead. Both detail pages updated. | /copy-bots/<id> + /claude-bots/<id> fact sheet | |
| BUG-DEEP-SW-1 | deep-audit-r6 | Source-wallet leaderboard WR shown for any sample size - a wallet with 1W 0L rendered '100%' which mis-ranked it as a top performer. Now applies WR_MIN_SAMPLE=5 (same threshold used elsewhere): under-sampled wallets get wr=None → renderer shows '-'. wr_n exposed for tooltip context. | /copy-bots/<id> + /claude-bots/<id> source-wallet LB | |
| BUG-DEEP-POS-1 | deep-audit-r6 | Position table price-bucket badge interpolated raw bucket string into HTML - defense-in-depth XSS vector if copy_bot_signals.price_bucket ever takes user-controlled input. Now html.escape(bucket) before render. Visual unchanged. | /copy-bots/<id> + /claude-bots/<id> position table |