{"record":{"id":"2e1b8b59156cff4f","repo":"CherryHQ/cherry-studio","slug":"image-generation-job-model-modelid-not-found","errorCode":null,"errorMessage":"Image generation job: model '${modelId}' not found for provider '${providerId}'","messagePattern":"Image generation job: model '(.+?)' not found for provider '(.+?)'","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/ai/provider/custom/tasks/imageGenerationJobHandler.ts","lineNumber":65,"sourceCode":" * 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,\n      trustProviderReportedCost: provider.apiFeatures.reportsActualCost,\n      reportedCostCurrency: provider.reportedCostCurrency,","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/CherryHQ/cherry-studio/blob/726446b54cd69ffe51a276638672f6d95ca0768c/src/main/ai/provider/custom/tasks/imageGenerationJobHandler.ts#L47-L83","documentation":"Thrown immediately after the provider lookup in imageGenerationJobHandler.execute() when modelService.getByKey(providerId, modelId) returns null — the model record is missing for that provider. Like 397, it reflects a stale payload: the model existed at enqueue time but not at execution.","triggerScenarios":"A job enqueued for a specific provider+model combination, but the model record was deleted (or provider/model mismatch) before execution. The modelId is parsed from uniqueModelId; a wrong pairing or a removed model triggers it.","commonSituations":"User removes the model while a job is queued, model sync lag, modelId drift after a rename, or uniqueModelId built with the wrong provider/model pairing.","solutions":["Confirm the model still exists: check modelService.getByKey(providerId, modelId) before enqueue and at execution.","If the model was removed, fail the job with a user-facing 'model removed' message.","Validate the provider↔model pairing when building uniqueModelId.","Investigate model catalog sync if the model should exist."],"exampleFix":"// before\nconst model = modelService.getByKey(providerId, modelId)\nif (!model) throw new Error(`Image generation job: model '${modelId}' not found for provider '${providerId}'`)\n// after — hint at the likely cause\nif (!model) throw new Error(`Image generation job: model '${modelId}' for provider '${providerId}' not in DB; was it removed or renamed?`)","handlingStrategy":"validation","validationCode":"const model = modelService.getByKey(providerId, modelId)\nif (!model) throw new Error(`Cannot enqueue: model '${modelId}' not found for provider '${providerId}'`)","typeGuard":"export function isJobModelNotFound(e: unknown): boolean {\n  return e instanceof Error && /Image generation job: model .* not found/.test(e.message)\n}","tryCatchPattern":"try {\n  await job.execute(ctx)\n} catch (e) {\n  if (isJobModelNotFound(e)) {\n    // mark job failed with user-facing 'model removed' reason\n  }\n  throw e\n}","preventionTips":["Validate model existence at enqueue.","Cancel in-flight jobs when a model is deleted or renamed.","Verify provider↔model pairing when constructing uniqueModelId."],"tags":["job-handler","model-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"}