{"record":{"id":"6f92b20a715d80a1","repo":"musistudio/claude-code-router","slug":"this-media-job-has-no-media-api-model-binding-and","errorCode":null,"errorMessage":"This media job has no media API model binding and cannot be resumed.","messagePattern":"This media job has no media API model binding and cannot be resumed\\.","errorType":"error_code","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/core/src/media/service.ts","lineNumber":713,"sourceCode":"}\n\nfunction requiredModelSelector(value: string): string {\n  const selector = value?.trim();\n  if (!selector) throw new Error(\"A media model must be selected.\");\n  return selector;\n}\n\nfunction isProviderApiJob(job: MediaJob): boolean {\n  const backend = (job as unknown as { backend?: string }).backend;\n  return backend === \"gateway-media-api\" || backend === \"provider-api\" || backend === \"xai-api\";\n}\n\nfunction jobModelSelector(job: MediaJob): string;\nfunction jobModelSelector(job: MediaJob, required: false): string | undefined;\nfunction jobModelSelector(job: MediaJob, required = true): string | undefined {\n  const selector = optionalString((job as Partial<MediaJob>).modelSelector);\n  if (selector) return selector;\n  if (required) throw new Error(\"This media job has no media API model binding and cannot be resumed.\");\n  return undefined;\n}\n\nexport function resolveProviderMediaTarget(config: AppConfig, selector: string, operation?: MediaOperation): GatewayMediaTarget {\n  const normalizedSelector = normalizeMediaModelSelector(config, selector, operation);\n  const registry = modelRegistryForConfig(config);\n  let resolved = registry.resolveProviderModel(normalizedSelector);\n  if (!resolved) {\n    const parsed = parseProviderModelSelector(normalizedSelector);\n    const provider = parsed ? registry.findProvider(parsed.provider) : undefined;\n    if (provider && isImportedGrokAgentProvider(provider) && grokMediaModelKind(parsed?.model)) {\n      resolved = { model: parsed!.model, provider };\n    }\n  }\n  if (!resolved) throw new Error(`Media model is not configured by a provider: ${normalizedSelector}`);\n  const expectedKind = operation === \"video-generate\" ? \"video\" : operation ? \"image\" : undefined;\n  const modelKind = grokMediaModelKind(resolved.model);\n  if (expectedKind && modelKind && modelKind !== expectedKind) {","sourceCodeStart":695,"sourceCodeEnd":731,"githubUrl":"https://github.com/musistudio/claude-code-router/blob/99f24806c6a2c660b16e53e95211c517448a6c90/packages/core/src/media/service.ts#L695-L731","documentation":"jobModelSelector(job) reads the persisted modelSelector on a media job; resume paths require it because a resumed job must rebind to the same media API model. Jobs created without a model binding (legacy or non-API jobs) cannot be resumed.","triggerScenarios":"Calling a resume/retry API on a job whose stored record lacks modelSelector — typically jobs created before model binding was tracked.","commonSituations":"Upgrading the service and resuming jobs persisted by an older version; jobs created through a local backend rather than a media API provider.","solutions":["Resume only jobs created by the current version that carry a model binding","Re-issue the original request as a new job instead of resuming","Migrate old job records to include modelSelector if resume support is required"],"exampleFix":"// before\nconst job = service.resumeJob(legacyJob.id);\n// after\nconst job = service.submitJob({ ...legacyJob.request }); // fresh job","handlingStrategy":"try-catch","validationCode":"const canResume = Boolean((job as Partial<MediaJob>).modelSelector);","typeGuard":"const isResumableJob = (job: unknown): job is { modelSelector: string } => typeof (job as { modelSelector?: unknown }).modelSelector === \"string\" && (job as { modelSelector: string }).modelSelector.length > 0;","tryCatchPattern":"try { service.resume(job.id); } catch (e) { if (e instanceof Error && e.message.includes(\"cannot be resumed\")) return resubmit(job); throw e; }","preventionTips":["Don't persist resume intent across versions","Re-submit old jobs instead of resuming"],"tags":["media","resume","migration"],"backgroundTag":"stale-persisted-state","analyzedSha":"99f24806c6a2c660b16e53e95211c517448a6c90","analyzedAt":"2026-08-27T04:11:01.184Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}