{"record":{"id":"e5bd57d32aef91fc","repo":"Hmbown/CodeWhale","slug":"key-must-be-a-nonnegative-safe-integer","errorCode":null,"errorMessage":"${key} must be a nonnegative safe integer","messagePattern":"(.+?) must be a nonnegative safe integer","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"npm/runtime-sdk/index.js","lineNumber":132,"sourceCode":"      return;\n    }\n    if (!response.body) {\n      throw new RuntimeApiError(\"Runtime API event response did not include a readable body\", {\n        method: \"GET\",\n        path,\n      });\n    }\n    for await (const event of parseEventStream(response.body)) {\n      yield event;\n    }\n  }\n\n  /** Read the existing durable thread journal. This never starts a turn. */\n  async *threadEvents(threadId, options = {}) {\n    const query = new URLSearchParams();\n    for (const [key, value] of [[\"since_seq\", options.sinceSeq], [\"replay_limit\", options.replayLimit]]) {\n      if (value === undefined) continue;\n      if (!Number.isSafeInteger(value) || value < 0) throw new TypeError(`${key} must be a nonnegative safe integer`);\n      query.set(key, String(value));\n    }\n    if (options.includeProgress !== undefined && typeof options.includeProgress !== \"boolean\")\n      throw new TypeError(\"includeProgress must be a boolean\");\n    if (options.includeProgress) query.set(\"progress\", \"true\");\n    const path = `/v1/threads/${segment(threadId)}/events?${query}`;\n    const response = await this.#rawRequest(path, {\n      method: \"GET\", capability: \"thread_event_stream\", accept: \"text/event-stream\",\n      signal: options.signal, redirect: \"error\",\n    });\n    if (!response.body || !/^text\\/event-stream(?:;|$)/i.test(response.headers.get(\"content-type\") ?? \"\")) {\n      await response.body?.cancel();\n      throw new RuntimeApiError(\"Runtime thread response is not an event stream\", { method: \"GET\", path });\n    }\n    if (options.includeProgress && response.headers.get(\"x-codewhale-event-progress\") !== \"1\") {\n      await response.body.cancel();\n      throw new RuntimeCapabilityError(\"thread_event_progress\", \"Runtime does not support thread replay progress\", { method: \"GET\", path, status: 501 });\n    }","sourceCodeStart":114,"sourceCodeEnd":150,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/npm/runtime-sdk/index.js#L114-L150","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Ensure the value is a nonnegative integer: use Number.isSafeInteger(v) && v >= 0 before calling.","Convert numeric strings with Number()/parseInt() first, and confirm the result is a safe integer.","Omit the option entirely (leave undefined) when you do not want replay filtering or a replay cap."],"exampleFix":"// before\nawait client.threadEvents(id, { sinceSeq: String(lastSeq) });\n// after\nconst since = Number(lastSeq);\nif (Number.isSafeInteger(since) && since >= 0) {\n  await client.threadEvents(id, { sinceSeq: since });\n}","handlingStrategy":"validation","validationCode":"function validReplayOpt(v) { return v === undefined || (Number.isSafeInteger(v) && v >= 0); }\nif (!validReplayOpt(opts.sinceSeq) || !validReplayOpt(opts.replayLimit)) throw new TypeError(\"sinceSeq/replayLimit must be nonnegative safe integers\");","typeGuard":"const isNonNegSafeInt = (v) => typeof v === \"number\" && Number.isSafeInteger(v) && v >= 0;","tryCatchPattern":"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; }","preventionTips":["Normalize any externally sourced seq value with Number() and re-validate before use.","Never pass strings or BigInts into threadEvents options.","Keep a single helper that builds validated replay options for the whole app."],"tags":["typeerror","validation","arguments"],"backgroundTag":"invalid-argument-value","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}