Skip to content

Commit 5bb5df6

Browse files
committed
fix: mask legacy routing probe policy
1 parent 2edab31 commit 5bb5df6

11 files changed

Lines changed: 71 additions & 92 deletions

frontend/src/features/claude-code/ClaudeCodeAccountListFeature.tsx

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import {
3838
buildCodexRoutePolicyPreview,
3939
buildCodexRoutePolicyRowStates,
4040
buildCodexRoutingProbeModelOptions,
41+
buildCodexRoutingProbeRequestInput,
4142
buildCodexRoutingProbeStreamLines,
4243
mergeCodexAuthFileModelMappings,
4344
resolveCodexRoutingProbeDefaultModel,
@@ -114,15 +115,14 @@ export default function ClaudeCodeAccountListFeature({ sidecarStatus }: ClaudeCo
114115
};
115116
}, [authFileModelMappings, detailRow]);
116117
const requestableRows = useMemo(() => orderedRows.filter((row) => row.requestable), [orderedRows]);
117-
const requestableOrderIDs = useMemo(() => requestableRows.map((row) => row.id), [requestableRows]);
118118
const routePolicyDraft = useMemo(
119119
() => ({
120120
allowAccountIDs: [],
121121
denyAccountIDs: [],
122-
orderAccountIDs: requestableOrderIDs,
123-
allowFallback: false,
122+
orderAccountIDs: [],
123+
allowFallback: true,
124124
}),
125-
[requestableOrderIDs],
125+
[],
126126
);
127127
const routePolicyPreviewRows = useMemo(
128128
() => buildCodexRoutePolicyPreview(orderedRows, routePolicyDraft),
@@ -147,10 +147,6 @@ export default function ClaudeCodeAccountListFeature({ sidecarStatus }: ClaudeCo
147147
);
148148
const latestRoutingProbeAttempt = routingProbeAttempts[routingProbeAttempts.length - 1] || null;
149149
const latestRoutingProbeAccountID = latestRoutingProbeAttempt?.accountID || '';
150-
const latestRoutingProbeExpectedID = routePolicyPreviewRows[0]?.id || '';
151-
const latestRoutingProbeUsedFallback =
152-
Boolean(latestRoutingProbeAccountID && latestRoutingProbeExpectedID) &&
153-
latestRoutingProbeAccountID !== latestRoutingProbeExpectedID;
154150
const routingProbeDisabled = !ready || saving || routingProbeRunning || !routingProbeModel.trim();
155151

156152
async function reload(messageOverride?: string) {
@@ -593,14 +589,7 @@ export default function ClaudeCodeAccountListFeature({ sidecarStatus }: ClaudeCo
593589
try {
594590
const collectedAttempts: CodexRoutingProbeAttemptView[] = [];
595591
for (let attemptIndex = 0; attemptIndex < safeAttempts; attemptIndex += 1) {
596-
const routePolicyInput = {
597-
model,
598-
attempts: 1,
599-
allowAccountIDs: [],
600-
denyAccountIDs: [],
601-
orderAccountIDs: requestableOrderIDs,
602-
allowFallback: false,
603-
};
592+
const routePolicyInput = buildCodexRoutingProbeRequestInput(model, 1);
604593
const result = await trackRequest('ProbeClaudeCodeAccountRouting', { ...routePolicyInput, index: attemptIndex + 1 }, () =>
605594
ProbeClaudeCodeAccountRouting(
606595
main.ProbeClaudeCodeAccountRoutingInput.createFrom({
@@ -829,7 +818,6 @@ export default function ClaudeCodeAccountListFeature({ sidecarStatus }: ClaudeCo
829818
routingProbeDisabled={routingProbeDisabled || routePolicyPreviewRows.length === 0}
830819
routePolicyPreviewRows={routePolicyPreviewRows}
831820
routingProbeStreamLines={routingProbeStreamLines}
832-
latestUsedFallback={latestRoutingProbeUsedFallback}
833821
onClose={() => setRouteProbeOpen(false)}
834822
onModelChange={setRoutingProbeModel}
835823
onProbeOnce={() => void runRoutingProbe(1)}

frontend/src/features/codex/CodexAccountListFeature.tsx

Lines changed: 29 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import {
5252
buildCodexRoutePolicyPreview,
5353
buildCodexRoutePolicyRowStates,
5454
buildCodexRoutingProbeModelOptions,
55+
buildCodexRoutingProbeRequestInput,
5556
buildCodexRoutingProbeStreamLines,
5657
buildOpenAICompatibleModelMappings,
5758
canEditCodexModelMappings,
@@ -126,15 +127,14 @@ export default function CodexAccountListFeature({ sidecarStatus }: CodexAccountL
126127
const orderChanged = orderDirty;
127128
const routingProbeModelOptions = useMemo(() => buildCodexRoutingProbeModelOptions(orderedRows), [orderedRows]);
128129
const requestableRows = useMemo(() => orderedRows.filter((row) => row.requestable), [orderedRows]);
129-
const requestableOrderIDs = useMemo(() => requestableRows.map((row) => row.id), [requestableRows]);
130130
const routePolicyDraft = useMemo(
131131
() => ({
132132
allowAccountIDs: [],
133133
denyAccountIDs: [],
134-
orderAccountIDs: requestableOrderIDs,
135-
allowFallback: false,
134+
orderAccountIDs: [],
135+
allowFallback: true,
136136
}),
137-
[requestableOrderIDs],
137+
[],
138138
);
139139
const routePolicyPreviewRows = useMemo(
140140
() => buildCodexRoutePolicyPreview(orderedRows, routePolicyDraft),
@@ -159,10 +159,6 @@ export default function CodexAccountListFeature({ sidecarStatus }: CodexAccountL
159159
);
160160
const latestRoutingProbeAttempt = routingProbeAttempts[routingProbeAttempts.length - 1] || null;
161161
const latestRoutingProbeAccountID = latestRoutingProbeAttempt?.accountID || '';
162-
const latestRoutingProbeExpectedID = routePolicyPreviewRows[0]?.id || '';
163-
const latestRoutingProbeUsedFallback =
164-
Boolean(latestRoutingProbeAccountID && latestRoutingProbeExpectedID) &&
165-
latestRoutingProbeAccountID !== latestRoutingProbeExpectedID;
166162
const routingProbeDisabled = !ready || saving || routingProbeRunning || !routingProbeModel.trim();
167163

168164
async function reload(messageOverride?: string) {
@@ -598,26 +594,31 @@ export default function CodexAccountListFeature({ sidecarStatus }: CodexAccountL
598594
channelOrder: index,
599595
}));
600596
const result = main.ChannelRoutingExplainResult.createFrom({
601-
channel: 'codex',
602-
routeMode: nextConfig.routeMode,
603-
selectedAccountID: candidates[0]?.id || '',
604-
candidates,
605-
filtered: orderedRows
606-
.filter((row) => !row.requestable)
607-
.map((row) => ({ id: row.id, reason: row.disabled ? 'account-disabled' : 'account-unrequestable' })),
608-
steps: [`mode:${nextConfig.routeMode}`, `candidates:${candidates.length}`, 'preview:browser'],
609-
snapshotVersion: 'preview',
610-
policyVersion: 'channel-routing-v1',
611-
shadow: nextConfig.shadowEnabled
612-
? {
613-
enabled: true,
614-
routeMode: nextConfig.shadowRouteMode,
615-
selectedAccountID: candidates[candidates.length - 1]?.id || candidates[0]?.id || '',
616-
diff: Boolean(candidates.length > 1),
617-
steps: [`mode:${nextConfig.shadowRouteMode}`, `candidates:${candidates.length}`, 'preview:shadow'],
597+
channel: 'codex',
598+
routeMode: nextConfig.routeMode,
599+
selectedAccountID: candidates[0]?.id || '',
600+
candidates,
601+
filtered: orderedRows
602+
.filter((row) => !row.requestable)
603+
.map((row) => ({ id: row.id, reason: row.disabled ? 'account-disabled' : 'account-unrequestable' })),
604+
steps: [
605+
`mode:${nextConfig.routeMode}`,
606+
'legacy:compatibility-mask=blocked',
607+
`candidates:${candidates.length}`,
608+
'preview:browser',
609+
],
610+
snapshotVersion: 'preview',
611+
policyVersion: 'channel-routing-v1',
612+
shadow: nextConfig.shadowEnabled
613+
? {
614+
enabled: true,
615+
routeMode: nextConfig.shadowRouteMode,
616+
selectedAccountID: candidates[candidates.length - 1]?.id || candidates[0]?.id || '',
617+
diff: Boolean(candidates.length > 1),
618+
steps: [`mode:${nextConfig.shadowRouteMode}`, `candidates:${candidates.length}`, 'preview:shadow'],
618619
}
619-
: undefined,
620-
});
620+
: undefined,
621+
});
621622
setChannelExplain(result);
622623
const event = buildPreviewChannelRouteAuditEvent({ channel: 'codex', explain: result });
623624
setChannelRouteEvents((prev) => (event ? [event, ...prev].slice(0, 5) : prev));
@@ -684,14 +685,7 @@ export default function CodexAccountListFeature({ sidecarStatus }: CodexAccountL
684685
try {
685686
const collectedAttempts: CodexRoutingProbeAttemptView[] = [];
686687
for (let attemptIndex = 0; attemptIndex < safeAttempts; attemptIndex += 1) {
687-
const routePolicyInput = {
688-
model,
689-
attempts: 1,
690-
allowAccountIDs: [],
691-
denyAccountIDs: [],
692-
orderAccountIDs: requestableOrderIDs,
693-
allowFallback: false,
694-
};
688+
const routePolicyInput = buildCodexRoutingProbeRequestInput(model, 1);
695689
const result = await trackRequest('ProbeCodexAccountRouting', { ...routePolicyInput, index: attemptIndex + 1 }, () =>
696690
ProbeCodexAccountRouting(
697691
main.ProbeCodexAccountRoutingInput.createFrom({
@@ -957,7 +951,6 @@ export default function CodexAccountListFeature({ sidecarStatus }: CodexAccountL
957951
routingProbeDisabled={routingProbeDisabled || routePolicyPreviewRows.length === 0}
958952
routePolicyPreviewRows={routePolicyPreviewRows}
959953
routingProbeStreamLines={routingProbeStreamLines}
960-
latestUsedFallback={latestRoutingProbeUsedFallback}
961954
onClose={() => setRouteProbeOpen(false)}
962955
onModelChange={setRoutingProbeModel}
963956
onProbeOnce={() => void runRoutingProbe(1)}

frontend/src/features/codex/components/CodexRouteProbeCard.stories.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,6 @@ function ProbeSample({
173173
routingProbeDisabled={running}
174174
routePolicyPreviewRows={visibleRows}
175175
routingProbeStreamLines={empty ? [] : fallback ? hitLines : idleLines}
176-
latestUsedFallback={fallback}
177176
onClose={() => undefined}
178177
onModelChange={setModel}
179178
onProbeOnce={() => undefined}

frontend/src/features/codex/components/CodexRouteProbeCard.tsx

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ export function RouteProbeCard({
1616
routingProbeDisabled,
1717
routePolicyPreviewRows,
1818
routingProbeStreamLines,
19-
latestUsedFallback,
2019
onClose,
2120
onModelChange,
2221
onProbeOnce,
@@ -36,7 +35,6 @@ export function RouteProbeCard({
3635
detail: string;
3736
status: CodexRoutingProbeStreamLineStatus;
3837
}>;
39-
latestUsedFallback: boolean;
4038
onClose: () => void;
4139
onModelChange: (value: string) => void;
4240
onProbeOnce: () => void;
@@ -58,10 +56,8 @@ export function RouteProbeCard({
5856
const latestHitLine = [...routingProbeStreamLines].reverse().find((line) => line.marker.startsWith('#') && line.status === 'hit');
5957
const statusLabel = routingProbeRunning
6058
? t('codex.account_list_probe_running')
61-
: latestUsedFallback
62-
? t('codex.account_list_probe_fallback_hit')
63-
: latestHitLine
64-
? latestHitLine.label
59+
: latestHitLine
60+
? latestHitLine.label
6561
: t('codex.account_list_probe_idle');
6662

6763
return (
@@ -92,7 +88,6 @@ export function RouteProbeCard({
9288
model={resolvedModel}
9389
candidateCount={routePolicyPreviewRows.length}
9490
statusLabel={statusLabel}
95-
latestUsedFallback={latestUsedFallback}
9691
/>
9792
<button
9893
type="button"
@@ -125,7 +120,6 @@ export function RouteProbeCard({
125120
<RouteProbeCandidateQueue rows={routePolicyPreviewRows} t={t} />
126121
<RouteProbeTerminal
127122
lines={routingProbeStreamLines}
128-
latestUsedFallback={latestUsedFallback}
129123
t={t}
130124
/>
131125
</div>
@@ -141,24 +135,17 @@ function ProbeStatusBar({
141135
model,
142136
candidateCount,
143137
statusLabel,
144-
latestUsedFallback,
145138
}: {
146139
t: (key: string) => string;
147140
model: string;
148141
candidateCount: number;
149142
statusLabel: string;
150-
latestUsedFallback: boolean;
151143
}) {
152144
return (
153145
<div className="hidden min-w-[28rem] grid-cols-[minmax(0,1.25fr)_7rem_minmax(0,1fr)] border-2 border-[var(--border-color)] bg-[var(--bg-main)] lg:grid">
154146
<ProbeMetric label={t('codex.account_list_probe_model')} value={model} />
155147
<ProbeMetric label={t('codex.account_list_policy_preview_count')} value={String(candidateCount).padStart(2, '0')} />
156-
<ProbeMetric
157-
label={t('codex.account_list_probe_result')}
158-
value={statusLabel}
159-
tone={latestUsedFallback ? 'critical' : 'neutral'}
160-
last
161-
/>
148+
<ProbeMetric label={t('codex.account_list_probe_result')} value={statusLabel} tone="neutral" last />
162149
</div>
163150
);
164151
}
@@ -321,7 +308,6 @@ function RouteProbeCandidateQueue({ rows, t }: { rows: CodexAccountRow[]; t: (ke
321308

322309
function RouteProbeTerminal({
323310
lines,
324-
latestUsedFallback,
325311
t,
326312
}: {
327313
lines: Array<{
@@ -331,7 +317,6 @@ function RouteProbeTerminal({
331317
detail: string;
332318
status: CodexRoutingProbeStreamLineStatus;
333319
}>;
334-
latestUsedFallback: boolean;
335320
t: (key: string) => string;
336321
}) {
337322
return (
@@ -341,11 +326,6 @@ function RouteProbeTerminal({
341326
<Terminal className="h-3.5 w-3.5" strokeWidth={3} />
342327
{t('codex.account_list_probe_terminal')}
343328
</div>
344-
{latestUsedFallback ? (
345-
<div className="border-2 border-[var(--accent-red)] bg-[color-mix(in_srgb,var(--color-status-danger)_10%,transparent)] px-2 py-1 text-[length:var(--font-size-ui-xs)] font-black uppercase tracking-wide text-[var(--accent-red)]">
346-
{t('codex.account_list_probe_fallback_hit')}
347-
</div>
348-
) : null}
349329
</div>
350330
<div className="max-h-[22rem] min-h-[13rem] overflow-auto bg-[var(--bg-main)] py-3 font-mono text-[length:var(--font-size-ui-md-compact)] font-black leading-6 text-[var(--text-primary)]">
351331
{lines.length === 0 ? (

frontend/src/features/codex/model/codexAccountList.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export {
1818
buildCodexRoutePolicyPreview,
1919
buildCodexRoutePolicyRowStates,
2020
buildCodexRoutePolicySummary,
21+
buildCodexRoutingProbeRequestInput,
2122
buildCodexRoutingProbeModelOptions,
2223
buildCodexRoutingProbeStreamLines,
2324
DEFAULT_CODEX_ROUTING_PROBE_MODEL,

frontend/src/features/codex/model/codexRoutePolicy.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,17 @@ export interface CodexRoutePolicyDraft {
1818
allowFallback: boolean;
1919
}
2020

21+
export function buildCodexRoutingProbeRequestInput(model: string, attempts: number) {
22+
return {
23+
model: String(model || '').trim(),
24+
attempts: Math.max(1, Math.min(5, Number.isFinite(attempts) ? attempts : 1)),
25+
allowAccountIDs: [],
26+
denyAccountIDs: [],
27+
orderAccountIDs: [],
28+
allowFallback: false,
29+
};
30+
}
31+
2132
export type CodexRoutePolicyRowMode = 'default' | 'allow' | 'deny' | 'blocked';
2233

2334
export interface CodexRoutePolicyRowState {

internal/wailsapp/channel_routing.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,7 @@ func explainChannelRoutingWithRuntime(accounts []accountsdomain.AccountRecord, c
306306
func explainNormalizedChannelRouting(accounts []accountsdomain.AccountRecord, normalized ChannelRoutingConfig, input ChannelRoutingExplainInput, meta ChannelRoutingConfigMeta, runtimeStates map[string]ChannelAccountRuntimeState) ChannelRoutingExplainResult {
307307
mode := normalized.RouteMode
308308
steps := []string{"mode:" + string(mode)}
309+
steps = appendChannelRoutingLegacyCompatibilitySteps(steps, normalized.Channel)
309310
scope := channelRouteScope{}
310311
if mode == ChannelRouteModeProject {
311312
binding, ok := matchChannelProjectBinding(normalized.ProjectBindings, input.ProjectName)
@@ -365,6 +366,18 @@ func decideChannelRoute(accounts []accountsdomain.AccountRecord, cfg ChannelRout
365366
}
366367
}
367368

369+
func appendChannelRoutingLegacyCompatibilitySteps(steps []string, channel string) []string {
370+
if strings.TrimSpace(channel) != "codex" {
371+
return steps
372+
}
373+
return append(
374+
steps,
375+
"legacy:session-affinity=blocked",
376+
"legacy:websocket-pin=blocked",
377+
"legacy:route-order-header=ignored",
378+
)
379+
}
380+
368381
func buildChannelRouteablePool(accounts []accountsdomain.AccountRecord, cfg ChannelRoutingConfig, input ChannelRoutingExplainInput, scope channelRouteScope, runtimeStates map[string]ChannelAccountRuntimeState) ([]channelRouteCandidate, []ChannelRoutingFilteredAccount) {
369382
groupLookup := channelAccountGroupLookup(cfg.AccountGroups, cfg.ChannelGroupStates)
370383
groupMembership := channelAccountGroupMembership(groupLookup)

internal/wailsapp/channel_routing_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,9 @@ func TestExplainChannelRoutingBalancedUsesActiveSessionsThenSortOrder(t *testing
156156
if result.SelectedAccountID != "auth-file:b.json" {
157157
t.Fatalf("SelectedAccountID = %q, want auth-file:b.json", result.SelectedAccountID)
158158
}
159+
assertStepContains(t, result.Steps, "legacy:session-affinity=blocked")
160+
assertStepContains(t, result.Steps, "legacy:websocket-pin=blocked")
161+
assertStepContains(t, result.Steps, "legacy:route-order-header=ignored")
159162
}
160163

161164
func TestExplainChannelRoutingDisabledStickyInvalidatesAndFallsBack(t *testing.T) {

internal/wailsapp/claude_code_routing_probe_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,8 @@ func TestProbeClaudeCodeAccountRoutingSendsAnthropicMessagesRequestAndRouteHeade
7171
if got := headers["X-GetTokens-Route-Allow"]; got != expectedRouteID {
7272
t.Fatalf("allow header = %q, want %q", got, expectedRouteID)
7373
}
74-
if got := headers["X-GetTokens-Route-Order"]; got != expectedRouteID {
75-
t.Fatalf("order header = %q, want %q", got, expectedRouteID)
74+
if got := headers["X-GetTokens-Route-Order"]; got != "" {
75+
t.Fatalf("order header = %q, want empty", got)
7676
}
7777
if got := headers["X-GetTokens-Route-Fallback"]; got != "false" {
7878
t.Fatalf("fallback header = %q, want false", got)

internal/wailsapp/codex_routing_probe.go

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ type codexRoutingRecentBucket struct {
6262
const (
6363
codexRouteAllowHeader = "X-GetTokens-Route-Allow"
6464
codexRouteDenyHeader = "X-GetTokens-Route-Deny"
65-
codexRouteOrderHeader = "X-GetTokens-Route-Order"
6665
codexRouteFallbackHeader = "X-GetTokens-Route-Fallback"
6766
)
6867

@@ -251,8 +250,7 @@ func buildCodexRoutingRouteHeaders(input ProbeCodexAccountRoutingInput, candidat
251250

252251
allowIDs := resolveCodexRoutingRouteIDs(input.AllowAccountIDs, routeIDByAccountID)
253252
denyIDs := resolveCodexRoutingRouteIDs(input.DenyAccountIDs, routeIDByAccountID)
254-
orderIDs := resolveCodexRoutingRouteIDs(input.OrderAccountIDs, routeIDByAccountID)
255-
if len(allowIDs) == 0 && len(denyIDs) == 0 && len(orderIDs) == 0 {
253+
if len(allowIDs) == 0 && len(denyIDs) == 0 {
256254
return nil
257255
}
258256

@@ -263,14 +261,7 @@ func buildCodexRoutingRouteHeaders(input ProbeCodexAccountRoutingInput, candidat
263261
if len(denyIDs) > 0 {
264262
headers[codexRouteDenyHeader] = strings.Join(denyIDs, ",")
265263
}
266-
if len(orderIDs) > 0 {
267-
headers[codexRouteOrderHeader] = strings.Join(orderIDs, ",")
268-
}
269-
if input.AllowFallback {
270-
headers[codexRouteFallbackHeader] = "true"
271-
} else {
272-
headers[codexRouteFallbackHeader] = "false"
273-
}
264+
headers[codexRouteFallbackHeader] = "false"
274265
return headers
275266
}
276267

0 commit comments

Comments
 (0)