denoland/deno · error · Error
Counter can only be incremented
Error message
Counter can only be incremented
What it means
Counter.add() in Deno's OTel shim (ext/telemetry/telemetry.ts:1056) throws when value is negative and the counter is a plain counter (created via createCounter, upDown = false). Counters are monotonic per the metrics specification; only up-down counters created with createUpDownCounter accept negative deltas. The check happens at call time, before record().
Source
Thrown at ext/telemetry/telemetry.ts:1078
);
i += 1;
}
}
}
}
class Counter {
#instrument: Instrument | null;
#upDown: boolean;
constructor(instrument: Instrument | null, upDown: boolean) {
this.#instrument = instrument;
this.#upDown = upDown;
}
add(value: number, attributes?: MetricAttributes, _context?: Context): void {
if (value < 0 && !this.#upDown) {
throw new Error("Counter can only be incremented");
}
record(this.#instrument, value, attributes);
}
}
class Gauge {
#instrument: Instrument | null;
constructor(instrument: Instrument | null) {
this.#instrument = instrument;
}
record(
value: number,
attributes?: MetricAttributes,
_context?: Context,
): void {
record(this.#instrument, value, attributes);View on GitHub (pinned to a961cdec3b)
Solutions
- Create the instrument with meter.createUpDownCounter(name) when deltas can be negative
- Clamp the value: counter.add(Math.max(0, value))
- Restructure to monotonic events, e.g. count 'items_removed' in its own counter instead of subtracting
Example fix
// before
const size = meter.createCounter('queue.size');
size.add(onQueue.length - prevLength); // throws when queue shrinks
// after
const size = meter.createUpDownCounter('queue.size');
size.add(onQueue.length - prevLength); Defensive patterns
Strategy: validation
Validate before calling
if (value >= 0 || upDown) {
counter.add(value, attributes);
} else {
// log or route to an up-down counter
} Type guard
const isNonNegative = (n: number): boolean => typeof n === "number" && Number.isFinite(n) && n >= 0;
Try / catch
try {
counter.add(delta, attrs);
} catch (e) {
if (e instanceof Error && e.message === "Counter can only be incremented") {
upDown.add(delta, attrs); // fallback instrument that accepts negatives
} else {
throw e;
}
} Prevention
- Choose createUpDownCounter whenever deltas can be negative
- Compute deltas with clamping when feeding monotonic counters
- Unit-test instrumentation wrappers with negative inputs
When it happens
Trigger: counter.add(-1) to 'undo' a previous increment; counter.add(delta) where delta = removed - added from a diff computation and can go negative; generic instrumentation wrappers that pass through arbitrary signed values to a plain counter.
Common situations: Tracking queue size or connection count with createCounter and then feeding signed deltas on decrement; applying metrics patches/diffs from external collectors; math errors that underflow into negatives.
Related errors
- Observable counters 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/f5b0e2aeed66f833.
Report an issue: GitHub.