{"record":{"id":"38d047c1aba4b6e4","repo":"CherryHQ/cherry-studio","slug":"image-generation-job-provider-providerid-not","errorCode":null,"errorMessage":"Image generation job: provider '${providerId}' not found","messagePattern":"Image generation job: provider '(.+?)' not found","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/ai/provider/custom/tasks/imageGenerationJobHandler.ts","lineNumber":63,"sourceCode":" * `painting.id` — the row exists before enqueue) and have this handler write the\n * result there, which registers `painting_file_ref` rows and makes GC correct for\n * free. Then switch `recovery` back to `'retry'` and restore the resume branch from\n * the recipe in `docs/references/job-and-scheduler/handler-authoring.md`.\n */\nexport const imageGenerationJobHandler: JobHandler<ImageGenerationJobPayload> = {\n  recovery: 'abandon',\n  defaultQueue: (input) => `image-generation.${parseUniqueModelId(input.uniqueModelId).providerId}`,\n  defaultConcurrency: 2,\n  // The transport already retries transient poll errors internally; a job-level\n  // retry would re-submit and burn the user's vendor quota, so cap at 1 attempt\n  // (parity with agent.task).\n  defaultRetryPolicy: { maxAttempts: 1, backoff: 'none', baseDelayMs: 0, maxDelayMs: 0 },\n  defaultTimeoutMs: 30 * 60_000,\n  async execute(ctx) {\n    const input = ctx.input\n    const { providerId, modelId } = parseUniqueModelId(input.uniqueModelId)\n    const provider = providerService.getByProviderId(providerId)\n    if (!provider) throw new Error(`Image generation job: provider '${providerId}' not found`)\n    const model = modelService.getByKey(providerId, modelId)\n    if (!model) throw new Error(`Image generation job: model '${modelId}' not found for provider '${providerId}'`)\n\n    const { config, credentialReceipt } = await resolveProviderAiSdkConfig(provider, model)\n    const sdkConfig = {\n      ...config,\n      modelId: resolveWireModelId(model, resolveEffectiveEndpoint(provider, model).endpointType)\n    }\n    // Built fresh every execution and held in memory only. Upstream persists this\n    // to job metadata so a resumed run can still attribute its cost; with\n    // `recovery: 'abandon'` no run outlives the process, so persisting it would be\n    // a write nobody reads — and would imply a durability this handler does not have.\n    const captureContext = createAiUsageCaptureContext({\n      providerId: provider.id,\n      providerName: provider.name,\n      modelId: sdkConfig.modelId,\n      modelName: model.name,\n      pricing: model.pricing,","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/CherryHQ/cherry-studio/blob/726446b54cd69ffe51a276638672f6d95ca0768c/src/main/ai/provider/custom/tasks/imageGenerationJobHandler.ts#L45-L81","documentation":"Thrown at the start of imageGenerationJobHandler.execute() when providerService.getByProviderId(providerId) returns null — the provider record is not in the database. The providerId was parsed from the job payload's uniqueModelId, so this means the provider was removed (or never existed) between enqueue and execution.","triggerScenarios":"A job is enqueued for a custom image-generation provider, then before/at execution the provider record is deleted from the DB, or the uniqueModelId's providerId segment is malformed/unknown. Because recovery is 'abandon', this surfaces during a fresh execution attempt rather than a resumed one.","commonSituations":"User deletes the provider while a job is queued, provider DB sync issue, uniqueModelId corruption, or a provider id typo in test fixtures.","solutions":["Confirm the provider still exists: check providerService.getByProviderId(providerId) before enqueue and at execution.","If the provider was intentionally removed, fail the job with a user-facing 'provider removed' message and clean up.","Guard the uniqueModelId format (parseUniqueModelId) so malformed ids fail at enqueue, not at execute.","Investigate DB consistency if the provider should exist."],"exampleFix":"// before\nconst provider = providerService.getByProviderId(providerId)\nif (!provider) throw new Error(`Image generation job: provider '${providerId}' not found`)\n// after — include parse context\nconst provider = providerService.getByProviderId(providerId)\nif (!provider) throw new Error(`Image generation job: provider '${providerId}' (from '${input.uniqueModelId}') not in DB; was it removed?`)","handlingStrategy":"validation","validationCode":"// Validate provider existence before enqueue\nconst provider = providerService.getByProviderId(providerId)\nif (!provider) throw new Error(`Cannot enqueue: provider '${providerId}' not found`)","typeGuard":"export function isJobProviderNotFound(e: unknown): boolean {\n  return e instanceof Error && /Image generation job: provider .* not found/.test(e.message)\n}","tryCatchPattern":"try {\n  await job.execute(ctx)\n} catch (e) {\n  if (isJobProviderNotFound(e)) {\n    // mark job failed with user-facing 'provider removed' reason\n  }\n  throw e\n}","preventionTips":["Validate provider existence at enqueue, not just at execute.","Cancel in-flight jobs when a provider is deleted.","Guard uniqueModelId format so malformed ids fail early."],"tags":["job-handler","provider-not-found","database","image-generation"],"backgroundTag":null,"analyzedSha":"726446b54cd69ffe51a276638672f6d95ca0768c","analyzedAt":"2026-08-12T17:30:37.448Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}