Repository navigation
fix(oauth): actionable guidance for LAN-origin loopback mismatches (codex + Antigravity) - #8463
Merged
Merged
Conversation
…anel #8046 already stops the doomed login when a PKCE_CALLBACK_SERVER_PROVIDERS provider (codex / xai-oauth / grok-cli) is connected from a LAN IP, but it explained itself as one long English sentence rendered in the generic red "Connection failed" step. Two concrete problems with that: - the operator had to parse prose to work out WHICH ports to forward, and the command shipped with `<port>` / `<omniroute-host>` placeholders to resolve by hand; - it forwarded a single port. Both are required: the dashboard port is what makes the origin true-localhost (a LAN origin never reaches the callback-server branch at all), and the provider's fixed callback port is where the browser is actually sent back to. Forwarding either one alone still fails. buildPkceLoopbackMismatchHint() now returns the diagnosis as structured data with the detected host and both ports already filled in, and a dedicated OAuthLoopbackMismatchPanel renders it as: what happened -> how to fix, in three numbered steps with copy-to-clipboard fields. No "Try again" button — retrying the same origin fails identically. The panel yields to the paste-token tab so grok-cli (which is in both provider sets) never stacks the two views. The flat warning string stays exported for non-UI callers. docs: REMOTE-MODE.md gains a "Connecting Codex / Grok on a remote install" section with the fixed-callback table and the two-port tunnel, mirroring the existing Antigravity section. i18n: 9 new oauthModal keys, hand-written for en + pt-BR and propagated to the remaining 40 locales as `__MISSING__:` sentinels (runtime falls back to the clean English value per #7258).
…e stale i18n copy Same LAN-origin family as the codex fix in this branch, different mechanism and a worse failure mode. Google providers (antigravity / agy) have no fixed foreign port: OAuthModal builds `http://127.0.0.1:<dashboardPort>/callback`. On a LAN origin that 127.0.0.1 is the BROWSER's machine, and Google's firstparty/nativeapp consent only releases the code once the loopback is reachable from the approving browser. When it is not, the consent never redirects at all — it hangs. So unlike an ordinary provider there is no error page and no callback URL in the address bar. That made the existing copy actively wrong. `googleOAuthWarning` was corrected when the login helper shipped (#5203), but a changed English value does not invalidate existing translations and `i18n:sync-ui` only fills keys that are ABSENT, never ones that are STALE — so 39 of 43 locales (pt-BR, pt, es, de, fr, ja, zh-CN, …) kept the original "wait for the redirect, copy the full URL and paste it below", instructing a flow that cannot complete. The drift gate that should have caught this is a no-op: `check-translation-drift.mjs` needs `.i18n-state.json`, which is not in the repo, and it runs `--warn`. Because the key's MEANING changed, it is renamed rather than edited — a new key cannot inherit a stale translation. `googleOAuthWarning` is removed from all 43 locales and replaced by 7 `googleLoopback*` keys, hand-written for en + pt-BR and marked `__MISSING__:` elsewhere so the runtime falls back to correct English (#7258). UI: `OAuthGoogleLoopbackNotice` states what is happening and surfaces both real remedies with the detected host and port filled in — the local login helper (recommended; its blob is what the Step 2 field accepts) and a single-port SSH forward. It also REPLACES `remoteAccessInfo` for this family instead of stacking on top of it: that notice promises an error page whose URL you copy, true for ordinary providers and false here. `agy` deliberately gets no helper command. bin/cli/commands/login.mjs pins PROVIDER = "antigravity" and parsePastedCredentials() rejects a blob whose embedded provider does not match the route provider, so advertising the helper there would send the operator to a blob guaranteed to be refused. It keeps the tunnel path. Refactor: the shared `resolveDashboardPort` / `buildSshLocalForward` helpers move to `loopbackTunnel.ts`, used by both hint builders. The codex builder's behaviour is unchanged (its 11 tests still pass untouched). docs: REMOTE-MODE.md notes that the dashboard now surfaces the remedies, states that one forward is enough for Antigravity (contrasting the two-port codex case), and aligns Option B's command on 127.0.0.1 to match what the UI generates.
diegosouzapw
added a commit
that referenced
this pull request
Jul 26, 2026
… cap #8463 renamed `oauthModal.googleOAuthWarning` away but left the old key behind in zh-TW, so the "the stale googleOAuthWarning key is GONE from every locale" guard fails on the branch. Removed it. The auth.ts frozen line cap goes 2486 -> 2492. Restoring the dropped exactCooldownIsUpstreamReset call site costs 7 lines, and staging the file makes lint-staged reformat four pre-existing over-100-column lines to prettier's rule — unavoidable without bypassing the hook, which hard rule #10 forbids. The file still sits 12 lines below where the cap was set relative to its actual size.
diegosouzapw
added a commit
that referenced
this pull request
Jul 26, 2026
* fix: repair five base-red failures on release/v3.8.49 Every PR cut from this branch fails CI on the branch's own breakage. Five distinct causes, none introduced by the PRs that trip over them: 1. dast-smoke / Turbopack build — src/sse/handlers/chat.ts imported PROVIDER_BREAKER_FAILURE_STATUSES twice in one statement. A duplicate import specifier is an ECMAScript syntax error, so the production build never compiled. Introduced by #8258, whose export fix landed on top of an import that already existed. 2. Unit Tests — the #8393 verified-cooldown bypass was renamed exactCooldownVerified -> exactCooldownIsUpstreamReset during the #8254 conflict resolution, which also dropped the flag at the markAccountUnavailable call site entirely. The rename left the test passing the old key (so the flag was silently ignored and a verified upstream reset got clamped back to maxCooldownMs), and the dropped call site meant no real caller set it at all. Align the test on the surviving name, restore the call site, and restore the doc comment explaining #6863 vs #7940. 3. Unit Tests — #8526 added four common.* keys to en.json only, breaking the strict key-parity tests for pt-BR and vi. Translated into all 42 locales. 4. Unit Tests — vi carried 17 __MISSING__ placeholders from #8354 and #8463, and vi is the one locale with a no-placeholder test. Translated. 5. No new ESLint warnings — four suppressed `any`s in tests/unit/combo-routing-engine.test.ts no longer exist, and ESLint exits 2 on stale suppressions. Pruned (271 -> 267); no other entry moved. Also regenerates skills/cli-backup-sync/SKILL.md, which still documented the `backup status` flags #8512 removed — the merge-integrity gate compares the generated output against the tree. Not fixed here: the env/docs contract (NEXT_PUBLIC_OMNIROUTE_BASE_PATH and OMNIROUTE_BACKUP_SCHEDULE_JOB_INTERVAL_MS missing from .env.example), which #8690 already covers, and the quality baselines, which #8686 covers. * fix(quality): keep prettier off the generated SKILL.md files check:agent-skills-sync diffs the generator's output against the tree byte for byte, but lint-staged runs prettier over any staged *.md — and prettier inserts a blank line after the frontmatter that the generator does not emit. Committing a regenerated skill therefore made the gate fail again on the very file that was just brought back in sync. The 44 untouched skills only escape this because they never pass through lint-staged. The generator is the formatter of record for these files, so ignore them. * fix(i18n,quality): drop the stale zh-TW key; raise the auth.ts frozen cap #8463 renamed `oauthModal.googleOAuthWarning` away but left the old key behind in zh-TW, so the "the stale googleOAuthWarning key is GONE from every locale" guard fails on the branch. Removed it. The auth.ts frozen line cap goes 2486 -> 2492. Restoring the dropped exactCooldownIsUpstreamReset call site costs 7 lines, and staging the file makes lint-staged reformat four pre-existing over-100-column lines to prettier's rule — unavoidable without bypassing the hook, which hard rule #10 forbids. The file still sits 12 lines below where the cap was set relative to its actual size.
diegosouzapw
added a commit
that referenced
this pull request
Jul 27, 2026
…ten (#8574) Closes the gap that let #8463 ship. `oauthModal.googleOAuthWarning`'s English value was rewritten when the Antigravity login helper landed (#5203); 39 of 43 locales kept a translation of the PREVIOUS English, which told operators to "copy the full URL and paste it below" — a flow that cannot complete for that provider family. Non-English users read confident, wrong instructions for months and no gate noticed. None of the three existing gates can see this class: - `sync-ui-keys.mjs` only backfills keys that are ABSENT, never ones that are STALE; - `check-ui-keys-coverage.mjs` counts key PRESENCE, so a stale translation scores as fully covered (all 43 locales sat at 99.6% throughout); - `check-translation-drift.mjs` tracks the `docs/i18n/<locale>/**.md` documentation mirrors — it never reads `src/i18n/messages/*.json` at all. (Its `.i18n-state.json` is also absent, so it self-skips, but bootstrapping it would not have helped: wrong surface.) New gate `scripts/i18n/check-ui-value-drift.mjs` is DIFF-AWARE rather than baseline-backed: it compares `en.json` at the merge base against the working tree, and for every key whose English value changed, reports any locale still holding an untouched translation. That choice deliberately freezes pre-existing debt — a diff cannot reveal which old English a long-standing translation came from, so the gate judges only what the current change touches, and unrelated PRs never pay for historical drift. The alternative, a per-key hash baseline over 11207 keys, would have cost a ~600 KB generated file (3x the largest existing baseline) churning on every i18n PR. Two ways to satisfy it: refresh the translations, or set them to `__MISSING__:<new english>` so the runtime serves the corrected English (#7258) while the key queues for translation. When the string's MEANING changes, renaming the key is better still — a new key cannot inherit a stale translation, which is what #8463 did. Wired blocking into the `i18n-ui-coverage` job (the `i18n` job is `continue-on-error: true`, so a gate there could not block anything). That job gains `fetch-depth: 0` because the gate needs the base ref; without it the gate self-skips with `base-unresolved`, mirroring `check-openapi-breaking`. `BASE_REF` is passed via `env:` and reaches git only through `execFileSync` argv — never a shell string. Verified against the real defect: rewriting an English value with translations left behind reports exactly 39 stale locales and exits 1; `--warn` exits 0; an unresolvable base exits 0 with `SKIP reason=base-unresolved`. Co-authored-by: ikelvingo <im.kelvinwong@gmail.com>
HouMinXi
pushed a commit
to HouMinXi/OmniRoute
that referenced
this pull request
Aug 2, 2026
…odex + Antigravity) (cheaper-inference#8463) * fix(oauth): explain the LAN-IP loopback mismatch with an actionable panel cheaper-inference#8046 already stops the doomed login when a PKCE_CALLBACK_SERVER_PROVIDERS provider (codex / xai-oauth / grok-cli) is connected from a LAN IP, but it explained itself as one long English sentence rendered in the generic red "Connection failed" step. Two concrete problems with that: - the operator had to parse prose to work out WHICH ports to forward, and the command shipped with `<port>` / `<omniroute-host>` placeholders to resolve by hand; - it forwarded a single port. Both are required: the dashboard port is what makes the origin true-localhost (a LAN origin never reaches the callback-server branch at all), and the provider's fixed callback port is where the browser is actually sent back to. Forwarding either one alone still fails. buildPkceLoopbackMismatchHint() now returns the diagnosis as structured data with the detected host and both ports already filled in, and a dedicated OAuthLoopbackMismatchPanel renders it as: what happened -> how to fix, in three numbered steps with copy-to-clipboard fields. No "Try again" button — retrying the same origin fails identically. The panel yields to the paste-token tab so grok-cli (which is in both provider sets) never stacks the two views. The flat warning string stays exported for non-UI callers. docs: REMOTE-MODE.md gains a "Connecting Codex / Grok on a remote install" section with the fixed-callback table and the two-port tunnel, mirroring the existing Antigravity section. i18n: 9 new oauthModal keys, hand-written for en + pt-BR and propagated to the remaining 40 locales as `__MISSING__:` sentinels (runtime falls back to the clean English value per cheaper-inference#7258). * fix(oauth): correct the Antigravity remote-login guidance and drop the stale i18n copy Same LAN-origin family as the codex fix in this branch, different mechanism and a worse failure mode. Google providers (antigravity / agy) have no fixed foreign port: OAuthModal builds `http://127.0.0.1:<dashboardPort>/callback`. On a LAN origin that 127.0.0.1 is the BROWSER's machine, and Google's firstparty/nativeapp consent only releases the code once the loopback is reachable from the approving browser. When it is not, the consent never redirects at all — it hangs. So unlike an ordinary provider there is no error page and no callback URL in the address bar. That made the existing copy actively wrong. `googleOAuthWarning` was corrected when the login helper shipped (cheaper-inference#5203), but a changed English value does not invalidate existing translations and `i18n:sync-ui` only fills keys that are ABSENT, never ones that are STALE — so 39 of 43 locales (pt-BR, pt, es, de, fr, ja, zh-CN, …) kept the original "wait for the redirect, copy the full URL and paste it below", instructing a flow that cannot complete. The drift gate that should have caught this is a no-op: `check-translation-drift.mjs` needs `.i18n-state.json`, which is not in the repo, and it runs `--warn`. Because the key's MEANING changed, it is renamed rather than edited — a new key cannot inherit a stale translation. `googleOAuthWarning` is removed from all 43 locales and replaced by 7 `googleLoopback*` keys, hand-written for en + pt-BR and marked `__MISSING__:` elsewhere so the runtime falls back to correct English (cheaper-inference#7258). UI: `OAuthGoogleLoopbackNotice` states what is happening and surfaces both real remedies with the detected host and port filled in — the local login helper (recommended; its blob is what the Step 2 field accepts) and a single-port SSH forward. It also REPLACES `remoteAccessInfo` for this family instead of stacking on top of it: that notice promises an error page whose URL you copy, true for ordinary providers and false here. `agy` deliberately gets no helper command. bin/cli/commands/login.mjs pins PROVIDER = "antigravity" and parsePastedCredentials() rejects a blob whose embedded provider does not match the route provider, so advertising the helper there would send the operator to a blob guaranteed to be refused. It keeps the tunnel path. Refactor: the shared `resolveDashboardPort` / `buildSshLocalForward` helpers move to `loopbackTunnel.ts`, used by both hint builders. The codex builder's behaviour is unchanged (its 11 tests still pass untouched). docs: REMOTE-MODE.md notes that the dashboard now surfaces the remedies, states that one forward is enough for Antigravity (contrasting the two-port codex case), and aligns Option B's command on 127.0.0.1 to match what the UI generates. --------- Co-authored-by: ikelvingo <im.kelvinwong@gmail.com>
HouMinXi
pushed a commit
to HouMinXi/OmniRoute
that referenced
this pull request
Aug 2, 2026
…nce#8706) * fix: repair five base-red failures on release/v3.8.49 Every PR cut from this branch fails CI on the branch's own breakage. Five distinct causes, none introduced by the PRs that trip over them: 1. dast-smoke / Turbopack build — src/sse/handlers/chat.ts imported PROVIDER_BREAKER_FAILURE_STATUSES twice in one statement. A duplicate import specifier is an ECMAScript syntax error, so the production build never compiled. Introduced by cheaper-inference#8258, whose export fix landed on top of an import that already existed. 2. Unit Tests — the cheaper-inference#8393 verified-cooldown bypass was renamed exactCooldownVerified -> exactCooldownIsUpstreamReset during the cheaper-inference#8254 conflict resolution, which also dropped the flag at the markAccountUnavailable call site entirely. The rename left the test passing the old key (so the flag was silently ignored and a verified upstream reset got clamped back to maxCooldownMs), and the dropped call site meant no real caller set it at all. Align the test on the surviving name, restore the call site, and restore the doc comment explaining cheaper-inference#6863 vs cheaper-inference#7940. 3. Unit Tests — cheaper-inference#8526 added four common.* keys to en.json only, breaking the strict key-parity tests for pt-BR and vi. Translated into all 42 locales. 4. Unit Tests — vi carried 17 __MISSING__ placeholders from cheaper-inference#8354 and cheaper-inference#8463, and vi is the one locale with a no-placeholder test. Translated. 5. No new ESLint warnings — four suppressed `any`s in tests/unit/combo-routing-engine.test.ts no longer exist, and ESLint exits 2 on stale suppressions. Pruned (271 -> 267); no other entry moved. Also regenerates skills/cli-backup-sync/SKILL.md, which still documented the `backup status` flags cheaper-inference#8512 removed — the merge-integrity gate compares the generated output against the tree. Not fixed here: the env/docs contract (NEXT_PUBLIC_OMNIROUTE_BASE_PATH and OMNIROUTE_BACKUP_SCHEDULE_JOB_INTERVAL_MS missing from .env.example), which cheaper-inference#8690 already covers, and the quality baselines, which cheaper-inference#8686 covers. * fix(quality): keep prettier off the generated SKILL.md files check:agent-skills-sync diffs the generator's output against the tree byte for byte, but lint-staged runs prettier over any staged *.md — and prettier inserts a blank line after the frontmatter that the generator does not emit. Committing a regenerated skill therefore made the gate fail again on the very file that was just brought back in sync. The 44 untouched skills only escape this because they never pass through lint-staged. The generator is the formatter of record for these files, so ignore them. * fix(i18n,quality): drop the stale zh-TW key; raise the auth.ts frozen cap cheaper-inference#8463 renamed `oauthModal.googleOAuthWarning` away but left the old key behind in zh-TW, so the "the stale googleOAuthWarning key is GONE from every locale" guard fails on the branch. Removed it. The auth.ts frozen line cap goes 2486 -> 2492. Restoring the dropped exactCooldownIsUpstreamReset call site costs 7 lines, and staging the file makes lint-staged reformat four pre-existing over-100-column lines to prettier's rule — unavoidable without bypassing the hook, which hard rule cheaper-inference#10 forbids. The file still sits 12 lines below where the cap was set relative to its actual size.
HouMinXi
pushed a commit
to HouMinXi/OmniRoute
that referenced
this pull request
Aug 2, 2026
…ten (cheaper-inference#8574) Closes the gap that let cheaper-inference#8463 ship. `oauthModal.googleOAuthWarning`'s English value was rewritten when the Antigravity login helper landed (cheaper-inference#5203); 39 of 43 locales kept a translation of the PREVIOUS English, which told operators to "copy the full URL and paste it below" — a flow that cannot complete for that provider family. Non-English users read confident, wrong instructions for months and no gate noticed. None of the three existing gates can see this class: - `sync-ui-keys.mjs` only backfills keys that are ABSENT, never ones that are STALE; - `check-ui-keys-coverage.mjs` counts key PRESENCE, so a stale translation scores as fully covered (all 43 locales sat at 99.6% throughout); - `check-translation-drift.mjs` tracks the `docs/i18n/<locale>/**.md` documentation mirrors — it never reads `src/i18n/messages/*.json` at all. (Its `.i18n-state.json` is also absent, so it self-skips, but bootstrapping it would not have helped: wrong surface.) New gate `scripts/i18n/check-ui-value-drift.mjs` is DIFF-AWARE rather than baseline-backed: it compares `en.json` at the merge base against the working tree, and for every key whose English value changed, reports any locale still holding an untouched translation. That choice deliberately freezes pre-existing debt — a diff cannot reveal which old English a long-standing translation came from, so the gate judges only what the current change touches, and unrelated PRs never pay for historical drift. The alternative, a per-key hash baseline over 11207 keys, would have cost a ~600 KB generated file (3x the largest existing baseline) churning on every i18n PR. Two ways to satisfy it: refresh the translations, or set them to `__MISSING__:<new english>` so the runtime serves the corrected English (cheaper-inference#7258) while the key queues for translation. When the string's MEANING changes, renaming the key is better still — a new key cannot inherit a stale translation, which is what cheaper-inference#8463 did. Wired blocking into the `i18n-ui-coverage` job (the `i18n` job is `continue-on-error: true`, so a gate there could not block anything). That job gains `fetch-depth: 0` because the gate needs the base ref; without it the gate self-skips with `base-unresolved`, mirroring `check-openapi-breaking`. `BASE_REF` is passed via `env:` and reaches git only through `execFileSync` argv — never a shell string. Verified against the real defect: rewriting an English value with translations left behind reports exactly 39 stale locales and exits 1; `--warn` exits 0; an unresolvable base exits 0 with `SKIP reason=base-unresolved`. Co-authored-by: ikelvingo <im.kelvinwong@gmail.com>
5 tasks done
muhamadgalihsaputra
pushed a commit
to niyatna/NiyatnaRoute
that referenced
this pull request
Sep 27, 2026
…odex + Antigravity) (cheaper-inference#8463) * fix(oauth): explain the LAN-IP loopback mismatch with an actionable panel cheaper-inference#8046 already stops the doomed login when a PKCE_CALLBACK_SERVER_PROVIDERS provider (codex / xai-oauth / grok-cli) is connected from a LAN IP, but it explained itself as one long English sentence rendered in the generic red "Connection failed" step. Two concrete problems with that: - the operator had to parse prose to work out WHICH ports to forward, and the command shipped with `<port>` / `<omniroute-host>` placeholders to resolve by hand; - it forwarded a single port. Both are required: the dashboard port is what makes the origin true-localhost (a LAN origin never reaches the callback-server branch at all), and the provider's fixed callback port is where the browser is actually sent back to. Forwarding either one alone still fails. buildPkceLoopbackMismatchHint() now returns the diagnosis as structured data with the detected host and both ports already filled in, and a dedicated OAuthLoopbackMismatchPanel renders it as: what happened -> how to fix, in three numbered steps with copy-to-clipboard fields. No "Try again" button — retrying the same origin fails identically. The panel yields to the paste-token tab so grok-cli (which is in both provider sets) never stacks the two views. The flat warning string stays exported for non-UI callers. docs: REMOTE-MODE.md gains a "Connecting Codex / Grok on a remote install" section with the fixed-callback table and the two-port tunnel, mirroring the existing Antigravity section. i18n: 9 new oauthModal keys, hand-written for en + pt-BR and propagated to the remaining 40 locales as `__MISSING__:` sentinels (runtime falls back to the clean English value per cheaper-inference#7258). * fix(oauth): correct the Antigravity remote-login guidance and drop the stale i18n copy Same LAN-origin family as the codex fix in this branch, different mechanism and a worse failure mode. Google providers (antigravity / agy) have no fixed foreign port: OAuthModal builds `http://127.0.0.1:<dashboardPort>/callback`. On a LAN origin that 127.0.0.1 is the BROWSER's machine, and Google's firstparty/nativeapp consent only releases the code once the loopback is reachable from the approving browser. When it is not, the consent never redirects at all — it hangs. So unlike an ordinary provider there is no error page and no callback URL in the address bar. That made the existing copy actively wrong. `googleOAuthWarning` was corrected when the login helper shipped (cheaper-inference#5203), but a changed English value does not invalidate existing translations and `i18n:sync-ui` only fills keys that are ABSENT, never ones that are STALE — so 39 of 43 locales (pt-BR, pt, es, de, fr, ja, zh-CN, …) kept the original "wait for the redirect, copy the full URL and paste it below", instructing a flow that cannot complete. The drift gate that should have caught this is a no-op: `check-translation-drift.mjs` needs `.i18n-state.json`, which is not in the repo, and it runs `--warn`. Because the key's MEANING changed, it is renamed rather than edited — a new key cannot inherit a stale translation. `googleOAuthWarning` is removed from all 43 locales and replaced by 7 `googleLoopback*` keys, hand-written for en + pt-BR and marked `__MISSING__:` elsewhere so the runtime falls back to correct English (cheaper-inference#7258). UI: `OAuthGoogleLoopbackNotice` states what is happening and surfaces both real remedies with the detected host and port filled in — the local login helper (recommended; its blob is what the Step 2 field accepts) and a single-port SSH forward. It also REPLACES `remoteAccessInfo` for this family instead of stacking on top of it: that notice promises an error page whose URL you copy, true for ordinary providers and false here. `agy` deliberately gets no helper command. bin/cli/commands/login.mjs pins PROVIDER = "antigravity" and parsePastedCredentials() rejects a blob whose embedded provider does not match the route provider, so advertising the helper there would send the operator to a blob guaranteed to be refused. It keeps the tunnel path. Refactor: the shared `resolveDashboardPort` / `buildSshLocalForward` helpers move to `loopbackTunnel.ts`, used by both hint builders. The codex builder's behaviour is unchanged (its 11 tests still pass untouched). docs: REMOTE-MODE.md notes that the dashboard now surfaces the remedies, states that one forward is enough for Antigravity (contrasting the two-port codex case), and aligns Option B's command on 127.0.0.1 to match what the UI generates. --------- Co-authored-by: ikelvingo <im.kelvinwong@gmail.com>
muhamadgalihsaputra
pushed a commit
to niyatna/NiyatnaRoute
that referenced
this pull request
Sep 27, 2026
…nce#8706) * fix: repair five base-red failures on release/v3.8.49 Every PR cut from this branch fails CI on the branch's own breakage. Five distinct causes, none introduced by the PRs that trip over them: 1. dast-smoke / Turbopack build — src/sse/handlers/chat.ts imported PROVIDER_BREAKER_FAILURE_STATUSES twice in one statement. A duplicate import specifier is an ECMAScript syntax error, so the production build never compiled. Introduced by cheaper-inference#8258, whose export fix landed on top of an import that already existed. 2. Unit Tests — the cheaper-inference#8393 verified-cooldown bypass was renamed exactCooldownVerified -> exactCooldownIsUpstreamReset during the cheaper-inference#8254 conflict resolution, which also dropped the flag at the markAccountUnavailable call site entirely. The rename left the test passing the old key (so the flag was silently ignored and a verified upstream reset got clamped back to maxCooldownMs), and the dropped call site meant no real caller set it at all. Align the test on the surviving name, restore the call site, and restore the doc comment explaining cheaper-inference#6863 vs cheaper-inference#7940. 3. Unit Tests — cheaper-inference#8526 added four common.* keys to en.json only, breaking the strict key-parity tests for pt-BR and vi. Translated into all 42 locales. 4. Unit Tests — vi carried 17 __MISSING__ placeholders from cheaper-inference#8354 and cheaper-inference#8463, and vi is the one locale with a no-placeholder test. Translated. 5. No new ESLint warnings — four suppressed `any`s in tests/unit/combo-routing-engine.test.ts no longer exist, and ESLint exits 2 on stale suppressions. Pruned (271 -> 267); no other entry moved. Also regenerates skills/cli-backup-sync/SKILL.md, which still documented the `backup status` flags cheaper-inference#8512 removed — the merge-integrity gate compares the generated output against the tree. Not fixed here: the env/docs contract (NEXT_PUBLIC_OMNIROUTE_BASE_PATH and OMNIROUTE_BACKUP_SCHEDULE_JOB_INTERVAL_MS missing from .env.example), which cheaper-inference#8690 already covers, and the quality baselines, which cheaper-inference#8686 covers. * fix(quality): keep prettier off the generated SKILL.md files check:agent-skills-sync diffs the generator's output against the tree byte for byte, but lint-staged runs prettier over any staged *.md — and prettier inserts a blank line after the frontmatter that the generator does not emit. Committing a regenerated skill therefore made the gate fail again on the very file that was just brought back in sync. The 44 untouched skills only escape this because they never pass through lint-staged. The generator is the formatter of record for these files, so ignore them. * fix(i18n,quality): drop the stale zh-TW key; raise the auth.ts frozen cap cheaper-inference#8463 renamed `oauthModal.googleOAuthWarning` away but left the old key behind in zh-TW, so the "the stale googleOAuthWarning key is GONE from every locale" guard fails on the branch. Removed it. The auth.ts frozen line cap goes 2486 -> 2492. Restoring the dropped exactCooldownIsUpstreamReset call site costs 7 lines, and staging the file makes lint-staged reformat four pre-existing over-100-column lines to prettier's rule — unavoidable without bypassing the hook, which hard rule cheaper-inference#10 forbids. The file still sits 12 lines below where the cap was set relative to its actual size.
muhamadgalihsaputra
pushed a commit
to niyatna/NiyatnaRoute
that referenced
this pull request
Sep 27, 2026
…ten (cheaper-inference#8574) Closes the gap that let cheaper-inference#8463 ship. `oauthModal.googleOAuthWarning`'s English value was rewritten when the Antigravity login helper landed (cheaper-inference#5203); 39 of 43 locales kept a translation of the PREVIOUS English, which told operators to "copy the full URL and paste it below" — a flow that cannot complete for that provider family. Non-English users read confident, wrong instructions for months and no gate noticed. None of the three existing gates can see this class: - `sync-ui-keys.mjs` only backfills keys that are ABSENT, never ones that are STALE; - `check-ui-keys-coverage.mjs` counts key PRESENCE, so a stale translation scores as fully covered (all 43 locales sat at 99.6% throughout); - `check-translation-drift.mjs` tracks the `docs/i18n/<locale>/**.md` documentation mirrors — it never reads `src/i18n/messages/*.json` at all. (Its `.i18n-state.json` is also absent, so it self-skips, but bootstrapping it would not have helped: wrong surface.) New gate `scripts/i18n/check-ui-value-drift.mjs` is DIFF-AWARE rather than baseline-backed: it compares `en.json` at the merge base against the working tree, and for every key whose English value changed, reports any locale still holding an untouched translation. That choice deliberately freezes pre-existing debt — a diff cannot reveal which old English a long-standing translation came from, so the gate judges only what the current change touches, and unrelated PRs never pay for historical drift. The alternative, a per-key hash baseline over 11207 keys, would have cost a ~600 KB generated file (3x the largest existing baseline) churning on every i18n PR. Two ways to satisfy it: refresh the translations, or set them to `__MISSING__:<new english>` so the runtime serves the corrected English (cheaper-inference#7258) while the key queues for translation. When the string's MEANING changes, renaming the key is better still — a new key cannot inherit a stale translation, which is what cheaper-inference#8463 did. Wired blocking into the `i18n-ui-coverage` job (the `i18n` job is `continue-on-error: true`, so a gate there could not block anything). That job gains `fetch-depth: 0` because the gate needs the base ref; without it the gate self-skips with `base-unresolved`, mirroring `check-openapi-breaking`. `BASE_REF` is passed via `env:` and reaches git only through `execFileSync` argv — never a shell string. Verified against the real defect: rewriting an English value with translations left behind reports exactly 39 stale locales and exits 1; `--warn` exits 0; an unresolvable base exits 0 with `SKIP reason=base-unresolved`. Co-authored-by: ikelvingo <im.kelvinwong@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Actionable guidance for both loopback-mismatch families in the OAuth connect dialog,
replacing prose (and, for Google, actively wrong prose) with the concrete commands.
Family 1 — fixed foreign port (
codex,xai-oauth,grok-cli)#8046correctly stops the doomed login from a LAN IP, but explained itself as onelong sentence in the generic red "Connection failed" step, with
<port>/<omniroute-host>placeholders to resolve by hand — and it forwarded a single port,which does not work. Both are required:
callback-server branch at all;
the PKCE callback server listens on the server's loopback.
buildPkceLoopbackMismatchHint()returns the finding as data with the detected host andboth ports filled in, and
OAuthLoopbackMismatchPanelrenders what happened → how tofix in three numbered steps with copy-to-clipboard fields:
No "Try again" button — retrying the same origin fails identically. The panel yields
to the paste-token tab so
grok-cli(in bothPKCE_CALLBACK_SERVER_PROVIDERSandTOKEN_PASTE_PROVIDERS) never stacks the panel on the import form.Family 2 — Google loopback (
antigravity,agy)Different mechanism, worse failure mode. There is no fixed foreign port: OAuthModal
builds
http://127.0.0.1:<dashboardPort>/callback. On a LAN origin that 127.0.0.1 is thebrowser's machine, and Google's firstparty/nativeapp consent only releases the code
once the loopback is reachable from the approving browser. When it is not, the consent
never redirects — it hangs. There is no error page and no callback URL to copy.
The stale-translation defect
googleOAuthWarningwas corrected when the login helper shipped (#5203), but a changedEnglish value does not invalidate existing translations, and
i18n:sync-uionly fillskeys that are ABSENT — never ones that are STALE. So 39 of 43 locales (pt-BR, pt, es,
de, fr, ja, zh-CN, …) kept the original text:
That instructs a flow that cannot complete for this provider family. The gate that
should have caught it is a no-op:
check-translation-drift.mjsrequires.i18n-state.json, which is not in the repo, and it runs--warn.Because the key's meaning changed, it is renamed rather than edited — a new key
cannot inherit a stale translation.
googleOAuthWarningis removed from all 43 localesand replaced by 7
googleLoopback*keys: hand-written foren+pt-BR, marked__MISSING__:elsewhere so the runtime falls back to correct English (#7258).The notice
OAuthGoogleLoopbackNoticestates what is happening and surfaces both real remedies withthe detected host and port filled in:
It replaces
remoteAccessInfofor this family rather than stacking on it — thatnotice promises an error page whose URL you copy, which is true for ordinary providers and
false here; showing both is a contradiction on one screen.
agydeliberately gets no helper command:bin/cli/commands/login.mjspinsPROVIDER = "antigravity"andparsePastedCredentials()rejects a blob whose embeddedprovider does not match the route provider, so advertising the helper there would send the
operator to a blob guaranteed to be refused. It keeps the tunnel path.
Docs
docs/guides/REMOTE-MODE.md:— per-provider callback table, the two-port tunnel, and why each forward is needed;
forward is enough here (contrasting the codex case), and Option B's command is aligned
on
127.0.0.1to match what the UI generates.i18n mechanics
<user>and the angle-bracket placeholders are passed as ICU values, never inlinedinto a catalog string — next-intl parses angle brackets in a message as rich-text tags and
would throw at render time.
Tests
Written failing-first, 21 new tests across two files:
oauth-lan-loopback-guidance.test.ts(11) — per-provider port mapping, the two-portcommand, degradation edge cases (unknown provider, empty
location.port, dashboardalready on the callback port), modal wiring, paste-tab guard, en/pt-BR key coverage.
oauth-google-loopback-guidance.test.ts(10) — callback rides the dashboard port,single forward, helper present for
antigravityand absent foragy, the renamedkey is gone from all 43 locales, en/pt-BR are hand-written (not sentinels), and no
hand-written string tells the operator to copy a callback URL.
oauth-modal-codex-lan-ip-8046.test.ts— the assertion pinning the old call site wasretargeted to the new builder. Not weakened: it now also asserts the arm actually
return;s instead of merely annotating before falling through.Refactor: shared
resolveDashboardPort/buildSshLocalForwardmoved toloopbackTunnel.ts. The codex builder's behaviour is unchanged — its 11 tests passuntouched.
Validation
typecheck:corelint(changed files)*oauth*,*antigravity*)test:vitestcheck-docs-synci18n:check-ui-coveragecheck-glossary-consistency --locale=zh-CNcheck:cycles/check:any-budget:t11/check:tracked-artifactsPre-existing base reds (not from this PR; reproduced on files identical to
origin/release/v3.8.49, with no import path to anything changed here):npm run lint→ 4 ×no-explicit-anyintests/unit/claude-to-openai-think-close-5123.test.tsnpm run test:unit→ 2 failures intests/unit/services/ServiceSupervisor.test.ts(
probeBeforeSpawn adopts a healthy existing instance,adopted service resolves … real pid) —they bind a real port and resolve a real PID; they reproduce in isolation on the untouched file.
Follow-up (not in this PR)
The i18n drift gate is inert —
.i18n-state.jsonis absent, socheck-translation-drift.mjscompares nothing, and it is--warnon top of that. That iswhy 39 stale translations shipped unnoticed. Bootstrapping it would turn this class of bug
into a CI failure instead of a user-visible one.