plandex-ai/plandex · error

%s is a built-in base model id, so it can't be used for a cu

Error message

%s is a built-in base model id, so it can't be used for a custom model

What it means

Custom model ids must not shadow built-in base model ids. The handler checks each custom model's ModelId against shared.BuiltInBaseModelsById; a collision is rejected with HTTP 422 (Unprocessable Entity) and a message naming the offending id. This protects role resolution, which looks ids up in the built-in table first.

Source

Thrown at app/server/handlers/models.go:87

			msg := "Provider name is required"
			log.Println(msg)
			http.Error(w, msg, http.StatusBadRequest)
			return
		}
	}

	for _, model := range modelsInput.CustomModels {
		if model.ModelId == "" {
			msg := "Model id is required"
			log.Println(msg)
			http.Error(w, msg, http.StatusBadRequest)
			return
		}

		if shared.BuiltInBaseModelsById[model.ModelId] != nil {
			msg := fmt.Sprintf("%s is a built-in base model id, so it can't be used for a custom model", model.ModelId)
			log.Println(msg)
			http.Error(w, msg, http.StatusUnprocessableEntity)
			return
		}
	}

	for _, modelPack := range modelsInput.CustomModelPacks {
		if modelPack.Name == "" {
			msg := "Model pack name is required"
			log.Println(msg)
			http.Error(w, msg, http.StatusBadRequest)
			return
		}

		if shared.BuiltInModelPacksByName[modelPack.Name] != nil {
			msg := fmt.Sprintf("%s is a built-in model pack name, so it can't be used for a custom model pack", modelPack.Name)
			log.Println(msg)
			http.Error(w, msg, http.StatusUnprocessableEntity)
			return
		}

View on GitHub (pinned to e2d772072e)

Solutions

  1. Rename the custom model to a distinct id that does not collide with built-in base model ids
  2. If you want the built-in model's behavior, reference the built-in id directly in your model pack instead of redefining it
  3. To change defaults, define a custom model with a new id and assign it to the desired roles
  4. After a Plandex upgrade, re-check custom ids against the new BuiltInBaseModelsById list

Example fix

// before
{"customModels": [{"modelId": "gpt-4o", "baseUrl": "https://proxy.example.com"}]}
// after
{"customModels": [{"modelId": "gpt-4o-proxy", "baseUrl": "https://proxy.example.com"}]}
Defensive patterns

Strategy: validation

Validate before calling

for _, m := range input.CustomModels {
    if shared.BuiltInBaseModelsById[m.ModelId] != nil {
        return fmt.Errorf("%s is a built-in base model id; choose a different custom id", m.ModelId)
    }
}

Type guard

func isBuiltInBaseModelId(id shared.ModelId) bool { return shared.BuiltInBaseModelsById[id] != nil }

Prevention

When it happens

Trigger: POSTing custom models where a modelId exactly matches a built-in base model id (e.g. "gpt-4o", "claude-3-5-sonnet-latest"), or renaming a custom model to a built-in id.

Common situations: Trying to override a built-in model's settings by reusing its id instead of creating a new id; a typo where a wrapper id was meant (e.g. "gpt-4o-custom"); upgrading Plandex and a previously-legal custom id becoming a new built-in id.

Related errors


AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05). Data as JSON: /api/errors/287da3567619b256. Report an issue: GitHub.