Hmbown/CodeWhale · error · TypeError

must be a nonnegative safe integer

Error message

${key} must be a nonnegative safe integer

What it means

client.threadEvents() validates the sinceSeq and replayLimit replay options before building the query string. This TypeError is thrown when either value is provided but is not a nonnegative safe integer (negative, fractional, non-number, NaN, or beyond Number.MAX_SAFE_INTEGER). The SDK fails fast client-side so malformed options never reach the runtime API.

Solutions

  1. Ensure the value is a nonnegative integer: use Number.isSafeInteger(v) && v >= 0 before calling.
  2. Convert numeric strings with Number()/parseInt() first, and confirm the result is a safe integer.
  3. Omit the option entirely (leave undefined) when you do not want replay filtering or a replay cap.

Example fix

// before
await client.threadEvents(id, { sinceSeq: String(lastSeq) });
// after
const since = Number(lastSeq);
if (Number.isSafeInteger(since) && since >= 0) {
  await client.threadEvents(id, { sinceSeq: since });
}
Defensive patterns

Strategy: validation

Validate before calling

function validReplayOpt(v) { return v === undefined || (Number.isSafeInteger(v) && v >= 0); }
if (!validReplayOpt(opts.sinceSeq) || !validReplayOpt(opts.replayLimit)) throw new TypeError("sinceSeq/replayLimit must be nonnegative safe integers");

Type guard

const isNonNegSafeInt = (v) => typeof v === "number" && Number.isSafeInteger(v) && v >= 0;

Try / catch

try { for await (const ev of client.threadEvents(id, opts)) handle(ev); } catch (err) { if (err instanceof TypeError && err.message.includes("safe integer")) fixOptsAndRetry(); else throw err; }

Prevention

When it happens

Trigger: Calling threadEvents(threadId, { sinceSeq: -1 }), { replayLimit: 1.5 }, { sinceSeq: '10' } (string), { replayLimit: NaN }, or a value > Number.MAX_SAFE_INTEGER (e.g. a seq parsed from an arbitrary JSON number without validation).

Common situations: Passing a seq value parsed from user input or JSON without Number conversion; copying a seq from a biginteger source; off-by-one math producing negative 'seek back' values; passing a BigInt or null into the options object.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15). Data as JSON: /api/errors/e5bd57d32aef91fc. Report an issue: GitHub.

Appendix: source

Thrown at npm/runtime-sdk/index.js:132

      return;
    }
    if (!response.body) {
      throw new RuntimeApiError("Runtime API event response did not include a readable body", {
        method: "GET",
        path,
      });
    }
    for await (const event of parseEventStream(response.body)) {
      yield event;
    }
  }

  /** Read the existing durable thread journal. This never starts a turn. */
  async *threadEvents(threadId, options = {}) {
    const query = new URLSearchParams();
    for (const [key, value] of [["since_seq", options.sinceSeq], ["replay_limit", options.replayLimit]]) {
      if (value === undefined) continue;
      if (!Number.isSafeInteger(value) || value < 0) throw new TypeError(`${key} must be a nonnegative safe integer`);
      query.set(key, String(value));
    }
    if (options.includeProgress !== undefined && typeof options.includeProgress !== "boolean")
      throw new TypeError("includeProgress must be a boolean");
    if (options.includeProgress) query.set("progress", "true");
    const path = `/v1/threads/${segment(threadId)}/events?${query}`;
    const response = await this.#rawRequest(path, {
      method: "GET", capability: "thread_event_stream", accept: "text/event-stream",
      signal: options.signal, redirect: "error",
    });
    if (!response.body || !/^text\/event-stream(?:;|$)/i.test(response.headers.get("content-type") ?? "")) {
      await response.body?.cancel();
      throw new RuntimeApiError("Runtime thread response is not an event stream", { method: "GET", path });
    }
    if (options.includeProgress && response.headers.get("x-codewhale-event-progress") !== "1") {
      await response.body.cancel();
      throw new RuntimeCapabilityError("thread_event_progress", "Runtime does not support thread replay progress", { method: "GET", path, status: 501 });
    }

View on GitHub (pinned to 433685b202)