Repository navigation
fix(desktop): active provider sync — UI badge and generate IPC now share single source of truth - #55
Merged
Merged
Conversation
Contributor
There was a problem hiding this comment.
Findings
- [Major] Renderer
baseUrlstill takes precedence after provider override, which can route the active provider request to a stale endpoint and send the active provider API key to the wrong host. This reintroduces cross-provider drift in transport even though model/provider are now canonicalized, evidenceapps/desktop/src/main/index.ts:162,apps/desktop/src/main/index.ts:276.
Suggested fix:// Do not trust renderer-supplied baseUrl once provider/model are resolved from cached config. const baseUrl = active.baseUrl ?? undefined;
Summary
- Review mode: initial
- 1 issue found in the latest diff.
docs/VISION.mdanddocs/PRINCIPLES.mdwere not available in this checkout (Not found in repo/docs), so this review used repo-visible constraints plus changed code.
Testing
- Not run (automation)
open-codesign Bot
| // diverge from what the user sees. | ||
| const active = resolveActiveModel(cfg, payload.model); | ||
| const apiKey = getApiKeyForProvider(active.model.provider); | ||
| const baseUrl = payload.baseUrl ?? active.baseUrl ?? undefined; |
Contributor
There was a problem hiding this comment.
payload.baseUrl is still prioritized here even when resolveActiveModel has overridden provider/model. That can send the active provider's API key to a stale host. Prefer canonical config endpoint only:
const baseUrl = active.baseUrl ?? undefined;| } | ||
| const active = resolveActiveModel(cfg, payload.model); | ||
| const apiKey = getApiKeyForProvider(active.model.provider); | ||
| const baseUrl = payload.baseUrl ?? active.baseUrl ?? undefined; |
Contributor
There was a problem hiding this comment.
Same precedence issue in the legacy shim: payload.baseUrl can bypass canonical provider endpoint selection. Align with resolveActiveModel output:
const baseUrl = active.baseUrl ?? undefined;
hqhq1025
added a commit
that referenced
this pull request
Apr 18, 2026
Codex Major on PR #55: even after resolveActiveModel snaps to the canonical active provider, the IPC handlers were still preferring payload.baseUrl over active.baseUrl. A stale renderer hint like {provider: openai, baseUrl: duckcoding.ai/v1} would route the active openrouter API key to duckcoding's host. Once we've snapped, the renderer baseUrl belongs to a (possibly stale) different provider and cannot be trusted. Always use the per-provider baseUrl from cached config — same source the Settings UI reads. Adds a vitest case covering the cross-provider baseUrl scenario. Signed-off-by: hqhq1025 <1506751656@qq.com>
hqhq1025
force-pushed
the
wt/loop-fix-active-provider-sync
branch
from
April 18, 2026 20:20
b44459d to
b9dcd07
Compare
…are single source of truth The renderer kept its own copy of cfg.provider/modelPrimary in the Zustand store and forwarded them in GeneratePayloadV1.model. When that copy drifted from cachedConfig (e.g. after switching providers in Settings), the generate handler silently called a different provider than the Active badge displayed — users would see OpenRouter active but logs showed openai + a leftover duckcoding baseUrl. Add resolveActiveModel() that snaps the renderer hint back to the canonical active provider in cachedConfig (the same source toProviderRows uses for the badge), then use it in codesign:v1:generate, the legacy codesign:generate shim, and codesign:apply-comment. baseUrl is now sourced from the active provider's cached entry, never from a stale per-provider lookup keyed by the renderer hint. Mismatches are logged as resolve_active.override so the dev log timeline shows when a snap happened. Signed-off-by: hqhq1025 <1506751656@qq.com>
Codex Major on PR #55: even after resolveActiveModel snaps to the canonical active provider, the IPC handlers were still preferring payload.baseUrl over active.baseUrl. A stale renderer hint like {provider: openai, baseUrl: duckcoding.ai/v1} would route the active openrouter API key to duckcoding's host. Once we've snapped, the renderer baseUrl belongs to a (possibly stale) different provider and cannot be trusted. Always use the per-provider baseUrl from cached config — same source the Settings UI reads. Adds a vitest case covering the cross-provider baseUrl scenario. Signed-off-by: hqhq1025 <1506751656@qq.com>
Contributor
There was a problem hiding this comment.
Findings
- None.
Summary
- Review mode: follow-up after new commits
- No new issues found in added/modified lines of the latest diff.
docs/VISION.mdanddocs/PRINCIPLES.md: Not found in repo/docs.- Residual risk: behavior changed in
codesign:v1:generate, legacycodesign:generate, andcodesign:apply-comment, but this diff only adds unit tests forresolveActiveModeland does not add handler-level IPC tests for those call paths.
Testing
- Not run (automation)
- Suggested tests: Vitest IPC handler tests that send stale renderer hints and assert canonical
provider/modelIdplus canonical per-providerbaseUrlare used in both generate handlers and apply-comment.
open-codesign Bot
Defense-in-depth for the codex Major finding: even though the IPC handlers already use active.baseUrl exclusively, the renderer-supplied payload.baseUrl could still leak into future log/handler additions. Mutate payload.baseUrl post-snap so the canonical per-provider value is the only baseUrl reachable from this point onward, and add a vitest case that pins the openrouter-active vs. openai-hint scenario. Signed-off-by: hqhq1025 <1506751656@qq.com>
hqhq1025
force-pushed
the
wt/loop-fix-active-provider-sync
branch
from
April 18, 2026 20:25
b9dcd07 to
652fccd
Compare
Contributor
There was a problem hiding this comment.
Findings
- None.
Summary
- Review mode: follow-up after new commits
- No new issues found in added/modified lines of the latest diff.
docs/VISION.mdanddocs/PRINCIPLES.md: Not found in repo/docs.- Residual risk: behavior changed in
codesign:v1:generate, legacycodesign:generate, andcodesign:apply-comment, but this diff only adds unit tests forresolveActiveModeland does not add handler-level IPC tests for those paths.
Testing
- Not run (automation)
- Suggested tests: Vitest IPC handler tests that send stale renderer hints and assert canonical
provider/modelIdand canonical per-providerbaseUrlare used in both generate handlers and apply-comment.
open-codesign Bot
hqhq1025
added a commit
that referenced
this pull request
Apr 19, 2026
…are single source of truth (#55) * fix(desktop): active provider sync — UI badge and generate IPC now share single source of truth The renderer kept its own copy of cfg.provider/modelPrimary in the Zustand store and forwarded them in GeneratePayloadV1.model. When that copy drifted from cachedConfig (e.g. after switching providers in Settings), the generate handler silently called a different provider than the Active badge displayed — users would see OpenRouter active but logs showed openai + a leftover duckcoding baseUrl. Add resolveActiveModel() that snaps the renderer hint back to the canonical active provider in cachedConfig (the same source toProviderRows uses for the badge), then use it in codesign:v1:generate, the legacy codesign:generate shim, and codesign:apply-comment. baseUrl is now sourced from the active provider's cached entry, never from a stale per-provider lookup keyed by the renderer hint. Mismatches are logged as resolve_active.override so the dev log timeline shows when a snap happened. Signed-off-by: hqhq1025 <1506751656@qq.com> * fix(desktop): drop renderer baseUrl after provider snap Codex Major on PR #55: even after resolveActiveModel snaps to the canonical active provider, the IPC handlers were still preferring payload.baseUrl over active.baseUrl. A stale renderer hint like {provider: openai, baseUrl: duckcoding.ai/v1} would route the active openrouter API key to duckcoding's host. Once we've snapped, the renderer baseUrl belongs to a (possibly stale) different provider and cannot be trusted. Always use the per-provider baseUrl from cached config — same source the Settings UI reads. Adds a vitest case covering the cross-provider baseUrl scenario. Signed-off-by: hqhq1025 <1506751656@qq.com> * fix(desktop): explicitly null payload.baseUrl on provider override Defense-in-depth for the codex Major finding: even though the IPC handlers already use active.baseUrl exclusively, the renderer-supplied payload.baseUrl could still leak into future log/handler additions. Mutate payload.baseUrl post-snap so the canonical per-provider value is the only baseUrl reachable from this point onward, and add a vitest case that pins the openrouter-active vs. openai-hint scenario. Signed-off-by: hqhq1025 <1506751656@qq.com> --------- Signed-off-by: hqhq1025 <1506751656@qq.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.
Summary
cfg.provider/cfg.modelPrimaryin the Zustand store and forwarded them inGeneratePayloadV1.model. When that store drifted fromcachedConfig(e.g. between Settings tabs, multiple windows, or any path that mutated cfg without firingsetConfig), the generate handler silently called a different provider than the Active badge displayed.provider: openai, baseUrl: https://api.duckcoding.ai/v1(a leftover per-provider baseUrl entry resolved against the stale renderer hint).resolveActiveModel(cfg, hint)inprovider-settings.tsis the single resolver: it snaps the renderer hint back tocachedConfig.provider(the SAME sourcetoProviderRowsuses for the Active badge), preserves the hint'smodelIdwhen the providers match (so the renderer can still pick fast vs primary), and otherwise snaps the modelId tocfg.modelPrimary. baseUrl is sourced exclusively from the active provider's cachedbaseUrlsentry.codesign:v1:generate, the legacycodesign:generateshim, andcodesign:apply-comment. Mismatches logstep=resolve_active.overrideso the dev timeline shows when a snap happened.Root cause / disconnect
Two implicit sources of truth:
runListProviders()→toProviderRows(cachedConfig)(canonical).payload.model.providerfrom the renderer (could be stale).Whenever the renderer's store fell behind a Settings mutation, source (2) diverged from (1). The fix collapses both onto
cachedConfigviaresolveActiveModel.Test plan
pnpm --filter @open-codesign/desktop test— 142 tests pass, including 4 newresolveActiveModelcases (matching hint, stale-hint snap, baseUrl threading, missing-secret guard).pnpm typecheck— clean.pnpm lint— clean (only pre-existing warnings).provider: openrouter, baseUrl: <default>and never the stale openai/duckcoding combo.PRINCIPLES §5b
modelas-is.