You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Bug]: Repair time grows quadratically with escapes in a string #245
Every escape in a string on the repair path (\n, \", \uXXXX, …) calls _rebuild_unmatched_opening_braces, which rescans the whole string accumulated so far. Time grows with escapes × length, the same kind of slowdown as #203 but for escapes. A truncated tool call carrying a 1,000-line file takes 3.8 s on CPython 3.13.
How to reproduce
fromjson_repairimportrepair_jsonline=' value = compute(item, \\"name\\")\\n'repair_json('{"a": "'+line*1000)
Expected behavior
Time roughly linear in the input: about 50 ms here. I have a small fix and will open a PR.
Opening PRs here is limited to collaborators, so the fix is on my fork (branch fix-escape-rescan, 2 files, +21/−26). Happy to open a PR if you allow it, or feel free to cherry-pick.
Each escape rescanned the whole accumulated string to rebuild the brace/bracket state. Dropping the trailing backslash never changes that state, so only the decoded replacement needs scanning. Output is unchanged: the test suite passes, and a 200,000-input differential fuzz against main (four option sets, including logging) matched exactly. The new test in test_performance.py fails on main.
Median time per repair, before → after:
Input
CPython 3.13
PyPy 3.11, warm
Pystachy, AOT
Truncated string, 1,000 lines of code (37 KB)
3.74 s → 48 ms
517 → 69 ms
1.05 s → 3.7 ms
String with 4,000 \n escapes (12 KB)
1.18 s → 12 ms
146 → 8.7 ms
330 → 2.2 ms
Short string with 4 escapes
33 → 31 µs
7.2 → 7.3 µs
6.9 → 6.2 µs
Five interleaved processes per cell (seven for the short string), each discarding eight warm-up batches; one pinned core, Linux x86-64 (Xeon 2.3 GHz), PyPy 8.0.1.
We found this while porting json_repair's core (the in-memory parser) to Pystachy, a compiler for typed Python. The Pystachy column runs that port compiled ahead of time, with this rescan restored for "before". With the fix in both, the compiled port is up to 13× faster than CPython on these inputs (5× on the short string).
Activity
den-run-ai commentedon Nov 3, 2026
Opening PRs here is limited to collaborators, so the fix is on my fork (branch
fix-escape-rescan, 2 files, +21/−26). Happy to open a PR if you allow it, or feel free to cherry-pick.Each escape rescanned the whole accumulated string to rebuild the brace/bracket state. Dropping the trailing backslash never changes that state, so only the decoded replacement needs scanning. Output is unchanged: the test suite passes, and a 200,000-input differential fuzz against
main(four option sets, includinglogging) matched exactly. The new test intest_performance.pyfails onmain.Median time per repair, before → after:
\nescapes (12 KB)Five interleaved processes per cell (seven for the short string), each discarding eight warm-up batches; one pinned core, Linux x86-64 (Xeon 2.3 GHz), PyPy 8.0.1.
We found this while porting json_repair's core (the in-memory parser) to Pystachy, a compiler for typed Python. The Pystachy column runs that port compiled ahead of time, with this rescan restored for "before". With the fix in both, the compiled port is up to 13× faster than CPython on these inputs (5× on the short string).