janhq/jan · warning · Error

Model with ID ${model.id} already exists

Error message

Model with ID ${model.id} already exists

What it means

Thrown by update() when renaming a model: the destination folder provider/models/<model.id> already exists. update() moves the model folder from modelId to model.id and rewrites model_path; it refuses to clobber an existing model. The check is a pure filesystem existence test.

Source

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

    modelId: string,
    model: Partial<modelInfo>
  ): Promise<void> {
    // Delegate to the same logic as llamacpp since they share the model dir
    const modelFolderPath = await joinPath([
      await this.getProviderPath(),
      'models',
      modelId,
    ])
    const modelConfig = await invoke<ModelConfig>('read_yaml', {
      path: await joinPath([modelFolderPath, 'model.yml']),
    })
    const newFolderPath = await joinPath([
      await this.getProviderPath(),
      'models',
      model.id,
    ])
    if (await fs.existsSync(newFolderPath)) {
      throw new Error(`Model with ID ${model.id} already exists`)
    }
    const newModelConfigPath = await joinPath([newFolderPath, 'model.yml'])
    await fs.mv(modelFolderPath, newFolderPath).then(() =>
      invoke('write_yaml', {
        data: {
          ...modelConfig,
          model_path: modelConfig?.model_path?.replace(
            `mlx/models/${modelId}`,
            `mlx/models/${model.id}`
          ),
        },
        savePath: newModelConfigPath,
      })
    )
  }

  override async import(modelId: string, opts: ImportOptions): Promise<void> {
    if (!isValidModelId(modelId))

View on GitHub (pinned to fad3f12a14)

Solutions

  1. Choose a new model.id that does not match any existing entry in list().
  2. Delete or rename the conflicting existing model first.
  3. If the destination folder is stale leftover (no model.yml), remove it manually then retry.
  4. On case-insensitive filesystems, pick an id that differs by more than case.

Example fix

// before
await engine.update(modelId, { id: newId })

// after
const dest = await joinPath([await engine.getProviderPath(), 'models', newId])
if (await fs.existsSync(dest)) {
  throw new Error(`Name '${newId}' is taken; pick another`)
}
await engine.update(modelId, { id: newId })
Defensive patterns

Strategy: validation

Validate before calling

async function targetNameFree(providerPath: string, newId: string): Promise<boolean> {
  return !(await fs.existsSync(await joinPath([providerPath, 'models', newId])))
}

if (!(await targetNameFree(await engine.getProviderPath(), model.id))) {
  throw new Error(`Name '${model.id}' already taken`)
}

Try / catch

try {
  await engine.update(modelId, { id: newId })
} catch (e) {
  if (/already exists/.test(String(e))) {
    model.id = `${newId}-2` // pick alternate and retry
    await engine.update(modelId, { id: model.id })
  } else throw e
}

Prevention

When it happens

Trigger: Calling update() to rename a model to an id that is already taken; a previous failed update left the destination folder behind; two models with ids differing only by casing on a case-insensitive filesystem.

Common situations: User picks a new name colliding with another installed model; rename target equals the source (no-op rename); leftover folder from an interrupted prior rename/import.

Related errors


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