{"record":{"id":"7dc0f9f95f6b306e","repo":"hcengineering/platform","slug":"ticks-must-be-1","errorCode":null,"errorMessage":"Ticks must be >= 1","messagePattern":"Ticks must be >= 1","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"foundations/net/packages/core/src/utils.ts","lineNumber":111,"sourceCode":"    }\n    this.started = true\n    const to = setInterval(\n      () => {\n        this.tick().catch((err) => {\n          console.error('Error in tick manager:', err)\n        })\n      },\n      Math.round(1000 / this.tps)\n    )\n    this.stop = () => {\n      this.started = false\n      clearInterval(to)\n    }\n  }\n\n  async waitTick (ticks: number): Promise<void> {\n    if (ticks < 1) {\n      throw new Error('Ticks must be >= 1')\n    }\n    const targetTick = this._tick + ticks\n\n    await new Promise<void>((resolve) => {\n      this.tickListeners.set(targetTick, [...(this.tickListeners.get(targetTick) ?? []), resolve])\n    })\n  }\n}\n\nexport function composeCID (prefix: string, id: string): ContainerUuid {\n  return `${prefix}_${id}` as ContainerUuid\n}\n\nexport class FakeTickManager implements TickManager {\n  private currentTime: number = 0\n  private readonly handlers: Array<{ handler: TickHandler, interval: number, lastTick: number }> = []\n\n  now = (): number => {","sourceCodeStart":93,"sourceCodeEnd":129,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/foundations/net/packages/core/src/utils.ts#L93-L129","documentation":"waitTick resolves when the internal tick counter reaches the current tick plus `ticks`; it requires at least 1 tick of waiting. Zero or negative values have no future target tick, so the library rejects them immediately instead of resolving spuriously or hanging.","triggerScenarios":"Calling await ticker.waitTick(0) or waitTick(negative) — typically computed values like waitTick(someCounter) where the counter was 0, or waitTick(end - start) with end <= start.","commonSituations":"Passing a computed delay that evaluated to 0 (e.g. ticks = Math.floor(elapsed) with elapsed < 1); unit confusion passing seconds and expecting auto-conversion; loop logic where an empty batch yields 0 ticks.","solutions":["Pass an integer >= 1, or skip the call entirely when no wait is needed: if (ticks >= 1) await ticker.waitTick(ticks).","Clamp computed values: Math.max(1, computedTicks) when a wait is mandatory.","Ensure the quantity is measured in ticks, not seconds/milliseconds; convert explicitly if needed.","Audit the call site that produced 0 and handle the no-op case before awaiting."],"exampleFix":"// before\nawait ticker.waitTick(batchCount) // batchCount can be 0\n// after\nif (batchCount >= 1) await ticker.waitTick(batchCount)","handlingStrategy":"validation","validationCode":"async function safeWaitTick(ticker: Ticker, ticks: number): Promise<void> {\n  if (!Number.isInteger(ticks) || ticks < 1) return // nothing to wait for\n  await ticker.waitTick(ticks)\n}","typeGuard":"function isPositiveTickCount(v: unknown): v is number {\n  return typeof v === 'number' && Number.isInteger(v) && v >= 1\n}","tryCatchPattern":"try {\n  await ticker.waitTick(ticks)\n} catch (e) {\n  if ((e as Error).message === 'Ticks must be >= 1') {\n    console.warn(`waitTick(${ticks}) invalid — skipping wait`)\n  } else throw e\n}","preventionTips":["Guard computed tick counts: skip the wait when the value is 0 instead of calling waitTick.","Document that the parameter is ticks, not seconds; convert units at the call boundary.","Write unit tests for wait sites using boundary values 0 and 1.","Prefer a small helper wrapper that clamps/short-circuits instead of calling waitTick directly."],"tags":["validation","timer","tick","async"],"backgroundTag":"invalid-parameter-value","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}