{"record":{"id":"8058e1bf102820c2","repo":"mudler/LocalAI","slug":"no-job-id-returned-from-server","errorCode":null,"errorMessage":"No job ID returned from server","messagePattern":"No job ID returned from server","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"core/http/react-ui/src/pages/ImportModel.jsx","lineNumber":307,"sourceCode":"      if (prefs.enable_parameters.trim()) prefsObj.enable_parameters = prefs.enable_parameters.trim()\n      if (prefs.cuda) prefsObj.cuda = true\n      customPrefs.forEach(cp => {\n        if (cp.key.trim() && cp.value.trim()) prefsObj[cp.key.trim()] = cp.value.trim()\n      })\n\n      const result = await modelsApi.importUri({\n        uri: importUri.trim(),\n        preferences: Object.keys(prefsObj).length > 0 ? prefsObj : null,\n      })\n\n      const hasSize = result.estimated_size_display && result.estimated_size_display !== '0 B'\n      const hasVram = result.estimated_vram_display && result.estimated_vram_display !== '0 B'\n      if (hasSize || hasVram) {\n        setEstimate({ sizeDisplay: result.estimated_size_display || '', vramDisplay: result.estimated_vram_display || '' })\n      }\n\n      const jobId = result.uuid || result.ID\n      if (!jobId) throw new Error('No job ID returned from server')\n\n      addToast(t('toasts.started'), 'success')\n      // Clear any prior ambiguity alert once the server accepts the import.\n      setAmbiguity(null)\n      startJobPolling(jobId)\n    } catch (err) {\n      // Structured ambiguity response — render the inline picker instead of\n      // a toast. The server returns HTTP 400 with { error, modality,\n      // candidates } which api.handleResponse attaches as err.body.\n      if (err?.status === 400 && err?.body && err.body.error === 'ambiguous import') {\n        setAmbiguity({\n          modality: err.body.modality || '',\n          candidates: Array.isArray(err.body.candidates) ? err.body.candidates : [],\n        })\n        setIsSubmitting(false)\n        return\n      }\n      addToast(t('toasts.startImportFailed', { message: err.message }), 'error')","sourceCodeStart":289,"sourceCodeEnd":325,"githubUrl":"https://github.com/mudler/LocalAI/blob/44413a9d06bf5bc52ce088ba8ca74e5a2e8bee26/core/http/react-ui/src/pages/ImportModel.jsx#L289-L325","documentation":"ImportModel.jsx calls modelsApi.importUri to start a URI-based (e.g. HuggingFace) model import and expects the response to carry a job identifier under uuid or ID, which startJobPolling then tracks. When neither field is present the client cannot poll the job, so it throws rather than showing a fake 'started' state. The next lines' ambiguity branch (HTTP 400 {error:'ambiguous import'}) is a different, handled path — this throw is for a 2xx response with an unexpected body.","triggerScenarios":"The server accepts the import (2xx) but returns a body without uuid/ID: version mismatch where an older/newer backend renamed the field (e.g. returns {id: ...} or {job: ...}), a proxy rewriting the response, or a server that synchronously completed the import and returned a different shape. The field check result.uuid || result.ID covers both casings, so genuinely missing means shape drift.","commonSituations":"React UI (newer) talking to an older LocalAI binary whose import-uri endpoint predates job-based responses; response mangled by an intercepting proxy; API contract changed in a fork.","solutions":["Align versions: run a LocalAI server of the same generation as the React UI (rebuild/update the backend)","Inspect the actual response: console.log(result) or curl -X POST $BASE/importuri to see which field name the server uses","If the server completed synchronously, skip polling: treat presence of the model in /v1/models as success instead of throwing","Add the server's field name to the jobId lookup (see exampleFix)"],"exampleFix":"// before\nconst jobId = result.uuid || result.ID\nif (!jobId) throw new Error('No job ID returned from server')\n\n// after — tolerate shape drift\nconst jobId = result.uuid || result.ID || result.id || result.job?.uuid\nif (!jobId) throw new Error('No job ID returned from server')","handlingStrategy":"validation","validationCode":"// defensive shape check before relying on the job id\nfunction extractJobId(result) {\n  if (!result || typeof result !== 'object') return null\n  return result.uuid ?? result.ID ?? result.id ?? null\n}","typeGuard":"function isImportJobResponse(result) {\n  return Boolean(result) && typeof result === 'object' && ('uuid' in result || 'ID' in result)\n}","tryCatchPattern":"try {\n  const result = await modelsApi.importUri({ uri, preferences })\n  const jobId = result.uuid || result.ID || result.id\n  if (!jobId) throw new Error('No job ID returned from server')\n  startJobPolling(jobId)\n} catch (err) {\n  if (err?.status === 400 && err?.body?.error === 'ambiguous import') setAmbiguity(err.body)\n  else if (err.message === 'No job ID returned from server') addToast('server version mismatch — update LocalAI', 'error')\n  else addToast(err.message, 'error')\n}","preventionTips":["Keep the React UI and the LocalAI backend on matching versions — job fields are part of the contract","Log the raw import response body when the id lookup fails to identify field drift quickly","Treat missing job id as a distinct, named error so users get a version-mismatch hint, not a generic failure"],"tags":["model-import","api-contract","polling","version-mismatch","react-ui"],"backgroundTag":null,"analyzedSha":"44413a9d06bf5bc52ce088ba8ca74e5a2e8bee26","analyzedAt":"2026-08-15T10:13:50.291Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}