fix(codex): pin shadowed config provider on resume (#4818)

## Related issue

N/A — reported and reproduced locally.

## Summary

- Pin Codex's detected `config.toml` provider when an explicit, non-default same-name Omnigent entry shadows ambient default synthesis.
- Resolve the provider once during native launch so rollout metadata, app-server, and remote TUI use the same immutable selection.
- Preserve spec, explicit-default, global-auth, subscription, and dismissed-provider precedence.

ELI5: if Codex is configured to use a gateway but Omnigent's matching provider entry is not marked default, a resumed conversation now follows Codex's actual gateway instead of falling back to unauthenticated OpenAI.

```text
Codex config detection ──► resolved native launch ──► resume rollout/TUI
      Databricks                  Databricks                 Databricks
```

## Test Plan

- `uv run --frozen pytest tests/test_native_codex_provider.py -k 'config_provider_shadowed_by_nondefault_explicit_entry_still_pins or shadowed_config_detection_uses_active_profile_provider or resolve_native_codex_launch_undismissed_config_provider_routes_via_pin or resolve_native_codex_launch_dismissed_config_provider_pins_openai'`
- `uv run --frozen pytest tests/test_codex_native.py -k 'resolve_native_codex_launch_no_provider_sets_login_fallback_summary or resolve_native_codex_launch_databricks_provider_sets_summary'`
- `uv run --frozen ruff check omnigent/codex_native_app_server.py tests/test_native_codex_provider.py tests/test_codex_native.py`
- `uv run --frozen ruff format --check omnigent/codex_native_app_server.py tests/test_native_codex_provider.py tests/test_codex_native.py`
- `git diff --check`

## Demo

N/A — non-visual backend fix.

## Type of change

- [x] Bug fix
- [ ] Feature
- [ ] UI / frontend change
- [ ] Refactor / chore
- [ ] Docs
- [ ] Test / CI
- [ ] Breaking change

## Test coverage

- [x] Unit tests added / updated
- [ ] Integration tests added / updated
- [ ] E2E tests added / updated
- [ ] Manual verification completed
- [x] Existing tests cover this change
- [ ] Not applicable

## Coverage notes

The new tests reproduce the shadowed non-default provider state and verify active Codex profile selection. Existing tests cover dismissed providers, ordinary detected providers, explicit defaults, and no-provider summaries.

## Changelog

Resumed Codex conversations now keep using the provider selected in Codex configuration.

Signed-off-by: Zeyi (Rice) Fan <zeyi.f@databricks.com>
This commit is contained in:
Zeyi (Rice) Fan
2026-08-14 15:17:47 -07:00
committed by GitHub
parent 73abf26b8e
commit 6c2daae4a9
3 changed files with 107 additions and 8 deletions
+33 -4
View File
@@ -2283,8 +2283,9 @@ def resolve_native_codex_launch(
config (issue #2744 — parity with the in-process codex harness).
:returns: The resolved :class:`NativeCodexLaunch`.
"""
from omnigent.onboarding.ambient import codex_config_detection
from omnigent.onboarding.detected import (
codex_config_provider_dismissed,
dismissed_detection_names,
effective_config_with_detected,
)
from omnigent.onboarding.provider_config import (
@@ -2296,14 +2297,17 @@ def resolve_native_codex_launch(
from omnigent.spec.types import DatabricksAuth
explicit = load_config()
config_detection = codex_config_detection()
config_provider_dismissed = (
config_detection is not None
and config_detection.name in dismissed_detection_names(explicit)
)
# When the launch ends up on codex's own login with NO provider routing,
# the bridged config.toml's custom default model_provider would still
# apply — including one the user explicitly Removed (dismissed). Pin
# codex's built-in provider in that case so the dismissal holds at run
# time. An undetectable/undismissed custom provider keeps its routing.
no_provider_overrides = (
['model_provider="openai"'] if codex_config_provider_dismissed(explicit) else []
)
no_provider_overrides = ['model_provider="openai"'] if config_provider_dismissed else []
if spec is not None and (
spec.executor.auth is not None
or spec.executor.profile
@@ -2384,6 +2388,31 @@ def resolve_native_codex_launch(
)
entry = default_provider_for_harness(effective_config_with_detected(explicit), "codex")
if (
entry is None
and config_detection is not None
and config_detection.model_provider is not None
and not config_provider_dismissed
):
# An adopted cli-config entry can explicitly shadow the same ambient
# detection without being marked the Omnigent default. Codex still
# selects that provider from config.toml, so pin the already-resolved
# detection instead of describing this as an OpenAI-login launch.
# This keeps rollout metadata, app-server, and remote TUI routing on
# one immutable provider selection during cold resume.
provider_id = config_detection.model_provider
_logger.info(
"native-codex routing: config.toml provider %r (ambient fallback, model=%s)",
provider_id,
model,
)
return NativeCodexLaunch(
config_overrides=[f"model_provider={json.dumps(provider_id)}"],
model=model,
profile=None,
summary=f"Codex config.toml provider {provider_id!r} (ambient fallback)",
)
if entry is None:
_logger.info(
"native-codex routing: Codex CLI login (no provider configured for the Codex "
+6 -4
View File
@@ -10272,11 +10272,12 @@ def test_resolve_native_codex_launch_no_provider_sets_login_fallback_summary(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""No configured provider -> summary names the login fallback (#2745)."""
from omnigent.onboarding import detected, provider_config
from omnigent.onboarding import ambient, detected, provider_config
from omnigent.runtime import workflow
monkeypatch.setattr(provider_config, "load_config", dict)
monkeypatch.setattr(detected, "codex_config_provider_dismissed", lambda cfg: False)
monkeypatch.setattr(ambient, "codex_config_detection", lambda: None)
monkeypatch.setattr(detected, "dismissed_detection_names", lambda cfg: frozenset())
monkeypatch.setattr(detected, "effective_config_with_detected", lambda cfg: {})
monkeypatch.setattr(provider_config, "default_provider_for_harness", lambda cfg, harness: None)
monkeypatch.setattr(workflow, "_load_global_auth", lambda: None)
@@ -10292,11 +10293,12 @@ def test_resolve_native_codex_launch_databricks_provider_sets_summary(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""A Databricks provider default -> summary names the ucode profile (#2745)."""
from omnigent.onboarding import detected, provider_config
from omnigent.onboarding import ambient, detected, provider_config
entry = SimpleNamespace(kind=provider_config.DATABRICKS_KIND, profile="my-profile")
monkeypatch.setattr(provider_config, "load_config", dict)
monkeypatch.setattr(detected, "codex_config_provider_dismissed", lambda cfg: False)
monkeypatch.setattr(ambient, "codex_config_detection", lambda: None)
monkeypatch.setattr(detected, "dismissed_detection_names", lambda cfg: frozenset())
monkeypatch.setattr(
provider_config, "default_provider_for_harness", lambda cfg, harness: entry
)
+68
View File
@@ -440,6 +440,74 @@ def test_resolve_native_codex_launch_undismissed_config_provider_routes_via_pin(
assert launch.profile is None
def test_config_provider_shadowed_by_nondefault_explicit_entry_still_pins(
_isolated: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""A nondefault adopted entry cannot hide Codex's active config provider.
The explicit entry shadows ambient synthesis by name, but Codex itself
still selects the provider from config.toml. An empty launch would make a
synthesized resume rollout record OpenAI and lose this provider's auth.
"""
monkeypatch.setattr("omnigent.onboarding.ambient._ollama_reachable", lambda: False)
codex_dir = _isolated / ".codex"
codex_dir.mkdir()
(codex_dir / "config.toml").write_text(_DISMISSIBLE_CODEX_CONFIG)
_seed(
_isolated,
{
"codex-databricks": {
"kind": "cli-config",
"cli": "codex",
"model_provider": "Databricks",
"display_name": "Databricks AI Gateway",
}
},
)
launch = resolve_native_codex_launch(model="test-model")
assert launch.config_overrides == ['model_provider="Databricks"']
assert launch.model == "test-model"
assert launch.profile is None
def test_shadowed_config_detection_uses_active_profile_provider(
_isolated: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""The fallback pins the provider selected by Codex's active profile."""
monkeypatch.setattr("omnigent.onboarding.ambient._ollama_reachable", lambda: False)
codex_dir = _isolated / ".codex"
codex_dir.mkdir()
(codex_dir / "config.toml").write_text(
'profile = "work"\n'
'model_provider = "UnusedTopLevel"\n'
"[profiles.work]\n"
'model_provider = "Databricks"\n'
"[model_providers.Databricks]\n"
'name = "Databricks AI Gateway"\n'
'base_url = "https://example.ai-gateway.cloud.databricks.com/codex/v1"\n'
"[model_providers.Databricks.auth]\n"
'command = "jq"\n'
)
_seed(
_isolated,
{
"codex-databricks": {
"kind": "cli-config",
"cli": "codex",
"model_provider": "Databricks",
"display_name": "Databricks AI Gateway",
}
},
)
launch = resolve_native_codex_launch(model=None)
assert launch.config_overrides == ['model_provider="Databricks"']
assert launch.profile is None
# ── Spec-level credentials (issue #2744) ────────────────────────────────────