Fix Codex edit dialog masking live OAuth during proxy takeover

The reported "OAuth access token disappears when enabling Codex proxy
takeover" was a display artifact, not data loss: auth.json on disk kept
the OAuth login the whole time. During takeover the edit dialog falls
back to the stored provider config (so it does not surface the proxy
placeholder), which for a third-party provider shows that provider's own
key instead of the live auth.json, making the OAuth token look gone.

Thread an isProxyTakeover flag from App through ProviderForm into the
Codex editor and show an explicit notice plus storage-aware auth/config
hints clarifying that the form displays the stored provider config while
the live config is temporarily managed by the proxy. Drop the
proxy-running condition so the notice shows whenever takeover is active,
even with the proxy stopped.

Add a regression test asserting the dialog does not read live settings
during takeover and renders the database config. i18n synced across
en/zh/ja/zh-TW.
This commit is contained in:
Jason
2026-05-31 21:00:50 +08:00
parent ce993baefa
commit a04e72a267
10 changed files with 98 additions and 3 deletions
@@ -247,6 +247,7 @@ export function EditProviderDialog({
onSubmittingChange={setIsFormSubmitting}
initialData={initialData}
showButtons={false}
isProxyTakeover={isProxyTakeover}
/>
</FullScreenPanel>
);
@@ -1,6 +1,9 @@
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { Info } from "lucide-react";
import { CodexAuthSection, CodexConfigSection } from "./CodexConfigSections";
import { CodexCommonConfigModal } from "./CodexCommonConfigModal";
import { Alert, AlertDescription } from "@/components/ui/alert";
interface CodexConfigEditorProps {
authValue: string;
@@ -11,6 +14,8 @@ interface CodexConfigEditorProps {
showRemoteCompaction?: boolean;
isProxyTakeover?: boolean;
onAuthChange: (value: string) => void;
onConfigChange: (value: string) => void;
@@ -43,6 +48,7 @@ const CodexConfigEditor: React.FC<CodexConfigEditorProps> = ({
configValue,
providerName,
showRemoteCompaction,
isProxyTakeover = false,
onAuthChange,
onConfigChange,
onAuthBlur,
@@ -57,6 +63,7 @@ const CodexConfigEditor: React.FC<CodexConfigEditorProps> = ({
onExtract,
isExtracting,
}) => {
const { t } = useTranslation();
const [isCommonConfigModalOpen, setIsCommonConfigModalOpen] = useState(false);
const handleCloseCommonConfigModal = () => {
@@ -66,12 +73,22 @@ const CodexConfigEditor: React.FC<CodexConfigEditorProps> = ({
return (
<div className="space-y-6">
{isProxyTakeover && (
<Alert>
<Info className="h-4 w-4" />
<AlertDescription>
{t("codexConfig.proxyTakeoverStorageNotice")}
</AlertDescription>
</Alert>
)}
{/* Auth JSON Section */}
<CodexAuthSection
value={authValue}
onChange={onAuthChange}
onBlur={onAuthBlur}
error={authError}
isProxyTakeover={isProxyTakeover}
/>
{/* Config TOML Section */}
@@ -85,6 +102,7 @@ const CodexConfigEditor: React.FC<CodexConfigEditorProps> = ({
onEditCommonConfig={() => setIsCommonConfigModalOpen(true)}
commonConfigError={commonConfigError}
configError={configError}
isProxyTakeover={isProxyTakeover}
/>
{/* Common Config Modal */}
@@ -29,6 +29,7 @@ interface CodexAuthSectionProps {
onChange: (value: string) => void;
onBlur?: () => void;
error?: string;
isProxyTakeover?: boolean;
}
/**
@@ -39,6 +40,7 @@ export const CodexAuthSection: React.FC<CodexAuthSectionProps> = ({
onChange,
onBlur,
error,
isProxyTakeover = false,
}) => {
const { t } = useTranslation();
const [isDarkMode, setIsDarkMode] = useState(false);
@@ -90,7 +92,11 @@ export const CodexAuthSection: React.FC<CodexAuthSectionProps> = ({
{!error && (
<p className="text-xs text-muted-foreground">
{t("codexConfig.authJsonHint")}
{t(
isProxyTakeover
? "codexConfig.authJsonStorageHint"
: "codexConfig.authJsonHint",
)}
</p>
)}
</div>
@@ -107,6 +113,7 @@ interface CodexConfigSectionProps {
onEditCommonConfig: () => void;
commonConfigError?: string;
configError?: string;
isProxyTakeover?: boolean;
}
/**
@@ -122,6 +129,7 @@ export const CodexConfigSection: React.FC<CodexConfigSectionProps> = ({
onEditCommonConfig,
commonConfigError,
configError,
isProxyTakeover = false,
}) => {
const { t } = useTranslation();
const [isDarkMode, setIsDarkMode] = useState(false);
@@ -367,7 +375,11 @@ export const CodexConfigSection: React.FC<CodexConfigSectionProps> = ({
{!configError && (
<p className="text-xs text-muted-foreground">
{t("codexConfig.configTomlHint")}
{t(
isProxyTakeover
? "codexConfig.configTomlStorageHint"
: "codexConfig.configTomlHint",
)}
</p>
)}
</div>
@@ -230,6 +230,7 @@ export interface ProviderFormProps {
iconColor?: string;
};
showButtons?: boolean;
isProxyTakeover?: boolean;
}
export function ProviderForm(props: ProviderFormProps) {
@@ -251,6 +252,7 @@ function ProviderFormFull({
onSubmittingChange,
initialData,
showButtons = true,
isProxyTakeover = false,
}: ProviderFormProps) {
if (appId === "claude-desktop") {
throw new Error("ProviderFormFull should not receive claude-desktop");
@@ -2165,6 +2167,7 @@ function ProviderFormFull({
configValue={codexConfig}
providerName={form.watch("name")}
showRemoteCompaction={category !== "official"}
isProxyTakeover={isProxyTakeover}
onAuthChange={setCodexAuth}
onConfigChange={handleCodexConfigChange}
useCommonConfig={useCodexCommonConfigFlag}