Skip to content

toValue: keep the exact digits of large integer IDs in text results - #88

Merged
gabe4coding merged 3 commits into
mainfrom
cursor/tovalue-exact-integers-c46c
Oct 28, 2026
Merged

gabe4coding merged 3 commits into
mainfrom
cursor/tovalue-exact-integers-c46c

Conversation

@gabe-bots

@gabe-bots gabe-bots Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

For Gabe

Why: a text tool result rounds an integer ID above 2^53, so a later call can use a different ID.
Risk: R1, review the items below. A large ID arrives as text, so a program that expects a number sees a string. Invalid JSON stays the original text.
Prior work: no earlier issue or pull request. toValue has used JSON.parse since d73f18f. Greptile round 1 (unquoted key) is fixed in 43ed86f.

Intent

A tool result that is JSON text gives a program a wrong ID. The parse rounds an integer above 2^53. Keep the exact digits of that integer. Leave every other JSON value as the parse leaves it.
Not in scope: structured content, the program's own return value, decimals and exponents.

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Tool text] --> B{"Has structured content?"}
  B -->|yes| C[Give that value]
  B -->|no| D{"Text parses as JSON?"}
  D -->|no| E[Give the text]
  D -->|yes| F{"Unquoted integer lost digits?"}
  F -->|yes| G[Give the digits as text]
  F -->|no| H[Give the parsed value]
Loading

Risk

R1 (review the items below): Tool results that are JSON text now keep inexact integers as exact digit text.

ℹ️ 4 items need you: 4 validation gaps. Open to see them.

Validation gaps

Validation gap Why the checks miss it Fix for next time Where
A 400-digit integer inside an object stays digit text A bare 400-digit value matches the failure path too Parse an object field that is a 400-digit integer hooks/protocol.ts:135
A decimal or exponent with long digits stays a number The probe never parsed a decimal or an exponent Compare those two forms with JSON.parse in the probe hooks/protocol.ts:174
A negative integer a number cannot hold keeps its minus sign The probe used only a positive id Expect the signed digits of a negative 19-digit id hooks/protocol.ts:170
Escaped quotes beside a long id stay the parsed text Only the new unit test walks the string scanner Put an escaped quote beside the id in the probe hooks/protocol.ts:156

Note

No RISK.md: the level uses the defaults. Run skill puredori:risk-init to set your rules.

Drift Decisions Reach Rollout Evidence Reading map
✅ no drift 3 ⚠️ programs get text IDs ✅ revert and raise the version ✅ 6/6 diagram paths proven 4 files, read the parser first

Drift

Asked: issue 85

✅ Checked against the intent: no drift.

Decisions

Decision Other option Chosen because Rollback
Digits stay as text BigInt JSON.stringify throws on a BigInt easy: revert
Plain parse, then the scan Scan before the parse The first parse decides validity easy: revert
Saved JSON uses the same parse Live text only A large result is that file easy: revert

Reach

What changed Who else sees it How you know
A large integer becomes text ⚠️ found: a program in a run Search: toValue in hooks/register.tsx
Version 0.12.20 ✅ checked, nothing found: an open session keeps its copy The version rule in CODING_STANDARDS.md
Learned shape of a result ⚠️ found: a later search can say string shapeOf names string and number apart

Rollout and rollback

  • Deploy: ✅ checked, nothing found: one version loads per session.
  • State: ⚠️ found: no saved ID. A learned shape can say number until the next call.
  • Stop: ✅ checked, nothing found: revert, then raise the version. No flag. No restore.

Evidence

Claim Proof Result
Has structured content? → yes: give that value node script, real toValue ✅ 1/1
Has structured content? → no: read the text same script, bare integer ✅ 1/1
Text parses as JSON? → no: give the text plain text, an unquoted key, a trailing comma ✅ 1/1
Text parses as JSON? → yes: give the value same script, integer 42 ✅ 1/1
Unquoted integer lost digits? → yes: digits stay text four repros, then JSON.stringify ✅ 1/1
Unquoted integer lost digits? → no: the number stays 42, 1.5, 1e21, and negative zero ✅ 1/1
What Before After
Bare 19-digit ID number 1234567890123456800 string "1234567890123456789"
ID in an object {"id":1234567890123456800} {"id":"1234567890123456789"}
9007199254740993 in an array [9007199254740992] ["9007199254740993"]
Quoted control "12345678901234567890" "12345678901234567890"
Unquoted key {"9007199254740993":1} on a0d78ef the original text
660 KB, no long integer 2.98 ms 2.90 ms
660 KB, one 19-digit ID 2.37 ms 10.71 ms
ℹ️ Full run output.
INVALID "{9007199254740993:1}"
  a0d78ef {"9007199254740993":1}
  now     "{9007199254740993:1}"
  saved text    {"ok":true,"value":"{9007199254740993:1}"}
  saved json    {"ok":false,"error":"the saved result is not whole"}
  saved unknown {"ok":true,"value":"{9007199254740993:1}"}
INVALID "[1234567890123456789,]"
  a0d78ef "[1234567890123456789,]"
  now     "[1234567890123456789,]"
GOOD {"id":"1234567890123456789"}
exact 9007199254740992 number
negative zero stays -0

=== timing, 5 runs after 2 warmup, same process ===
main  small bytes 660001 median_ms 2.98 runs 2.37,2.75,2.98,3.07,3.11
a0d78 small bytes 660001 median_ms 3.09 runs 2.73,2.75,3.09,3.29,3.38
now   small bytes 660001 median_ms 2.90 runs 2.61,2.66,2.90,2.97,3.04
main  big   bytes 659995 median_ms 2.37 runs 2.33,2.37,2.37,2.44,2.68
a0d78 big   bytes 659995 median_ms 7.06 runs 6.62,6.74,7.06,7.42,8.68
now   big   bytes 659995 median_ms 10.71 runs 8.90,9.66,10.71,10.97,12.86
big id 1234567890123456789

Reading map

ℹ️ 4 files, 2 to read first. Open to choose what to read.
%%{init: {'theme': 'neutral'}}%%
flowchart TD
  f1["1 · plugin.json<br/>config"]
  f2["2 · usage.mdx"]
  f3["3 · protocol.ts<br/>shared code, public API"] --> f4["4 · code-mode.test.ts<br/>tests changed"]
Loading
# File Kind of change Lines
1 plugin.json config 3
2 docs/usage.mdx
3 protocol.ts shared code, public API 107-215, 273
4 code-mode.test.ts tests changed 119-200, 293-297

Stack

Step 1/1. Previous: none. Next: none.

Closes #85

Story

A text tool result rounds an integer ID above 2^53, so the program can call the wrong row. Keep the exact digits of that integer, and leave every other JSON value as the parse leaves it.
Not in scope: structured content, the program's own return value, decimals and exponents.

# Step Reach Rollout Evidence PR
1 Keep exact digits of large integer IDs in tool text Programs that read a tool result One plugin version per session; revert and bump the version Four repro lines before and after, plus a large-JSON timing #88 (open)

cursoragent and others added 2 commits October 28, 2026 04:26
Co-authored-by: Gabe <gpavanello@thefork.com>
JSON.parse rounds an integer it cannot store. Return those digits as
text, including from a saved JSON file. Version 0.12.20.

Co-authored-by: Gabe <gpavanello@thefork.com>
@gabe-bots gabe-bots Bot added the review:r0 puredori risk R0: Gabe reviews all of it label Oct 28, 2026
@gabe4coding

Copy link
Copy Markdown
Owner

@greptile review

@greptile-apps

greptile-apps Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 4/5

[Medium impact] The change reaches programs that receive MCP tool results through code-mode.

The PR appears safe to merge, with a non-blocking edge case where non-JSON text becomes an object.

Findings

  1. P2 Non-JSON text becomes an object ▶
Fix with agent prompt
### Issue 1
hooks/protocol.ts:194
`parseToolJson` rewrites `{9007199254740993:1}` as `{"9007199254740993":1}` because the scanner quotes the unquoted numeric key. `toValue` then returns an object instead of the original text. Programs that expect to read or return that text receive a different type.

Keep invalid JSON unchanged, and add a regression test for this case.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

The PR preserves exact digits for integer tokens that JavaScript numbers would round.

  • Tool results keep exact digits for integers JavaScript cannot represent.

Acknowledgment: gabe-bots[bot] explicitly identifies the live and saved number-to-string changes as intentional public API changes.

Reviews (1) · Last reviewed commit: "Keep exact digits of large integer IDs i..." · Reviewed by Greptile

Comment thread hooks/protocol.ts Outdated
return parts.join('')
}

const parseToolJson = (text: string): unknown => JSON.parse(quoteUnsafeIntegers(text))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Non-JSON text becomes an object

parseToolJson rewrites {9007199254740993:1} as {"9007199254740993":1} because the scanner quotes the unquoted numeric key. toValue then returns an object instead of the original text. Programs that expect to read or return that text receive a different type.

Keep invalid JSON unchanged, and add a regression test for this case.

Knowledge Base Used: Tool dispatch and protocol

Prompt To Fix With AI
This is a comment left during a code review.
Path: hooks/protocol.ts
Line: 194

Comment:
**Non-JSON text becomes an object**

`parseToolJson` rewrites `{9007199254740993:1}` as `{"9007199254740993":1}` because the scanner quotes the unquoted numeric key. `toValue` then returns an object instead of the original text. Programs that expect to read or return that text receive a different type.

Keep invalid JSON unchanged, and add a regression test for this case.

**Knowledge Base Used:** [Tool dispatch and protocol](https://app.greptile.com/gabe4coding/-/custom-context/knowledge-base/gabe4coding/claude-code-mode/-/docs/tool-dispatch-and-protocol.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Invalid JSON stays invalid: JSON.parse runs first, and the digit scan runs only after that parse succeeds, so {9007199254740993:1} comes back as the original text. Fixed in 43ed86f.

JSON.parse runs first. The digit scan runs only after that parse
succeeds, so an unquoted key stays the original text.

Co-authored-by: Gabe <gpavanello@thefork.com>
@gabe-bots gabe-bots Bot added review:r1 puredori risk R1: Gabe reviews the listed items and removed review:r0 puredori risk R0: Gabe reviews all of it labels Oct 28, 2026
@gabe4coding
gabe4coding enabled auto-merge October 28, 2026 05:40
@gabe4coding
gabe4coding merged commit 805a75e into main Oct 28, 2026
17 checks passed
@gabe4coding
gabe4coding deleted the cursor/tovalue-exact-integers-c46c branch October 28, 2026 05:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review:r1 puredori risk R1: Gabe reviews the listed items risk:t2-soft

Projects

None yet

Development

Successfully merging this pull request may close these issues.

toValue changes large integer IDs in text results (1234567890123456789 becomes ...800)

2 participants