denoland/deno · error · Error
Observable counters can only be incremented
Error message
Observable counters can only be incremented
What it means
ObservableResult.observe() (ext/telemetry/telemetry.ts:1141) throws when it observes a negative value on an instrument created with createObservableCounter (ObservableResult constructed with isRegularCounter = true). Observable counters are monotonic; observable up-down counters and observable gauges pass isRegularCounter = false and accept negative values.
Source
Thrown at ext/telemetry/telemetry.ts:1163
}
class ObservableResult {
#instrument: Instrument | null;
#isRegularCounter: boolean;
constructor(instrument: Instrument | null, isRegularCounter: boolean) {
this.#instrument = instrument;
this.#isRegularCounter = isRegularCounter;
}
observe(
this: ObservableResult,
value: number,
attributes?: MetricAttributes,
): void {
if (this.#isRegularCounter) {
if (value < 0) {
throw new Error("Observable counters can only be incremented");
}
}
recordObservable(this.#instrument, value, attributes);
}
}
async function observe(): Promise<void> {
if (ISOLATE_METRICS) {
op_otel_collect_isolate_metrics();
}
const promises: Promise<void>[] = [];
// Primordials are not needed, because this is a SafeMap.
// deno-lint-ignore deno-internal/prefer-primordials
for (const { 0: observable, 1: callbacks } of INDIVIDUAL_CALLBACKS) {
const result = getObservableResult(observable);
// Primordials are not needed, because this is a SafeSet.
// deno-lint-ignore deno-internal/prefer-primordialsView on GitHub (pinned to a961cdec3b)
Solutions
- Use meter.createObservableUpDownCounter(name) if observations can be negative
- Observe absolute cumulative totals (monotonic counters) instead of deltas
- Clamp in the callback: result.observe(Math.max(0, value))
Example fix
// before
meter.createObservableCounter('jobs_done', (res) => {
res.observe(jobsNow - jobsPrev); // negative when counter reset
});
// after
meter.createObservableCounter('jobs_done', (res) => {
res.observe(jobsNow); // absolute cumulative value
}); Defensive patterns
Strategy: validation
Validate before calling
// inside the observable callback
if (value >= 0 || !isMonotonicInstrument) {
result.observe(value, attributes);
} Type guard
const isObserveable = (n: number): boolean => typeof n === "number" && Number.isFinite(n);
Try / catch
try {
result.observe(value);
} catch (e) {
if (
e instanceof Error &&
e.message === "Observable counters can only be incremented"
) {
result.observe(0); // or skip this collection round
} else {
throw e;
}
} Prevention
- Observe absolute totals on observable counters, never deltas
- Use createObservableUpDownCounter for signed point-in-time metrics
- Guard collector callbacks against counter resets (previous > current)
When it happens
Trigger: Inside a createObservableCounter callback, calling result.observe(current - previous) where the underlying metric decreased between collections; reporting a signed value through an observable counter because it was the first observable factory reached for.
Common situations: Switching an instrument from createObservableGauge to createObservableCounter without adjusting the callback to report absolute totals; delta-style collectors feeding cumulative instruments; metrics that dip below zero during resets or counter restarts.
Related errors
- Counter can only be incremented
- MeterProvider can not be constructed
- Only valueType: DOUBLE is supported
- vsock is not supported on this platform
- invalid vsock addr
AI-assisted analysis of denoland/deno@a961cdec3b (2026-08-20).
Data as JSON: /api/errors/fa4f331cb85760c4.
Report an issue: GitHub.