janhq/jan · warning · Error

Model ${modelId} does not exist

Error message

Model ${modelId} does not exist

What it means

Thrown by delete() when the target folder's model.yml is absent. delete() resolves provider/models/<modelId>/model.yml and refuses to proceed if it does not exist, because without the config it cannot know the model_path to clean up weights. This guards against deleting arbitrary/empty folders.

Source

Thrown at extensions/mlx-extension/src/index.ts:512

            throw new Error(error.message)
          }
        }
      }
    } finally {
      reader.releaseLock()
    }
  }

  override async delete(modelId: string): Promise<void> {
    const modelDir = await joinPath([
      await this.getProviderPath(),
      'models',
      modelId,
    ])

    const modelConfigPath = await joinPath([modelDir, 'model.yml'])
    if (!(await fs.existsSync(modelConfigPath))) {
      throw new Error(`Model ${modelId} does not exist`)
    }

    const modelConfig = await invoke<ModelConfig>('read_yaml', {
      path: modelConfigPath,
    })

    // Check if model_path is a relative path within mlx folder
    if (!isAbsoluteModelPath(modelConfig.model_path)) {
      // Model file is at {janDataFolder}/{model_path}
      // Delete the parent folder containing the actual model file
      const janDataFolderPath = await getJanDataFolderPath()
      const modelPath = await joinPath([
        janDataFolderPath,
        modelConfig.model_path,
      ])
      const parentDir = modelPath.substring(0, modelPath.lastIndexOf('/'))
      // Only delete if it's different from modelDir (i.e., not the same folder)
      if (parentDir !== modelDir) {

View on GitHub (pinned to fad3f12a14)

Solutions

  1. Refresh the model list before offering delete so it reflects actual disk state.
  2. If weights remain without model.yml, locate the model file under the jan data folder and remove it manually, then clear the list entry.
  3. Guard delete() callers with a list()/exists check and no-op if absent.
  4. Verify modelId casing/format matches the imported id.

Example fix

// before
await engine.delete(modelId)

// after
const cfg = await joinPath([await engine.getProviderPath(), 'models', modelId, 'model.yml'])
if (!(await fs.existsSync(cfg))) {
  logger.warn(`Model ${modelId} already gone; nothing to delete`)
  return
}
await engine.delete(modelId)
Defensive patterns

Strategy: validation

Validate before calling

async function modelInstalled(providerPath: string, modelId: string): Promise<boolean> {
  const p = await joinPath([providerPath, 'models', modelId, 'model.yml'])
  return fs.existsSync(p)
}

if (!(await modelInstalled(await engine.getProviderPath(), modelId))) {
  logger.warn(`Model ${modelId} not present; skipping delete`)
  return
}

Try / catch

try {
  await engine.delete(modelId)
} catch (e) {
  if (/does not exist/.test(String(e))) return // already gone
  throw e
}

Prevention

When it happens

Trigger: Calling delete() on a modelId that was never imported, was already deleted, or whose model.yml was removed manually while weights remain; a modelId with wrong casing/separators that doesn't resolve to the real folder.

Common situations: UI delete action fired twice (second finds it gone); partial delete earlier removed model.yml but not the folder; user manually cleaned model.yml; stale model list shows an id whose folder is gone.

Related errors


AI-assisted analysis of janhq/jan@fad3f12a14 (2026-08-12). Data as JSON: /api/errors/739f5f0253ce6eb3. Report an issue: GitHub.