{"record":{"id":"229b69813e261436","repo":"deepseek-ai/deepseek-harness","slug":"attachment-local-imagecompressionconcurrency-must","errorCode":null,"errorMessage":"attachment-local: imageCompressionConcurrency must be an integer from 1 through ${MAX_IMAGE_COMPRESSION_CONCURRENCY}","messagePattern":"attachment-local: imageCompressionConcurrency must be an integer from 1 through (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/attachment/attachment-local/src/index.ts","lineNumber":177,"sourceCode":"    super(ctx)\n    this.root = resolve(join(resolveDshHome(config.dshHome), 'attachments', 'v1'))\n    this.imageLimits = Object.freeze({\n      maxImageBytes: config.maxImageBytes ?? DEFAULT_MAX_IMAGE_BYTES,\n      maxImagesPerMessage: config.maxImagesPerMessage ?? DEFAULT_MAX_IMAGES_PER_MESSAGE,\n      maxMessageImageBytes: config.maxMessageImageBytes ?? DEFAULT_MAX_MESSAGE_IMAGE_BYTES,\n      maxImagePixels: config.maxImagePixels ?? DEFAULT_MAX_IMAGE_PIXELS,\n      maxImageDimension: config.maxImageDimension ?? DEFAULT_MAX_IMAGE_DIMENSION,\n      mediaTypes: Object.freeze(['image/png', 'image/jpeg', 'image/webp', 'image/gif'] as const),\n    })\n    this.normalizationPolicy = Object.freeze({\n      maxDimension: config.normalizedImageMaxDimension ?? DEFAULT_NORMALIZED_IMAGE_MAX_DIMENSION,\n      maxBytes: config.normalizedImageMaxBytes ?? DEFAULT_NORMALIZED_IMAGE_MAX_BYTES,\n    })\n    const compressionConcurrency = config.imageCompressionConcurrency ?? DEFAULT_IMAGE_COMPRESSION_CONCURRENCY\n    if (!Number.isSafeInteger(compressionConcurrency)\n      || compressionConcurrency < 1\n      || compressionConcurrency > MAX_IMAGE_COMPRESSION_CONCURRENCY) {\n      throw new Error(\n        `attachment-local: imageCompressionConcurrency must be an integer from 1 through ${MAX_IMAGE_COMPRESSION_CONCURRENCY}`,\n      )\n    }\n    this.imageCompressionConcurrency = compressionConcurrency\n    this.compression = new CompressionLimiter(compressionConcurrency)\n  }\n\n  async validateImage(input: SaveImageAttachment): Promise<void> {\n    await this.compression.run(() => validateImageFile(input, this.imageLimits, this.normalizationPolicy))\n  }\n\n  override async saveImages(inputs: readonly SaveImageAttachment[]): Promise<readonly ImageAttachmentRef[]> {\n    this.validateImageBatch(inputs)\n    const prepared = await Promise.all(inputs.map(input => this.compression.run(\n      () => prepareImageFile(input, this.imageLimits, this.normalizationPolicy),\n    )))\n    const refs: ImageAttachmentRef[] = []\n    for (const image of prepared) refs.push(await commitPreparedImageFile(this.root, image))","sourceCodeStart":159,"sourceCodeEnd":195,"githubUrl":"https://github.com/deepseek-ai/deepseek-harness/blob/b150a551b8d465e31e418e1b2eaf5e79bbb7d28e/packages/attachment/attachment-local/src/index.ts#L159-L195","documentation":"LocalAttachmentStore's constructor validates imageCompressionConcurrency — the number of simultaneous native sharp transformations per store — and throws a plain Error when the value is not a safe integer in [1, 8] (MAX_IMAGE_COMPRESSION_CONCURRENCY; default 2). The schemastery Config enforces the same range for cordis.yml loads, so this throw chiefly guards direct or programmatic construction with values like 0, 9, 2.5, or NaN, failing at plugin-load time before any image operation.","triggerScenarios":"Constructing or loading LocalAttachmentStore with config.imageCompressionConcurrency outside 1-8: 0 to disable, 9+ for more parallelism, fractional values, or NaN/Infinity from unvalidated env-derived input passed around the schema.","commonSituations":"Performance tuning that copies a larger worker count into this field; sourcing the value from an env var without parsing or clamping; test harnesses constructing the store directly with hand-built config objects.","solutions":["Set an integer from 1 through 8, or omit the field entirely (default 2).","When sourcing from env or flags, parse and clamp: Math.min(8, Math.max(1, Math.trunc(Number(value)))), and reject NaN.","If you need more than 8 concurrent native transformations, scale out with additional store instances rather than exceeding the cap."],"exampleFix":"// before\nctx.plugin(LocalAttachmentStore, { dshHome, imageCompressionConcurrency: 16 })\n\n// after\nctx.plugin(LocalAttachmentStore, { dshHome, imageCompressionConcurrency: 8 })","handlingStrategy":"validation","validationCode":"const raw = Number(process.env.IMAGE_COMPRESSION_CONCURRENCY ?? '2')\nif (!Number.isSafeInteger(raw) || raw < 1 || raw > 8) {\n  throw new Error('IMAGE_COMPRESSION_CONCURRENCY must be an integer from 1 through 8')\n}","typeGuard":null,"tryCatchPattern":"try {\n  ctx.plugin(LocalAttachmentStore, config)\n} catch (error) {\n  if (error instanceof Error && error.message.startsWith('attachment-local: imageCompressionConcurrency')) {\n    // configuration error: fail startup with the config path in the message\n  }\n  throw error\n}","preventionTips":["Source the value from the schema-validated cordis.yml Config, never raw env strings.","Keep the 1-8 bound in generated config docs and examples.","Treat a startup throw here as intended fail-loud behavior — do not catch-and-default."],"tags":["config","validation","attachment-local","startup"],"backgroundTag":"config-validation-failed","analyzedSha":"b150a551b8d465e31e418e1b2eaf5e79bbb7d28e","analyzedAt":"2026-08-24T18:12:29.105Z","schemaVersion":2},"datasetVersion":"2026-08-24T22:17:12.610Z"}