Skip to content

fix(pretty): coerce string offset and limit in readCall line range - #2

Merged
hypnguyen1209 merged 1 commit into
pifydev:mainfrom
iamxeph:fix/read-line-range-coercion
Oct 14, 2026
Merged

hypnguyen1209 merged 1 commit into
pifydev:mainfrom
iamxeph:fix/read-line-range-coercion

Conversation

@iamxeph

@iamxeph iamxeph commented Sep 29, 2026

Copy link
Copy Markdown
Contributor

Problem

When an LLM provides tool call arguments with string-typed numbers (e.g. offset: "1", limit: "2000", as observed with models like glm-5.3-flash), the read tool summary displays bizarre, inflated line ranges such as:

  • lines 1–11999 (for offset: "1", limit: "2000")
  • lines 1046–1046399 (for offset: "1046", limit: "400")

Root Cause

In src/summary.ts, readCall calculates the end of the line range as follows:

const range =
  args.offset || args.limit
    ? theme.fg("dim", ` · lines ${args.offset ?? 1}${args.limit ? `–${(args.offset ?? 1) + args.limit - 1}` : "+"}`)
    : "";

While args is typed as { offset?: number; limit?: number }, TypeScript types are erased at runtime. Tool call arguments deserialized from LLM responses may carry string values.
In JavaScript:

  1. (args.offset ?? 1) evaluates to "1" (string).
  2. ("1") + args.limit evaluates to "1" + "2000" = "12000" (string concatenation due to + associativity).
  3. "12000" - 1 coerces to a number via subtraction, resulting in 11999.

Solution

Explicitly coerce args.offset and args.limit to numbers using Number() before calculating the range:

const offset = args.offset ? Number(args.offset) : undefined;
const limit = args.limit ? Number(args.limit) : undefined;
const range =
  offset || limit
    ? theme.fg("dim", ` · lines ${offset ?? 1}${limit ? `–${(offset ?? 1) + limit - 1}` : "+"}`)
    : "";

This prevents string concatenation while preserving the exact original logic and public type definition.

Verification

  • Added regression tests in test/pretty.test.ts verifying string-typed arguments for offset and limit.
  • Ran tsc --noEmit: clean (0 errors).
  • Ran node --test test/pretty.test.ts: all 32 tests passed (0 failed).

@iamxeph
iamxeph force-pushed the fix/read-line-range-coercion branch from 6d5f241 to a07d28b Compare October 1, 2026 13:40
@iamxeph
iamxeph force-pushed the fix/read-line-range-coercion branch from a07d28b to f78e267 Compare October 14, 2026 06:44
@iamxeph

iamxeph commented Oct 14, 2026

Copy link
Copy Markdown
Contributor Author

@hypnguyen1209 could you take a look at the two small fix PRs I have open here when you get a chance? Both are one-liner changes with tests attached — details are in each description.

@hypnguyen1209 hypnguyen1209 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Good catch and a correct diagnosis: "1" + "2000" concatenates before the - 1 coerces, hence lines 1–11999. The coercion is in the right place (the summary is the only consumer, the public type stays honest), and the regression tests pin the exact shapes you saw from glm-5.3-flash. Typecheck and the full suite (132 tests) pass on your branch locally, and I have approved the CI run.

One non-blocking nit, since the point of this change is that the arguments cannot be trusted: a non-numeric string such as offset: "abc" now renders as lines NaN–NaN. Guarding with Number.isFinite (inline suggestion) keeps a bad value out of the range the same way a missing one is, without changing anything for the cases you fixed. Happy to merge either way.

Comment thread src/summary.ts Outdated
Comment on lines +55 to +56
const offset = args.offset ? Number(args.offset) : undefined;
const limit = args.limit ? Number(args.limit) : undefined;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
const offset = args.offset ? Number(args.offset) : undefined;
const limit = args.limit ? Number(args.limit) : undefined;
const offset = Number.isFinite(Number(args.offset)) && args.offset ? Number(args.offset) : undefined;
const limit = Number.isFinite(Number(args.limit)) && args.limit ? Number(args.limit) : undefined;

When LLMs pass tool arguments with string-typed numbers (e.g. offset: "1",
limit: "2000"), JavaScript evaluates `("1" + "2000") - 1` as `11999` instead of
`2000`, causing bizarre line ranges like "lines 1–11999".

Explicitly parse numeric arguments with Number() before calculating the range,
and treat a value that is not a finite number (a non-numeric string, an object)
as absent, the same as an omitted one.
@iamxeph
iamxeph force-pushed the fix/read-line-range-coercion branch from f78e267 to 2a87183 Compare October 14, 2026 07:15
@iamxeph

iamxeph commented Oct 14, 2026

Copy link
Copy Markdown
Contributor Author

Guarded with Number.isFinite and added a test for a non-numeric value. 132 tests + typecheck pass. The workflow run on the new SHA needs your approval.

@hypnguyen1209
hypnguyen1209 merged commit a4e5fc7 into pifydev:main Oct 14, 2026
2 checks passed
hypnguyen1209 added a commit that referenced this pull request Oct 14, 2026
- Edit results render from details.patch (pi's unified patch with hunk
  headers) instead of details.diff, so side-by-side split, word-level
  emphasis and the old/new gutter finally work for edit; the patch's
  file headers and trailing newline are stripped so the result matches
  the pre-apply preview (#1 by @iamxeph).
- The read summary coerces string-typed offset/limit before computing
  the line range, so "1" + "2000" no longer reads as lines 1-11999, and
  a non-numeric value is left out of the range (#2 by @iamxeph).
@iamxeph
iamxeph deleted the fix/read-line-range-coercion branch October 14, 2026 07:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants