0.49.0 — the error says whether money moved, and thirty-seven audit findings (fixes #132) - #143
Merged
Merged
Conversation
…ansient outage @blockrun/llm 3.15.1 carries the gateway's own message — the field that names the cause and says '(payment NOT charged)' — under `detail`. extractErrorMessage only read message/hint, so the text was dropped a second time here and the 'not charged' branch never fired (#132). A 501 is the gateway declining to serve a route, not a blip; the advice to retry in a few minutes was wrong. The 'nothing was charged' claim is made only when the 501 arrived before payment. hasLabelledServerStatus is exported so route-specific formatters share one status rule. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RNqnahSKcBqQemPn5TaMLg
…charged All four Predexon sports/* routes have returned an upstream 500 since 2026-08-04. The gateway marks them degraded, withdrew them from discovery and releases the payment nonce on upstream 5xx; this server still advertised them as live and rendered the failure as 'after payment … try again' (#132). The description now says the routes are degraded and points at markets + league=. A sports 5xx renders the outage, the date and 'nothing was charged'. No pre-payment block: the gateway decides whether Predexon has recovered. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RNqnahSKcBqQemPn5TaMLg
Since 2026-09-05 the gateway returns a pre-payment 501 for every
stocks/{market}/price|history call — 'We do not currently serve equity
prices'. On the default Solana chain the Base-only guard fired first and told
the user to switch chains to pay for a route that cannot succeed (#132).
Paid stock calls now return the gateway's own answer up front: withdrawn on
2026-09-05, nothing charged, the ticker catalog is still free, contact for
coverage. The description stops promising equity quotes. The paid-tool
confirmation table drops the stock row with a note on how to re-add it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RNqnahSKcBqQemPn5TaMLg
…-cost table README, four skills and the plugin manifest still sold paid stock quotes and the sports/* Predexon routes. Both now read as what the gateway serves. The context-cost profile table is updated to the measured totals after the description edits (12,992 full / 5,606 trading). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RNqnahSKcBqQemPn5TaMLg
Bump @blockrun/llm to ^3.15.1 (the SDK half of #132) and package version to 0.48.1. Also ships the three fixes that landed on main after 0.48.0: image account-rail settled cost (#140), OpenClaw verification and DeepSeek repricing (#131), brand numbers (#141). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RNqnahSKcBqQemPn5TaMLg
README troubleshooting and the blockrun-debug skill now carry the two issue-132 symptoms as symptom → cause → fix: the equity 501 from blockrun_price (withdrawn 2026-09-05, nothing charged, catalog still free) and the sports/* upstream outage behind blockrun_markets (degraded since 2026-08-04, payment released; older builds rendered it as "API error after payment: 502"). A third row covers the generic 501 "not served" wording and when it can and cannot claim nothing was charged. The debug skill's description and triggers pick up the new error strings. The cost-reporting table drops "paid blockrun_price" — no paid price path remains — and states blockrun_image's account-rail cost as settled (#140), with the estimate only as the fallback when no figure is returned. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RNqnahSKcBqQemPn5TaMLg
…gning verify:prices found the Solana gateway — a separate deployment that can lag Base — does not know azure/sora-2 and quotes 'Seedance 2.0 Pro video generation (5s)' at $1.135480 in its place, 2.7x the published Sora rate for a different model. The only check on the 402 price was the budget cap, which lets that through on any wallet holding $2. assertQuoteNearEstimate (budget.ts) refuses, unsigned, a quote more than 1.5x and $0.02 above the estimate the model was shown. Wired into blockrun_video on both rails and blockrun_image on Solana; the Solana helper now passes the decoded 402 to onQuote so callers can name WHAT was quoted. Messages end with 'no charge was made' and say how to proceed (for Sora: switch to Base). Two Solana tests that used a 4x quote to exercise the re-reserve now use a 1.27x quote — the 4x case is the thing being refused. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RNqnahSKcBqQemPn5TaMLg
…, not an under-reserve The 402 says what is being sold (Base under resource.description, Solana under accepts[0].extra.description). When the two gateways name different products for the same request, reserving the substitute's price would legitimise the substitution; since the tools now refuse such a quote before signing, it is reported loudly as a gateway bug and does not fail the release gate. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RNqnahSKcBqQemPn5TaMLg
CHANGELOG paragraph for the refused-quote behaviour; a symptom → cause → fix row in the debug skill; README profile totals resynced (13,023 full / 5,558 media). The full profile crossed 13,000 tokens, which rounds the badge's share of a 200K window from 6% to 7%, so the card is regenerated. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RNqnahSKcBqQemPn5TaMLg
…s also materially dearer The Solana gateway writes longer descriptions for the same route (rpc/ethereum: 40 words against Base's 5, same $0.002), which the first cut read as a substituted product. Substitution now requires the label to differ AND the Solana price to sit >25% above Base; the per-row tag says whether the tool is guarded (video/image) or merely reserves the Base figure. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RNqnahSKcBqQemPn5TaMLg
…nt over a key we could not read
ensureBothWallets called the SDK's file-only getOrCreateSolanaWallet(). Under
BLOCKRUN_KEYCHAIN=strict the .solana-session file is retired once the key is in
the keychain, so the default blockrun_wallet status call saw an empty slate and
minted keypair B; the next resolveSolanaKey() mirrored B over the funded key A
with -U and deleted the file. A was then nowhere (audit 2026-09-08, P0).
ensureSolanaWallet() is the twin of ensureEvmWallet(): env > file > keychain,
via keychainRead so a FAILED read refuses to mint instead of orphaning the key.
resolveSolanaKey() no longer memoises a miss, so a wallet provisioned later in
the process (or by another process) is visible without a restart.
Fresh install: Solana has been the default since 0.46.0 but nothing on that path
minted a wallet — getWalletInfo() went through the SDK constructor, which threw
'Private key required' for status/setup/qr/deposit. getWalletInfo() now
provisions via ensureSolanaWallet(), and buildSolanaClient() names the remedy
instead of the symptom when there is still no key.
getChainBalance('solana', address) queries the DISPLAYED address (the SDK's
getBalance only ever read the client's own wallet), and returns null, not $0,
when the RPC is unreachable.
price.ts: the equity pre-flight now runs before the market-required check, and
the dead Base-only chain guard behind it is removed with a note on re-enabling.
The keychain module header no longer claims protection against same-user code;
it protects the key at rest. A read-before-write guard in persistKey was tried
and dropped: replacing the key file IS the documented rotation path.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RNqnahSKcBqQemPn5TaMLg
…uard, prove the price pre-flight at the handler image-cost.test.ts mocked the paid client but blockrun_image picks its rail from the account key before asking for that client, so on a machine set up for account mode the suite posted to the gateway with the real key. HOME is now a temp dir with no key before the tool loads and the shared fetch helper is a trap. The confirm-spend static guard keyed on reserveBudget and skipped any file without one; it now classifies paid files by their imports and requires both a reservation and a confirm, with blockrun_image in the behavioural table. blockrun_price gets a handler-level test: equity price/history return the 501 text before the chain guard, the budget gate and the confirm dialog; stocks list and crypto price still reach the client. Also: a BLOCKRUN_BUDGET_LIMIT that does not parse now warns on stderr that the cap is OFF; the image-edit confirm label names the realpath of every local file about to leave the machine. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RNqnahSKcBqQemPn5TaMLg
…date withdraw destinations, track a lost relayer submit
Audit 2026-09-08, seven findings on the paths that move real USDC:
- Market orders were signed at an SDK-chosen limit with no user-visible bound.
walkBook() walks the live asks/bids best-first; the preview prints the worst
fill and that price is the signed limit, so a thinning book fills less, never
worse. Sell proceeds are the walked total. A FOK the book cannot absorb is
refused pre-sign.
- withdraw: to_address is checked with viem isAddress({strict:true}) before any
I/O; the confirmation says 'your agent wallet' only when it is.
- setup: a signer/deposit-wallet rotation no longer inherits deployed:true.
- relayer: a lost submit response persists pendingWithdraw with anti-retry
guidance; a definite 4xx or an untracked batch rethrows raw.
- fund: optional POLYMARKET_MAX_FUND_USD cap; the doc no longer claims every
action is capped.
- withdraw/redeem: bridge and transport errors are reported as themselves, not
rewritten into CLOB geoblock / market-resolved advice.
- MCP Apps order card: Place is disabled once the amount differs from the quote
until Re-quote; the card shows the worst-fill row.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RNqnahSKcBqQemPn5TaMLg
…t hears about it; sports remedy, prices and manifest corrected The gateway retired every Surf endpoint on 2026-09-06 (410 endpoint_retired, no 402 is ever issued). blockrun_surf now returns the retirement and the gateway's own alternatives before estimate, reservation or confirm dialog; the tool stays registered, its description shrinks, and README plus four skills stop selling 83 endpoints at $0.0095. skills/surf is a redirect skill. 0.48.1's own sports remedy pointed at markets+league and outcomes/:id, which the gateway removed on 2026-08-04 and 404s before payment; all eight surfaces now point at markets/search, polymarket/events and kalshi/markets, which 402 today. describeDegradedSportsFailure claims 'nothing was charged' only when the message carries the gateway's own evidence, and otherwise says to check the ledger. markets/exa/defi descriptions quoted the reserve as the charge and rpc quoted the base; all four now say base plus the gateway fee ($0.001 today, $0.002 reserved). server.template.json no longer tells Solana users to put a bs58 key in BLOCKRUN_WALLET_KEY; SOLANA_WALLET_KEY and BLOCKRUN_API_KEY are listed. The plugin manifest's surf entry and skills/blockrun's 'generated prices' claim are corrected to match. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RNqnahSKcBqQemPn5TaMLg
…atalogue, and say when a settled call failed Audit 2026-09-08. CHAT_PRICE_PER_MTOKEN claimed to hold every model above the $5/$30 default; openai/gpt-6-astra and anthropic/claude-fable-5.1 ($10/$50 on both gateways) had no row, so an explicit call reserved half of what settled. Rows added, and verify:prices now sweeps GET /api/v1/models on both gateways: it fails on an unpriced model above default, a row below the live rate, a FREE_CHAT_MODELS member that costs money, or an Anthropic ledger row above live — which is how claude-sonnet-5 drops from $3/$15 to its real $2/$10. thinking.budget_tokens is folded into the reserve only for Claude ids. anthropicCallCost resolves the gateway's dashed echoes (claude-fable-5-1, claude-haiku-4-5-20251001) to their catalogue key with hasOwn, never a sibling's rate by prefix. A chat call that settled and then failed mid-stream says so on the explicit-model, multi-turn and routing paths, via a helper that runs formatError on the bare error first so the settled note cannot earn 'needs funding'. formatError appends 'nothing was charged' when the gateway said so, and the #132 regression test now pins that sentence. FREE_CHAT_MODELS replaces the nvidia/ prefix test as the free classifier. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RNqnahSKcBqQemPn5TaMLg
…ide; unknown profiles, the update nudge, dex paths, skills --help, CI on Node 20 Completeness-critic findings, each confirmed by reading before changing: - warnOnLeakedKeys flagged mcpServers.*.env.BLOCKRUN_WALLET_KEY / SOLANA_WALLET_KEY — the override README, server.template.json and the setup skill document — as a compromised key on every launch. That location now gets a short plaintext-and-synced note; the banner is reserved for a key in a header, an args entry or the old hosted-auth field. Findings carry kind: leak | env-override. The array branch never checked string elements, so a raw key in args was invisible; fixed. Cursor and Windsurf config paths the README documents are scanned, Windows paths come from %APPDATA%. - An unknown --profile / BLOCKRUN_MCP_PROFILE silently exposed all 20 tools; it is logged with the known names, and the name is trimmed and lower-cased. - The update nudge told installed users to run a claude mcp add that refuses an existing name; it now says restart (npx re-resolves @latest), or remove then re-add if pinned. - blockrun_dex spliced the caller's token into the URL path unencoded; it is validated as an address shape (EVM, base58, TON, Sui) and refused otherwise. - skills install --help exited 2; explicit help exits 0, unknown subcommand 2. - ci.yml runs typecheck, build, the suite and --version on Node 20.19 and 22; a 'test' gate job keeps the branch-protection check name stable. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RNqnahSKcBqQemPn5TaMLg
…ver called free Audit 2026-09-08, seven findings on the async media rails: - api-key-call.ts: the poll fetch is wrapped and continues inside the deadline (as solana-402.ts already did), 429/502/503/504/522/524 keep polling with Retry-After bounded by the deadline; every give-up after a successful submit throws BilledJobError carrying the settled cost, the job id and a billing status (billed / not_charged / unknown). A submit that aborts says the job MAY have been billed; one that provably never connected is rethrown raw. - video.ts / music.ts: the catch tests BilledJobError before isTimeoutError, books err.paidUsd against the cap, names the job and the dashboard, and never says 'please try again'. Base poll loops continue on a transient rejection instead of abandoning a 9-minute paid job. A poll aborted while carrying the payment header may still settle server-side: say so and book conservatively. The descriptions stop promising 'if we give up, you are not charged'. - music.ts books a completed poll before validating the payload (video's 0.39.1 fix); the inline 200 books unconditionally. - realface.ts books the settled 2xx before checking asset_id. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RNqnahSKcBqQemPn5TaMLg
… budget Every other passthrough tool concatenates onto a fixed prefix; blockrun_phone's prefix was /v1/ itself, so no traversal was needed: path:"modal/sandbox/create" with an H100 body ran at phone's $0.012 unknown reserve, clearing any budget cap and showing a confirm dialog 16,000x too small. The normalized route (decoded, lower-cased, query dropped) is classified, so encoded or cased spellings of in-namespace routes still pass and no spelling of an out-of-namespace one does. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RNqnahSKcBqQemPn5TaMLg
…indings The #132 fix grew into a full ten-angle audit with an adversarial verifier per finding; every survivor is fixed in the commits above and written up under 'From the audit' in the release notes. Minor, not patch: market orders are now signed at the previewed worst fill, blockrun_phone refuses off-namespace paths, Surf is retired, and the quote guard refuses a 402 far above the published rate — behaviour changes, not just hardening. README context-cost table and card resynced to the measured 12,883 tokens (Surf's description shrank more than the others grew): badge 12.9K, 6% of a 200K window, trading profile 58% less. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RNqnahSKcBqQemPn5TaMLg
…and artifact The gateway retired Surf on 2026-09-06; every /v1/surf/* path answers 410 endpoint_retired and no 402 is ever issued. Earlier in this release the tool was made to answer with the retirement before reserving budget. That was the interim: a tool that can only return an error still costs every agent its schema on every turn, and blockrun's published brand artifact has read mcp.tools = 19 since the delisting, which is why the org's brand-numbers refresh (#142) was red against this repo. Removed the tool, its registration, its profile memberships, its price probes and its two test files; the 'trading' profile is now 8 tools and 'research' 5. The surf SKILL stays as a redirect map from each former endpoint to the tool that serves it, and names what has no replacement yet. The path-safety tests keep the traversal exploit they were written for and run it against a namespace that still exists, with the history noted. brand-numbers.json refreshed from the live artifact (78 chat / 102 total / 6 free, tools 19). Measured context cost drops 12,883 -> 12,657 tokens (badge 12.7K, 6% of a 200K window); trading profile 5,160, now 59% less than full. BREAKING: a client config that still names blockrun_surf gets an unknown-tool error from its MCP client. Nothing can be charged either way. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RNqnahSKcBqQemPn5TaMLg
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #132. Grew into the release audit: 21 commits, 90 files. Unblocks #142 (the org brand-numbers refresh, red because this repo still shipped 20 tools).
Summary
The #132 fix (three layers). The gateway's
(payment NOT charged)was dropped by the SDK sanitizer and again by this repo's extractor.@blockrun/llm3.15.1 (blockrun-llm-ts#39) keeps it underdetail; this repo reads it. A 501 is "not served", not "temporary".sports/*failures say the routes are degraded upstream since 2026-08-04 and that nothing was charged (only when the gateway's own evidence says so). Equity price/history answers up front: withdrawn 2026-09-05, nothing charged, catalog still free.Quote guard.
sol.blockrun.aiis a separate deployment that lags Base and quotesazure/sora-2as Seedance 2.0 at $1.135 (2.7x).blockrun_video(both rails) andblockrun_image(Solana) refuse, unsigned, any 402 more than 1.5x above the estimate the model was shown.verify:pricesreports a differing product label at a materially dearer price as a gateway bug.From the audit — ten finders, one adversarial verifier per finding, 37 confirmed, all fixed:
blockrun_walletstatus minted a second Solana key over the funded one and deleted the file. Keychain-awareensureSolanaWallet, refuses to mint on a failed read, no memoised miss, fresh-install provisioning, address-based Solana balance.blockrun_phonewas a root passthrough (modal/sandbox/createat a $0.012 reserve). Namespace pinned tophone/*andvoice/*on the normalized route.mcp.tools: 19in the published brand artifact (which is why the org's brand refresh, chore(brand): refresh the snapshot — the markers were rendering a stale catalog #142, was red against this repo).tradingis 8 tools,research5; thesurfskill is a redirect map. Also from the same finding: the 0.48.1 sports remedy pointed at routes that 404 and now points at ones that 402.BilledJobErrorcarries cost and job id; both tools book it and never advise retry; Base poll loops survive a transient rejection; music and realface book before validating payloads.gpt-6-astraandclaude-fable-5.1($10/$50) had no table row (2x under-reserve). Rows added;verify:pricessweeps both live catalogues;claude-sonnet-5ledger corrected to $2/$10; free models are a set, not a vendor prefix.withdrawvalidatesto_addressand labels the destination honestly; lost relayer submits stay tracked with anti-retry guidance; signer rotation resetsdeployed; optionalPOLYMARKET_MAX_FUND_USD; bridge errors no longer rewritten as CLOB advice; the order card refuses a stale amount.image-cost.test.tsleft the account rail unmocked and could POST with a real key; rail pinned. Confirm-spend guard now classifies paid files by import and holds all of them to reserve+confirm.mcpServers.*.env.BLOCKRUN_WALLET_KEYoverride as a compromised key on every launch; now a note, with Cursor/Windsurf configs scanned. Unknown--profileis logged; update nudge no longer saysclaude mcp add;blockrun_dexvalidates the token path segment;skills install --helpexits 0; CI runs Node 20.19 and 22.Full prose in
CHANGELOG.mdunder## 0.49.0(minor, not patch: the market-order limit, phone refusal, Surf removal and quote guard are behaviour changes; a config namingblockrun_surfnow gets an unknown-tool error).Verification
npm run typecheckclean;npm run buildcleannpm test: 644 pass, 0 fail (was 462 on main)npm run verify:prices: exit 0 — 0 under-reserved on either chain; new catalogue sweep: 83 Solana / 78 Base chat models, 0 would settle above the reserve; Sora/Solana substitution reported as a gateway bugbrand-numbers.jsonrefreshed from the live artifact (78 chat / 102 total / 6 free, tools 19)Not in this PR
sol.blockrun.aiso it knowsazure/sora-2; Predexonsports/*remains degraded upstreamspeech/musicBase-rail re-reserve, model-cache invalidation on chain switch, leak-scanner paths for undocumented clients) are queued for the next audit rounddocs/mcp-schema-overhead.mdis a dated 2026-09-01 snapshot and now differs from the README tableDocumentation
README.mdtroubleshooting and tools table,skills/blockrun-debugsymptom rows,skills/surf(now a redirect skill),skills/crypto-data,skills/prediction-markets,skills/gentech-blockrun,skills/phone,skills/polymarket-trading,docs/polymarket-trading-setup.md,server.template.json,.claude-plugin/marketplace.json.🤖 Generated with Claude Code
https://claude.ai/code/session_01RNqnahSKcBqQemPn5TaMLg