cd /news/developer-tools/my-benchmark-s-python-column-was-n-a… · home topics developer-tools article
[ARTICLE · art-61028] src=dev.to ↗ pub= topic=developer-tools verified=true sentiment=· neutral

My benchmark's Python column was N/A for a year — CPython's 4300-digit limit, and eight other bugs

A developer discovered that their multi-language A2A benchmark suite had a Python column showing N/A for a year due to CPython's 4300-digit integer-to-string conversion limit. The bug caused the Python agent to crash when computing Mersenne primes beyond the 24th exponent, as the stringified prime exceeded the limit. Additional issues included reliance on LLM prose for timing data, incorrect count reporting, and parser failures for sub-microsecond durations.

read4 min views1 publishedJul 15, 2026

Submission for DEV's Summer Bug Smash — Clear the Lineup track.

a2a-benchmark is my multi-language A2A (Agent-to-Agent) performance suite: four agents — Python and Go behind Gemini tool-calling via ADK, Node.js and Rust as direct handlers — each compute Mersenne primes with the Lucas–Lehmer test, while a harness sweeps N=1–24 and charts calculation time and round-trip time.

The committed results stopped at N=22. I never questioned that. I should have.

CPython 3.11+ limits int→str conversion to 4,300 digits by default (a DoS mitigation). My Python agent stringified each prime it found:

mersenne_primes.append(str((1 << p) - 1))

The 24th Mersenne exponent is p=19937, and 2^19937−1 has 6,002 digits. So for any request of 24+ primes, the tool raised ValueError

— and the A2A response dutifully delivered the stack-trace text instead of a result:

"text": "Exceeds the limit (4300 digits) for integer string conversion;
         use sys.set_int_max_str_digits() to increase the limit"

The benchmark's Python column was structurally incapable of producing data at N≥24. The kicker: the stringified list was never used. The tool returns only elapsed_time

. The fix is deleting the str()

— which also removes formatting work from the timed region that the Node and Rust agents never paid (Go had the same dead val.String()

call).

Fix: PR #1 — plus a switch from

time.time()

(wall clock, non-monotonic) to time.perf_counter()

, and a regression test at count=24.Before/after, N=24 row:

Node.js Rust Go Python
before 1633.01 ms 812.57 ms 1451.49 ms N/A (crash)
after 1616.13 ms 824.10 ms 1531.10 ms 2425.9 ms

The Python agent's timing came back in two places: a structured elapsed_time

in the tool artifact, and the model's prose. The harness regexed the prose first:

m = re.search(r"It took ([\d\.\-e]+) seconds", text)

In live runs, Gemini said "Calculating the first 5 Mersenne primes took 4.9591064453125e-05 seconds." one time and "The calculation took 2.40715261301375 seconds." the next. Neither matches "It took"

. Every datapoint survived only because a fallback happened to dig out the structured value. Measurement data should never depend on how an LLM felt like phrasing a sentence.

Meanwhile Node and Rust reported elapsed.toFixed(2)

ms — so sub-10µs runs returned 0.00ms

, which parses to 0.0

and silently vanishes from a log-scale chart.

Fix: PR #2 — structured artifact first, prose as last resort; 4-decimal timing output.

The exponent table has 26 entries, but both direct agents echoed the requested count:

$ curl ... "Calculate the first 100 Mersenne primes"
node: "Found first 100 Mersenne primes in 4450.78ms."   # computed 26
rust: "Found first 100 Mersenne primes in 2160.45ms."   # computed 26

Fix: PR #3 — report

primes.length

/ primes.len()

.Python and Go requests route through Gemini tool-calling; Node and Rust regex the number out of the message and compute directly. The RTT chart presented all four as one comparison "including LLM/Tool calling" — true for exactly half the lines. Median RTT: Rust 2.6ms, Node 4.6ms vs Go ~1.6s, Python ~1.8s. That ~400× gap is pipeline architecture, not language performance.

Fix: PR #4 — agents tagged by pipeline; the chart renders

The fix made the code too fast for its own benchmark. With formatting removed from Go's timed region, small-N runs dropped under a microsecond — and Go's time.Duration

started printing 836ns

, a suffix the harness parser had never seen. The datapoint silently became N/A. One more parser branch fixed it.

Gemini refused to repeat itself. My harness reused deterministic contextId

s, and ADK keeps per-context session history. On a rerun, the model answered "I already did that. Do you want to do it again?" — without calling the tool. Three datapoints gone. Benchmark IDs are now unique per run.

96/96 datapoints, up from a baseline where one language column crashed, small-N values were unplottable zeros, and two datapoints per rerun depended on an LLM's patience. Nine bugs, four PRs, one afternoon of reproduction scripts — all archived with before/after charts.

The suite runs on ADK + gemini-2.5-flash

tool-calling end to end. Two hard-won lessons for anyone building Gemini agents: read structured tool artifacts, never model prose, when a machine consumes the output — and never reuse context IDs for independent requests unless you want session memory changing your results.

Disclosure: I used Claude Code as the debugging/automation agent for this work — it reproduced each bug, wrote the fixes and regression tests, and ran the before/after benchmark sweeps. Every bug, number, and PR above is real and verifiable in the repo.

── more in #developer-tools 4 stories · sorted by recency
── more on @cpython 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/my-benchmark-s-pytho…] indexed:0 read:4min 2026-07-15 ·