Compound Labs
Get the newsletter
THE STANDUPAnthropic fixes Claude 5.1 error spikesERROR SPIKES, FIX DEPLOYEDCOMPOUND MASTODONSkillWorks records inactive files beside their roster status22 INACTIVE FILES STILL LOADCTXWINDOWContext Window tracks AI lab changes and filters their evidenceFILTERS KEEP EVIDENCE TOGETHERAGENTWIREmicrosoft/agent-governance-toolkit - AI Agent Governance ToolkitPOLICY ENFORCEMENT FOR AI AGENTSCOMPOUND FACEBOOKCompound Portfolio restores its sweep after a missing module stops desk dataSWEEP RESTORED, DESK DATA WRITTENBLOCKDEXBlockDex shows no destination for captured rows with rejected slash names31 ROWS, NO DESTINATIONCONTEXT WINDOWOpenAI adds 1024p image pricing: $0.50 and $0.251024P IMAGE, $0.50 AND $0.25COMPOUND BLUESKYCompound Labs renamed job labels so job-run finds breaker stateBREAKER STATE, NOW FINDABLECTXWINDOWContext Window tracks document changes but failed mobileMOBILE WORKFLOW, ONE COLUMNAGENTWIREMineDojo Voyager: Open-Ended Embodied AgentOPEN-ENDED EMBODIED AGENTFOUNDER BLUESKYdeploy.sh now fails false Vercel production landingsFALSE LANDINGS NOW FAILCOMPOUNDlast known fixes redirect tracking after migrationSTUDIO SOURCES NOW SAY COMPOUNDCONTEXT WINDOWOpenAI adds sora-2-pro pricing720P VIDEO, $0.15, $0.30FOUNDER LINKEDINdeploy.sh now stops failed promotes from marking production landedFAILED PROMOTES NO LONGER LANDUSINGITUPUsing It Up records a receipt fixing a wobbling wooden tableONE RECEIPT, TABLE STAYED LEVELCOMPOUNDpnpm v12.4.2POSIX BIN SHIMS REPLACEDFOUNDER PEERLISTCompound Labs' deploy runner rejects failed promotesFAILED PROMOTES NO LONGER LANDTRUSTDESKTrustDesk fixes host migration redirects while keeping /api/ outOLD HOST, 308; API STAYS LIVE
Independent product R&D labFounded and run by Isaiah Kim, @kyisaiah47Newest commit Sep 16, 2026, AgentwireNewest writing Sep 16, 2026Site changelog Sep 16, 2026
Aug 22, 20264 min read

One failed charge, three documents disagreeing about whether you may retry it

A subscription charge fails on a Tuesday. Sometimes that is a balance that will clear on payday. Sometimes it is a card the cardholder reported stolen on Monday. Charge the first one again and you probably get paid. Charge the second one again and you are not being annoying, you are breaking a card network rule, and the party that pays for it is the merchant whose payment account gets flagged.

So the first thing a recovery agent has to do is not retry. It has to work out which failures it is permitted to touch, and the answer to that lives in three separate documents that do not share a vocabulary.

Three sources, three ways of saying no

Stripe publishes a list of nine decline codes it will not automatically retry, on its Smart Retries page: incorrect_number, lost_card, pickup_card, stolen_card, revocation_of_authorization, revocation_of_all_authorizations, authentication_required, highest_risk_level, transaction_not_allowed. Its own words for that category are that the issuing bank has rejected the transaction and you can't retry it.

Visa says it differently and with more force. Its rules update AI10325, dated 3 September 2020 and effective 17 April 2021, sorts response codes into categories. Category 1 means the issuer will never approve, and the merchant is not permitted to reattempt. Category 2 means the issuer cannot approve at this time, and there you may reattempt up to 15 times in 30 days. Response code 14, invalid account number, gets its own sentence forbidding any reattempt on the same account number.

And then there is the issuer speaking directly. Stripe surfaces the bank's own guidance as an advice code, and do_not_try_again means the card must not be used again for this charge.

Any one of the three can be missing on a given failure. None of them can be ignored.

The order is the interesting part

They get checked in order of how close each one is to the bank that actually refused:

export function hardDeclineStop(signals: DeclineSignals): StopVerdict {
  const advice = norm(signals.networkAdviceCode);
  if (advice === DO_NOT_RETRY_ADVICE_CODE)
    return { stop: true, reason: "issuer_advice", authority: "Stripe · Card declines · advice codes", ... };

const code = norm(signals.declineCode); if (code && HARD_DECLINE_CODES.includes(code)) return { stop: true, reason: "hard_decline", authority: "Stripe · Hard decline codes", ... };

const network = norm(signals.networkDeclineCode).toUpperCase(); if (network && NETWORK_NEVER_APPROVE_CODES.includes(network)) return { stop: true, reason: "network_never_approve", authority: "Visa Rules · AI10325", ... };

return OPEN; } ```

The advice code wins because it is the bank itself. The decline code is Stripe reading the bank. The raw two-digit response code is what is left when neither of the first two came through. Every stop carries the sentence a human will read and the document it traces to, so a merchant looking at a stopped customer can see who said no.

Signal on the failed chargeWho is speakingVerdict
advice code do_not_try_againthe issuing bank, passed through by Stripestop, permanently
decline code stolen_cardStripe's published list of ninestop, permanently
response code 14Visa, named in its own sentencestop, permanently
response code 05, do not honorVisa Category 2, cannot approve nowretry on the schedule
decline code insufficient_fundsStripe, a soft declineretry on the schedule
a code none of the three documents listsnobodyretry on the schedule

The last row is the one people argue about

A gate built to fail closed should refuse anything it does not recognise. This one does the opposite, and there is a test pinning it: an unknown code does not stop.

The reason is that the two sets are shaped differently. The set of permanently dead cards is published, finite and maintained by Stripe and Visa. The set of temporarily failed cards is open ended, and nobody publishes it. Treating an unfamiliar code as terminal would mean every code added after the last read of those documents silently gives up on a paying customer, and the merchant never hears about it. So the closed list is the stop list, and everything else climbs the schedule.

Where failing closed does apply is the ceiling. Visa allows 15 reattempts in 30 days. The shipped default is four, spread over a week at days 1, 3, 5 and 7, with the first one silent because a Tuesday balance often clears by Wednesday and the customer never needs the email. A merchant who types 99 into the cap field gets 14, because the clamp refuses any value at or above the network's own limit.

How we built this: CardChase

---

One shipped product, taken apart, once a month. What it does, what it cost to build, what the pipeline behind it looks like, and what the numbers did, read off the repository and the live site, not written from memory. Join the list.

All writing