{"record":{"id":"43c6dffb27605953","repo":"TencentCloud/TencentDB-Agent-Memory","slug":"skill-worker-pool-concurrency-must-be-positive-i","errorCode":null,"errorMessage":"[skill-worker-pool] concurrency must be positive integer, got ${opts.concurrency}","messagePattern":"\\[skill-worker-pool\\] concurrency must be positive integer, got (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"MemoryCore/src/core/skill/conversation-add/worker-pool.ts","lineNumber":99,"sourceCode":"   * 2026-08-03 crash-recovery §4.5: 降级路径下周期性自愈扫描的间隔 ms。\n   * 只在 queue.getPeekStrategy() === \"rpop_lpush_downgrade\" 时启用。默认 60_000。\n   * 非降级路径下 start() 只跑一次冷启动扫描, 不启动定时器。\n   */\n  selfHealIntervalMs?: number;\n}\n\nexport class SkillWorkerPool {\n  private readonly opts: SkillWorkerPoolOptions;\n  private readonly logger: ExtractorLogger;\n  private readonly poolId: string;\n  private closed = false;\n  private started = false;\n  private loopPromises: Promise<void>[] = [];\n  private selfHealTimer: ReturnType<typeof setInterval> | undefined;\n\n  constructor(opts: SkillWorkerPoolOptions) {\n    if (!Number.isInteger(opts.concurrency) || opts.concurrency < 1) {\n      throw new Error(`[skill-worker-pool] concurrency must be positive integer, got ${opts.concurrency}`);\n    }\n    this.opts = opts;\n    this.logger = opts.logger;\n    this.poolId = opts.poolId ?? `skill-pool-${process.pid}`;\n  }\n\n  start(): void {\n    if (this.started) return;\n    this.started = true;\n    this.closed = false;\n    const n = this.opts.concurrency;\n    this.logger.info(\n      `[skill-worker-pool] start pool_id=${this.poolId} concurrency=${n} ` +\n        `brpopBlockMs=${this.opts.brpopBlockMs ?? 5000} ` +\n        `extractLockTtlMs=${this.opts.extractLockTtlMs ?? 600_000}`,\n    );\n\n    // 2026-08-03 crash-recovery §4.5: 冷启动跑一次 selfHealScan, 清历史遗留的","sourceCodeStart":81,"sourceCodeEnd":117,"githubUrl":"https://github.com/TencentCloud/TencentDB-Agent-Memory/blob/3efcd317b84146d6a08518ac0f7ee7c8a8d200ec/MemoryCore/src/core/skill/conversation-add/worker-pool.ts#L81-L117","documentation":"SkillWorkerPool validates its options in the constructor and refuses to build a pool whose concurrency is not a positive integer. This is a fail-fast programming-error guard: a pool with 0, negative, fractional, or NaN concurrency cannot run worker loops meaningfully.","triggerScenarios":"new SkillWorkerPool({ concurrency: 0 }), concurrency: -2, a fractional value like 2.5, NaN coming from an unparsed env var (Number.parseInt returning NaN), or undefined when the caller relies on a default that was never applied.","commonSituations":"Reading worker count from env/config (e.g. WORKER_CONCURRENCY) without validating the parsed number; arithmetic producing NaN; copy-pasted config where concurrency was commented out; dynamic sizing code computing 0 when the queue is empty.","solutions":["Pass an explicit positive integer: validate before constructing (Number.isInteger(n) && n >= 1)","Sanitize env-derived values with a fallback: const c = Number.parseInt(raw) || defaultConcurrency","Math.round/floor any computed concurrency and clamp to at least 1","Fix the config source so concurrency is actually provided"],"exampleFix":"// before\nconst pool = new SkillWorkerPool({ concurrency: Number(process.env.WORKERS), ... });\n// after\nconst workers = Math.max(1, Math.floor(Number(process.env.WORKERS) || 4));\nconst pool = new SkillWorkerPool({ concurrency: workers, ... });","handlingStrategy":"validation","validationCode":"function assertPositiveInt(n: unknown): asserts n is number {\n  if (!Number.isInteger(n) || (n as number) < 1)\n    throw new Error(`concurrency must be a positive integer, got ${n}`);\n}\nassertPositiveInt(opts.concurrency);","typeGuard":"function isValidConcurrency(v: unknown): v is number {\n  return typeof v === \"number\" && Number.isInteger(v) && v >= 1;\n}","tryCatchPattern":"let pool: SkillWorkerPool;\ntry {\n  pool = new SkillWorkerPool(opts);\n} catch (e) {\n  if (e instanceof Error && e.message.includes(\"concurrency must be positive integer\")) {\n    pool = new SkillWorkerPool({ ...opts, concurrency: 4 });\n  } else throw e;\n}","preventionTips":["Sanitize env-derived numbers: Math.max(1, Math.floor(Number(raw) || DEFAULT))","Never pass possibly-NaN values straight from config into constructors","Validate the whole options object at config-load time, not at pool construction","Add a startup smoke test that constructs the pool with production config"],"tags":["configuration","validation","constructor","worker-pool"],"backgroundTag":"invalid-config-value","analyzedSha":"3efcd317b84146d6a08518ac0f7ee7c8a8d200ec","analyzedAt":"2026-09-01T05:44:22.276Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}