Hmbown/CodeWhale · error · Error
lost precision: encode OTLP nanoseconds as a decimal string.
Error message
${field} lost precision: encode OTLP nanoseconds as a decimal string. What it means
The `ns` helper converts OTLP nanosecond timestamps to bigint. If a caller passes a JavaScript number that is not a safe integer (i.e. it already lost nanosecond precision as a double), it refuses the value and demands a decimal string (pet/src/core/ingest.ts:115).
Solutions
- Encode OTLP nanoseconds as decimal strings (as the OTLP JSON spec recommends) e.g. startTimeUnixNano:'1700000000000000000'
- If you have a safe integer number, pass it as-is; otherwise keep the string form end-to-end
- Fix the upstream serializer to quote nanosecond fields
Example fix
// before startTimeUnixNano: 1700000000000000000 // after startTimeUnixNano: '1700000000000000000'
Defensive patterns
Strategy: validation
Validate before calling
const safeNs=(v:unknown)=>typeof v!=='number'||Number.isSafeInteger(v)?null:'encode as decimal string';
Type guard
const isSafeNsNumber=(v:unknown): v is number => typeof v==='number'&&Number.isSafeInteger(v);
Try / catch
try { ingest(records); } catch (e) { if (/lost precision/.test(e.message)) { /* re-serialize nanos as strings upstream */ } else throw e; } Prevention
- Always serialize OTLP nanosecond fields as decimal strings in JSON
- Never round-trip nanosecond epochs through JS numbers
- Enable lossless JSON options (e.g. json-bigint) on transport
When it happens
Trigger: Passing startTimeUnixNano/endTimeUnixNano/timeUnixNano as a JS number too large for Number.isSafeInteger (roughly > 9e15, easily hit by nanosecond epochs).
Common situations: JSON parsers decoding large OTLP nanosecond integers as numbers; exporters emitting numbers instead of strings; naive Number(...) conversion of a nanosecond string before ingest.
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
- Invalid : expected an OTLP nanosecond integer string.
- OTLP span : end precedes start.
- Trace duration exceeds safely representable milliseconds.
- Every OTLP span requires traceId and spanId.
- GitHub timestamp is invalid
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/856703546319bf26.
Report an issue: GitHub.
Appendix: source
Thrown at pet/src/core/ingest.ts:115
const a = obj(v);
if ('stringValue' in a) return a.stringValue;
if ('boolValue' in a) return a.boolValue;
if ('doubleValue' in a) return a.doubleValue;
if ('intValue' in a) {
const n = Number(a.intValue);
return Number.isSafeInteger(n) ? n : String(a.intValue);
}
if ('bytesValue' in a) return a.bytesValue;
if ('arrayValue' in a) return list(obj(a.arrayValue).values).map(decodeAnyValue);
if ('kvlistValue' in a) return attributes(obj(a.kvlistValue).values);
return v;
}
export function attributes(value: unknown): Obj {
if (!Array.isArray(value)) return obj(value);
return Object.fromEntries(value.filter(x => typeof x?.key === 'string').map(x => [x.key, decodeAnyValue(x.value)]));
}
function ns(v: unknown, field: string): bigint {
if (typeof v === 'number' && !Number.isSafeInteger(v)) throw new Error(`${field} lost precision: encode OTLP nanoseconds as a decimal string.`);
const s = String(v ?? '');
if (!/^\d{1,20}$/.test(s) || BigInt(s) > 18446744073709551615n) throw new Error(`Invalid ${field}: expected an OTLP nanosecond integer string.`);
return BigInt(s);
}
function otelStatus(v: unknown): Status {
const c = obj(v).code;
return c === 2 || c === 'STATUS_CODE_ERROR' ? 'error' : c === 1 || c === 'STATUS_CODE_OK' ? 'success' : 'unknown';
}
function normalizedEvent(v: unknown, index: number): WhaleEvent {
const a = obj(v), id = str(a.id), traceId = str(a.traceId), name = str(a.name);
if (!id || !traceId || !name) throw new Error(`Record ${index + 1} requires nonempty id, traceId, and name.`);
if (a.schemaVersion !== undefined && a.schemaVersion !== 1) throw new Error(`Record ${index + 1}: unsupported schemaVersion ${a.schemaVersion}.`);
const startTime = number(a.startTime, 'startTime', false)!;
const endTime = number(a.endTime, 'endTime') ?? startTime;
if (endTime < startTime) throw new Error(`Record ${index + 1}: endTime precedes startTime.`);
if (a.category !== undefined && !CATEGORIES.includes(a.category)) throw new Error(`Unknown category "${a.category}". Use "other" plus subtype for extensions.`);
const allowed: Status[] = ['pending', 'running', 'success', 'error', 'unknown'];
if (a.status !== undefined && !allowed.includes(a.status)) throw new Error(`Record ${index + 1}: invalid status.`);View on GitHub (pinned to 433685b202)