← Blog
October 30, 2026 · 7 min

x402 2.29.0: the release that closed a month of fixes

The last two roundups ended on the same finding: the fixes were in main and in no published artifact. On 28 October that stopped being true. This week is about what shipped, what it breaks, and what a gateway that pins versions should do.

We searched merged PRs in x402-foundation/x402 from 23 October 00:00 UTC to 30 October 12:00 UTC. The count is far above the previous window. Of those PRs, 37 are from PhilBot402, 10 from phdargen, 9 from mintlify[bot] (docs), and 5 from lgalabru. Several are release PRs, so the raw number overstates code changes. The number that matters is different: one release.

The release, with dates

The npm registry dates @x402/core 2.29.0 at 2026-10-28 18:29:54 UTC. The previous version, 2.28.0, is dated 29 September. That is 29 days with no release while dozens of fixes merged.

PyPI shows x402 2.26.0 uploaded at 18:29 UTC the same day. Python numbering is independent of npm, so 2.26.0 on PyPI is the counterpart of 2.29.0 on npm. The Go module has a go/v2.29.0 tag, and its changelog is dated 28 October.

We did not trust the changelog alone. We unpacked both tarballs. In the 2.28.0 bundle of @x402/core, the HTTP server has no HEAD fallback and no AfterSettleAbortError. In 2.29.0, the HEAD branch is there (if (!matchingRoute && upperMethod === "HEAD")) and the new error class is exported. "Merged" is now "shipped" for these items.

What reached production

Items we covered earlier as unreleased, now in the artifacts:

New in this window and also in 2.29.0:

// AfterSettleAbortError

A hook can fail a settle closed

An afterSettle hook may now throw AfterSettleAbortError. The settle is reported as success: false while keeping transaction, amount and payer. EVM batch-settlement deposit persistence failures use it, so a store failure no longer looks like a successful settle. Other afterSettle errors stay non-fatal in TypeScript. The Python changelog states the same rule and notes that Go logs them instead.

This is the same fail-closed direction we described in the recovery audit: after money moves, the receipt must not lie about the ledger.

Smaller items worth knowing. Facilitator and Bazaar clients now cap buffered response bodies at 1 MiB for verify, settle and supported, and 4 MiB for discovery pages. A larger body throws ResponseBodyTooLargeError and cancels the stream. The JSON 402 body now carries the rejection reason for non-browser clients, and the Express settlement catch-all returns { "error": "settlement_failed" } instead of {}. afterVerify hooks now run when verify returns isValid: false. Mixed-namespace facilitator registration keeps one wildcard per CAIP-2 namespace.

What breaks on upgrade

This is a minor version, and it contains breaking changes. Read these before bumping.

Python also bounds the x402[mcp] extra to mcp<2, because mcp 2.x removed mcp.server.fastmcp.

Solana: version negotiation before transaction v1

The SVM mechanisms now negotiate the transaction message version. Facilitators advertise extra.transactionVersions in /supported, and the advertised set is [0]. Servers copy it into the 402. Clients build an advertised version, or refuse with unsupported_transaction_version. The change anticipates transaction v1 (SIMD-0385). The SVM upto facilitator can also choose the version of the claim, cancel and rent-cleanup transactions it builds itself. In Go, unset keeps v1.

The practical point: a client that builds Solana payments by hand now has a field to read before it signs.

What it means for LLM4Agents

Our gateway quotes and settles per call, so it sits on the seller side of these code paths. Three consequences.

First, the exposure window is closing, not closed. Everything we described as "in main, not shipped" for four weeks is now in a pinned-able version. Any deployment still on 2.28.0 keeps the HEAD bypass, the override ceiling gap and the paywall XSS. Merged code protected nobody until 28 October.

Second, the upgrade is not a patch bump. The zod 4 change touches every place that reads parse results. A gateway that logs ZodError.errors will log nothing useful after the bump. The Algorand ID change is a hard reject, so a stale config fails at runtime, not at install.

Third, AfterSettleAbortError gives us a clean place to put a ledger write. If our post-settle step cannot record the debit, the buyer should see a failed receipt, not a successful one we cannot account for. We have not changed our code yet. This is the design we would adopt, not something we run today.

Staying on the frontier

  1. Upgrade in a branch, not in place. Bump @x402/core to 2.29.0 behind a pin, then grep for ZodError, .errors and any Algorand network string.
  2. Re-run the bypass probes. HEAD, OPTIONS and %2F paths against a mock facilitator. A fix is real when the probe flips from 200 to 402 on the published bundle.
  3. Adopt the fail-closed hook. Wire our debit record into an afterSettle hook that throws AfterSettleAbortError on store failure.
  4. Read transactionVersions in our Solana path and refuse anything outside the advertised set.
  5. Watch the cadence. A 29-day gap is a risk. Add an alert for "merged security fix newer than the pinned release" so we know when we are exposed.

One caveat on method: we verified the tarballs and changelogs, not the behavior of every item. HEAD is the one we reproduced ourselves, against 2.28.0. The rest rests on the maintainers' changelog and PR text.

Pay per call, with the SDK pinned for you

LLM4Agents tracks x402 releases so your agent does not have to.

Register agent