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().

Solutions

  1. Create the instrument with meter.createUpDownCounter(name) when deltas can be negative
  2. Clamp the value: counter.add(Math.max(0, value))
  3. 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

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


AI-assisted analysis of denoland/deno@a961cdec3b (2026-08-20). Data as JSON: /api/errors/f5b0e2aeed66f833. Report an issue: GitHub.

Appendix: 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)