fix(cron): resolve per-job provider "custom" to providers.custom instead of codex

A cron job stored with `provider: "custom"` and a matching `providers.custom`
entry in config failed at execution with `auth_unavailable: providers=codex`.
Two layers conspired:

- `_get_named_custom_provider` returned None for bare "custom" *before*
  scanning config, so a literal `providers.custom` entry was never matched and
  resolution fell through to the global default (codex). Now it scans config
  for an entry literally named "custom"; with none it still returns None,
  preserving the legacy model.base_url trust path.
- `_resolve_model_override` blindly stripped bare "custom" at job creation and
  pinned `model.provider` (e.g. codex). It now keeps "custom" when a configured
  custom endpoint resolves, pinning the main provider only when it doesn't.
This commit is contained in:
xxxigm
2026-06-10 14:39:03 -07:00
committed by Teknium
parent 1e7316ced2
commit acd4f34e65
2 changed files with 44 additions and 10 deletions
+15 -7
View File
@@ -326,15 +326,23 @@ def _resolve_model_override(model_obj: Optional[Dict[str, Any]]) -> tuple:
return (None, None)
model_name = (model_obj.get("model") or "").strip() or None
provider_name = (model_obj.get("provider") or "").strip() or None
# Bare "custom" is an incomplete spec — the canonical form is
# "custom:<name>" matching a custom_providers entry. LLMs frequently
# Bare "custom" is usually an incomplete spec — the canonical form is
# "custom:<name>" matching a custom_providers entry, and LLMs frequently
# supply the bare type because the schema does not advertise the
# ":<name>" suffix, which used to bypass the pinning path below and
# leave the job stored with an unresolvable "custom" provider. Treat
# the bare value as "no provider supplied" so the current main
# provider gets pinned instead.
# ":<name>" suffix. It is only a problem when it can't resolve at runtime:
# a user may literally name a ``providers.custom`` (or custom_providers
# "custom") entry, in which case the job should keep ``provider="custom"``
# and run against that endpoint. Only when no such entry exists do we treat
# the bare value as "no provider supplied" and pin the current main
# provider below — otherwise pinning to ``model.provider`` (e.g. codex)
# silently hijacks a job that meant to use the configured custom endpoint.
if provider_name == "custom":
provider_name = None
try:
from hermes_cli.runtime_provider import has_named_custom_provider
if not has_named_custom_provider("custom"):
provider_name = None
except Exception:
provider_name = None
if model_name and not provider_name:
# Pin to the current main provider so the job is stable
try: