Build1 publisher3 min readPublished
An undeclared lazy import broke every payment through a live x402 endpoint for weeks
PyJWT was on every development machine as someone else's transitive dependency and missing from the container, so nothing failed until a real payment arrived. Three more bugs on the same endpoint passed the same green suite.
The Engineer · Build desk

What happened
- A developer writing on dev.to documented four bugs that reached production in a pay-per-call scraping API settling USDC on Base, and all four passed a green test suite.
- Payments through the Coinbase facilitator failed for weeks with facilitator_unreachable because cdp_facilitator.py imported PyJWT inside the token-signing function and requirements.txt never declared it.
- A debit path called Transaction.update() and .set() without ever committing, so the endpoint returned ok on every request while the customer's credit balance stayed where it was.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- constraint The import test protects three names in a dict and does not read the source, so the next lazily imported package that stays off the list ships exactly as PyJWT did.
- decision Anyone hand-writing a double for a cloud SDK now has to decide whether it models the client's buffering semantics or the ones they assumed, because 678 green tests did not distinguish the two.
- exposure An address copied from an exchange deposit screen leaves who holds the key an open question, and the round trip that would settle it costs a dollar.
Python resolves `import jwt` the first time the enclosing function runs. Put that line inside `build_bearer_jwt` and the interpreter never looks for PyJWT until something signs a bearer token [4]. On the development machines the package was present as another package's transitive dependency, and in the container it was not [5]. The first code path that needed it was a real payment arriving, so startup succeeded and no health check registered anything [6]. Clients got `facilitator_unreachable` for weeks [3].
The guard that replaced it is a unit test over a map. `RUNTIME_IMPORTS` pairs `jwt` with `pyjwt`, `cryptography` with `cryptography` and `stripe` with `stripe`, and the test asserts each distribution appears in the declared dependencies [7]. It does not scan source for imports [8]. The post is direct about what that buys: "It protects the dependencies someone remembered to add to it, which is better than nothing and less than it sounds," the author wrote [9].
The debit path failed differently. In `google-cloud-firestore`, `Transaction.update()` and `.set()` append to an internal write buffer, and `commit()` sends it; `commit` is what the `@firestore.transactional` decorator calls, or what the transaction calls on exit as a context manager [20]. The debit function used neither, so the buffer was discarded when the object was garbage collected [20]. The call returned `ok` without raising, the request was served, and the customer's balance stayed where it was [21]. Refunds wrote directly instead of through a transaction, so a balance could only ever go up [22].
678 tests passed over that code because the fake Firestore client applied transaction writes immediately, with `FakeTransaction.update` calling `ref.update(data)` [23]. "The fake modelled the API I assumed rather than the one that exists," the author wrote [24]. The fake now buffers until `commit()`, a test asserts writes are invisible before it, and the regression test runs a transaction that never commits while asserting the balance stays put and the call claims success [26].
The other two failures are properties of the chain, beyond the reach of any test double. The x402 `exact` scheme on Base settles with EIP-3009 `transferWithAuthorization`: the payer signs an authorization off-chain with an ECDSA key, and the facilitator submits it and pays the gas [10]. A contract wallet has no private key, so it cannot produce that signature and can never be the payer, while receiving USDC perfectly well and looking identical in a block explorer [11]. One `eth_getCode` call against mainnet.base.org separates the two, where `0x` is an EOA that can sign and anything longer is a contract that cannot [12]. The wallet receiving revenue returned 48 bytes [13].
An earlier receiving address had been pasted from an exchange deposit screen and treated as verified, and on chain it had never received or sent anything [15]. The rule that replaced the assumption is to send a dollar in and send it back out [16]. The contract wallet pays gas through a paymaster at roughly 1.5 cents of USDC per outbound transfer [17]. Taking revenue in costs nothing; every payment out is metered, so a thousand payouts is about $15 of gas [18].
Two of the four bugs were hidden by an environment kinder than production: one machine had a package the image lacked [5], one double wrote where the SDK buffers [23]. The import assertion covers three names someone typed into a dict [8]. The buffering fake covers every call that goes through it [26]. "The suite was not weak on coverage; it was confidently wrong about the dependency," the author wrote [25].
What to watch
- Whether the RUNTIME_IMPORTS map is replaced by a scan of source imports compared against declared distributions. That is the limit the author names.
- Whether the x402 or facilitator tooling stops returning facilitator_unreachable and starts returning a distinguishable error when the payer address is a contract.
- Whether the buffering fake catches a second uncommitted transaction. That catch is the only evidence that the double now matches the SDK.