janhq/jan · error · Error

Model ${modelId} already exists

Error message

Model ${modelId} already exists

What it means

Thrown by MlxExtension.import() when a model.yml already exists at <providerPath>/models/<modelId>/model.yml. The extension refuses to clobber an existing model registration, so import is treated as create-only, not upsert. The check runs after modelId format validation but before any download or folder copy.

Source

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

        savePath: newModelConfigPath,
      })
    )
  }

  override async import(modelId: string, opts: ImportOptions): Promise<void> {
    if (!isValidModelId(modelId))
      throw new Error(
        `Invalid modelId: ${modelId}. Only alphanumeric and / _ - . characters are allowed.`
      )

    const configPath = await joinPath([
      await this.getProviderPath(),
      'models',
      modelId,
      'model.yml',
    ])
    if (await fs.existsSync(configPath))
      throw new Error(`Model ${modelId} already exists`)

    const sourcePath = opts.modelPath

    if (sourcePath.startsWith('https://')) {
      // Download from URL to mlx models folder
      const janDataFolderPath = await getJanDataFolderPath()
      const modelDir = await joinPath([
        janDataFolderPath,
        'mlx',
        'models',
        modelId,
      ])
      const localPath = await joinPath([modelDir, 'model.safetensors'])

      const downloadManager = window.core.extensionManager.getByName(
        '@janhq/download-extension'
      )

View on GitHub (pinned to fad3f12a14)

Solutions

  1. Delete or rename the existing folder <JanData>/mlx/models/<modelId> (and its model.yml) before re-importing.
  2. Use a different modelId for the new import to avoid the collision.
  3. Call a list/get API (or fs.existsSync on the config path) first and treat an existing model as an update, not an error.
  4. If the previous import failed mid-way, clean up the partial model.yml it left behind.

Example fix

// before
await mlx.import(modelId, opts) // throws if model.yml exists

// after
const exists = await fs.existsSync(configPath)
if (exists) {
  await fs.rm(modelDir, { recursive: true, force: true })
}
await mlx.import(modelId, opts)
Defensive patterns

Strategy: validation

Validate before calling

import { fs, joinPath } from '@janhq/janope'
async function modelExists(modelId: string): Promise<boolean> {
  const providerPath = await mlx.getProviderPath()
  const configPath = await joinPath([providerPath, 'models', modelId, 'model.yml'])
  return fs.existsSync(configPath)
}
if (await modelExists(modelId)) {
  // delete first, or pick a new id, or treat as update
}

Try / catch

try {
  await mlx.import(modelId, opts)
} catch (e) {
  if (e instanceof Error && e.message.endsWith('already exists')) {
    // idempotent: model already registered
  } else throw e
}

Prevention

When it happens

Trigger: Calling import(modelId, opts) a second time with the same modelId; calling import after a previous import partially succeeded and left model.yml behind; importing a modelId that collides with a model registered through a different provider path.

Common situations: Re-running an import after a download error was already recovered; user re-clicking 'Import' in the UI for the same model; CI re-running a setup script that imports MLX models; modelId collision between a HuggingFace-style ID and a local alias.

Related errors


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