fix(oauth): expose manual Codex callback entry (#9944)

Merged after batch validation on a combined worktree cut from `release/v3.8.51` with #9908 and #7138.

**Evidence**
- Focused tests: 36/36 pass on the combined tree (`oauth-modal-codex-lan-ip-8046`, `antigravity-image-credential-retry`, `antigravity-usage-service`, `generic-quota-fetcher`), including the 2 pre-existing anchor tests that assert `codex` stays in `PKCE_CALLBACK_SERVER_PROVIDERS` and that the `localhost:1455` redirect URI is untouched.
- Gates on the combined tree: `check-complexity` PASS, `check-cognitive-complexity` PASS, `typecheck:core` PASS, `check-changelog-integrity` PASS.
- `check-file-size` is red, but reproduces byte-identical on the pure `release/v3.8.51` tip (`imageGeneration.ts`, `roundRobinCombo.ts`, `stream.ts`) — inherited base-red, not from this PR.

**Reconciled**
- `changelog.d/fixes/codex-manual-loopback-action.md` did not start with a markdown bullet, which is the one thing `check-changelog-integrity` failed on. Fixed in your branch (d3dbb5b) so the fragment convention holds; nothing else in your diff was touched.

Thanks for this one, @Ardem2025 — exposing the manual callback entry that already existed in the code instead of adding a new flow is exactly the right shape for the LAN/remote case, and keeping every PKCE/state check untouched made it easy to verify.
This commit is contained in:
Dmitry Kuznetsov
2026-09-17 01:59:25 +03:00
committed by GitHub
parent f1e7148c19
commit 2f0a01d75c
4 changed files with 35 additions and 6 deletions

View File

@@ -374,11 +374,10 @@ export default function OAuthModal({
[provider, onSuccess, reauthConnection, t]
);
// Start OAuth flow. `opts.grokBrowser` lets the grok-cli method tabs force a
// specific branch synchronously (avoids reading a just-set state value through
// a stale closure); when omitted, falls back to the grokBrowserMode state.
// Start OAuth flow. Options let method buttons select a branch synchronously
// instead of reading a just-set state value through a stale closure.
const startOAuthFlow = useCallback(
async (opts?: { grokBrowser?: boolean }) => {
async (opts?: { grokBrowser?: boolean; manualLoopback?: boolean }) => {
if (!provider) return;
try {
setError(null);
@@ -533,7 +532,7 @@ export default function OAuthModal({
setPolling(false);
forceManual = true;
}
} else if (isLocalhost) {
} else if (isLocalhost && !opts?.manualLoopback) {
setLoopbackHint(buildPkceLoopbackMismatchHint(provider, loopbackLocation));
setStep("loopback-mismatch");
return;
@@ -1160,6 +1159,7 @@ export default function OAuthModal({
<OAuthLoopbackMismatchPanel
providerName={providerInfo.name}
hint={loopbackHint}
onManualInput={() => void startOAuthFlow({ manualLoopback: true })}
onClose={handleClose}
/>
)}

View File

@@ -89,6 +89,7 @@ export function OAuthDeviceCodePanel({
type OAuthLoopbackMismatchPanelProps = {
providerName: string;
hint: PkceLoopbackMismatchHint;
onManualInput: () => void;
onClose: () => void;
};
@@ -103,6 +104,7 @@ type OAuthLoopbackMismatchPanelProps = {
export function OAuthLoopbackMismatchPanel({
providerName,
hint,
onManualInput,
onClose,
}: OAuthLoopbackMismatchPanelProps) {
const t = useTranslations("oauthModal");
@@ -194,6 +196,9 @@ export function OAuthLoopbackMismatchPanel({
<p className="text-xs text-text-muted">{t("loopbackMismatchAlternative")}</p>
</div>
<div className="flex gap-2">
<Button onClick={onManualInput} fullWidth>
{t("popupBlocked")}
</Button>
<Button onClick={onClose} variant="secondary" fullWidth>
{t("cancel")}
</Button>