Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions scripts/gateway-pre-start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,70 @@ if isinstance(primary_model, str) and primary_model.lower() in (
model_defaults["primary"] = "llamacpp/gemma4-e2b-it-q4_0"
changed = True

# Model migration: legacy ChatGPT-subscription devices can have their active
# model — or a fallback — stored as `openai/<gpt>` from before the setup UI
# routed ChatGPT picks through Codex. On a device with ChatGPT (Codex OAuth)
# auth and NO OpenAI API key, that id resolves to api.openai.com, which 401s
# with "Missing bearer or basic authentication in header": either on the
# active turn, or — more often — only once the OAuth token first refreshes
# and the failover chain reaches the keyless `openai/*` fallback, which
# surfaces as a FailoverError days into use. The chat-model pick route already
# rewrites `openai/<gpt>` -> `codex/<gpt>`, but only when the user re-picks the
# model; existing configs never re-pick, so migrate primary + fallbacks here on
# gateway start. Mirrors CODEX_SUPPORTED_MODEL_RE / hasOpenAiApiKeyProfile /
# hasCodexOauthProfile in src/app/setup-api/chat/model/route.ts. Guarded on
# "codex OAuth present AND no OpenAI API key" so dual-auth / API-key boxes,
# where openai/* is a valid keyed route, are left untouched.
_CODEX_SUPPORTED = ("gpt-5.5", "gpt-5.4", "gpt-5.4-mini")

def _auth_profiles():
_auth = cfg.get("auth")
_profiles = _auth.get("profiles") if isinstance(_auth, dict) else None
return _profiles.values() if isinstance(_profiles, dict) else []

def _has_openai_api_key_profile():
for _entry in _auth_profiles():
if not isinstance(_entry, dict):
continue
_p = str(_entry.get("provider", "")).strip().lower()
_m = str(_entry.get("mode", "")).strip().lower()
if _p == "openai" and _m in ("token", "api_key", "api-key"):
return True
return False

def _has_codex_oauth_profile():
for _entry in _auth_profiles():
if not isinstance(_entry, dict):
continue
_p = str(_entry.get("provider", "")).strip().lower()
_m = str(_entry.get("mode", "")).strip().lower()
if _p == "codex" and _m == "oauth":
return True
Comment on lines +188 to +195

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Recognize legacy openai-codex OAuth profiles.

This only accepts provider == "codex", although this script still treats openai-codex as a legacy Codex provider. Those OAuth-only devices will skip this migration and retain the openai/<gpt> IDs that cause the reported 401.

Proposed fix
-        if _p == "codex" and _m == "oauth":
+        if _p in ("codex", "openai-codex") and _m == "oauth":
             return True
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def _has_codex_oauth_profile():
for _entry in _auth_profiles():
if not isinstance(_entry, dict):
continue
_p = str(_entry.get("provider", "")).strip().lower()
_m = str(_entry.get("mode", "")).strip().lower()
if _p == "codex" and _m == "oauth":
return True
def _has_codex_oauth_profile():
for _entry in _auth_profiles():
if not isinstance(_entry, dict):
continue
_p = str(_entry.get("provider", "")).strip().lower()
_m = str(_entry.get("mode", "")).strip().lower()
if _p in ("codex", "openai-codex") and _m == "oauth":
return True
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/gateway-pre-start.sh` around lines 188 - 195, Update
_has_codex_oauth_profile to recognize both “codex” and the legacy “openai-codex”
provider values when mode is “oauth”. Preserve the existing profile filtering
and normalization behavior so either provider triggers the migration.

return False

def _openai_gpt_to_codex(model_id):
# `openai/<codex-supported gpt>` -> `codex/<gpt>`; otherwise None (leave as-is).
if not isinstance(model_id, str):
return None
_m = model_id.strip()
if not _m.lower().startswith("openai/"):
return None
_bare = _m[len("openai/"):]
return "codex/" + _bare if _bare.lower() in _CODEX_SUPPORTED else None

if _has_codex_oauth_profile() and not _has_openai_api_key_profile():
_migrated_primary = _openai_gpt_to_codex(model_defaults.get("primary"))
if _migrated_primary:
model_defaults["primary"] = _migrated_primary
changed = True
_fallbacks = model_defaults.get("fallbacks")
if isinstance(_fallbacks, list):
for _i, _fb in enumerate(_fallbacks):
_migrated_fb = _openai_gpt_to_codex(_fb)
if _migrated_fb and _migrated_fb != _fallbacks[_i]:
_fallbacks[_i] = _migrated_fb
changed = True

# Strip orphaned per-model keys that a newer-than-pinned plugin wrote and a
# version downgrade left behind, which fail strict config validation and
# brick the AI provider page until `openclaw doctor --fix`. `agentRuntime`
Expand Down
Loading