can1357/oh-my-pi · error · SyntaxError

Invalid number: ${token}

Error message

Invalid number: ${token}

What it means

Thrown by RelaxedJson.#number in strict mode when the scanned numeric token does not convert to a finite number via Number(token) — Number.isNaN(num) is true. The scanner greedily consumes numeric-ish characters (including hex digits a-f and x/X), so a token like '0xZZ' or a lone '-' yields NaN. In partial mode the parser returns the INCOMPLETE sentinel so enclosing containers roll back instead of committing junk.

Source

Thrown at packages/utils/src/json-parse.ts:481

				ch === "+" ||
				ch === "." ||
				ch === "e" ||
				ch === "E" ||
				ch === "x" ||
				ch === "X" ||
				(ch >= "a" && ch <= "f") ||
				(ch >= "A" && ch <= "F")
			) {
				this.#i++;
			} else {
				break;
			}
		}
		const token = s.slice(start, this.#i);
		const num = Number(token);
		if (Number.isNaN(num)) {
			if (this.#partial) return INCOMPLETE;
			throw new SyntaxError(`Invalid number: ${token}`);
		}
		return num;
	}

	#keyword(allowBareword: boolean): unknown {
		const s = this.#s;
		const i = this.#i;
		for (const [word, value] of KEYWORDS) {
			// Require a non-identifier boundary so `Truex` / `nullish` are not misread
			// as the keyword followed by junk.
			if (s.startsWith(word, i) && !isIdentChar(s.charCodeAt(i + word.length))) {
				this.#i += word.length;
				return value;
			}
		}
		if (this.#partial) {
			// Incomplete / unrecognized atomic token at the streaming edge — signal the
			// caller to roll back to the last valid prefix instead of committing junk.

View on GitHub (pinned to 9690622007)

Solutions

  1. Fix the numeric literal in the input to valid JSON number syntax
  2. Replace NaN/Infinity/-Infinity with null or a sentinel string — the parser intentionally rejects them
  3. Use parseStreamingJson if the token may be a partially streamed number (it rolls back instead of throwing)
  4. Validate tool arguments against a schema (finite number) before executing the call

Example fix

// before
const v = parseJsonWithRepair(`{"n": NaN}`); // SyntaxError: Invalid number: NaN
// after
const v = parseJsonWithRepair(`{"n": null}`); // {n: null}
Defensive patterns

Strategy: try-catch

Validate before calling

// Finite-number check for fields you expect to be numeric
function hasFiniteNumbers(obj: Record<string, unknown>): boolean {
  return Object.values(obj).every(v => typeof v !== "number" || Number.isFinite(v));
}

Type guard

function isFiniteNumber(v: unknown): v is number {
  return typeof v === "number" && Number.isFinite(v);
}

Try / catch

try {
  const value = parseJsonWithRepair<T>(text);
} catch (err) {
  if (err instanceof SyntaxError && err.message.startsWith("Invalid number:")) {
    // replace NaN/Infinity with null/sentinel and re-emit, or reject the call
  } else throw err;
}

Prevention

When it happens

Trigger: parseJsonWithRepair with a bad numeric token, e.g. '{"n": --}', '[0xg]', '{"n": .e}'. Note NaN/Infinity are deliberately NOT accepted as numbers (they fall into this guard) so a tool never executes with non-finite arguments.

Common situations: An LLM emitting a malformed number (double sign, hex digits mixed with junk); NaN/Infinity appearing in tool-call arguments where strict JSON forbids them; corrupted output from a broken serializer.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/ecbfb34357d6d351. Report an issue: GitHub.