Skip to content

Commit 5141b37

Browse files
committed
Fix CI ripgrep auth scope and effort tests
Scope GITHUB_TOKEN to the explicit ripgrep fallback postinstall instead of the full Bun dependency install, preserving normal install behavior without exposing the token to all lifecycle scripts. Replace leaking effort test module mocks with env-driven provider setup, expand provider env isolation, and allow the cold Codex import test enough time to avoid timeout flake.
1 parent 93a831e commit 5141b37

3 files changed

Lines changed: 111 additions & 70 deletions

File tree

.github/workflows/pr-checks.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,20 @@ jobs:
3737
cache-dependency-path: python/requirements.txt
3838

3939
- name: Install dependencies
40+
id: install
4041
run: bun install --frozen-lockfile
42+
continue-on-error: true
43+
44+
- name: Install ripgrep binary with GitHub auth
45+
if: steps.install.outcome == 'failure'
46+
run: node node_modules/@vscode/ripgrep/lib/postinstall.js
4147
env:
4248
GITHUB_TOKEN: ${{ github.token }}
4349

50+
- name: Retry dependency install
51+
if: steps.install.outcome == 'failure'
52+
run: bun install --frozen-lockfile
53+
4454
- name: Smoke check
4555
run: bun run smoke
4656

.github/workflows/release.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,20 @@ jobs:
5757
bun-version: 1.3.11
5858

5959
- name: Install dependencies
60+
id: install
6061
run: bun install --frozen-lockfile
62+
continue-on-error: true
63+
64+
- name: Install ripgrep binary with GitHub auth
65+
if: steps.install.outcome == 'failure'
66+
run: node node_modules/@vscode/ripgrep/lib/postinstall.js
6167
env:
6268
GITHUB_TOKEN: ${{ github.token }}
6369

70+
- name: Retry dependency install
71+
if: steps.install.outcome == 'failure'
72+
run: bun install --frozen-lockfile
73+
6474
- name: Run unit tests
6575
run: bun test --max-concurrency=1
6676

src/utils/effort.codex.test.ts

Lines changed: 91 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,106 @@
1-
import { afterEach, expect, mock, test } from 'bun:test'
2-
// Import the real auth.js and providerConfig.js up front so we can spread
3-
// their export surfaces into mock factories. `mock.module()` is process-global
4-
// in bun:test and `mock.restore()` does not undo it (see user.test.ts), so
5-
// any module we mock here needs to keep the full original export shape — or
6-
// downstream tests that load it via openaiShim/client/codexShim crash with
7-
// "Export named 'X' not found in module".
8-
import * as actualAuth from './auth.js'
9-
import * as actualProviderConfig from '../services/api/providerConfig.js'
10-
import * as actualThinking from './thinking.js'
11-
import * as actualGrowthbook from 'src/services/analytics/growthbook.js'
12-
import * as actualProviders from './model/providers.js'
13-
import * as actualModelSupportOverrides from './model/modelSupportOverrides.js'
1+
import { afterEach, beforeEach, expect, mock, test } from 'bun:test'
2+
3+
const ENV_KEYS = [
4+
'CLAUDE_CODE_USE_BEDROCK',
5+
'CLAUDE_CODE_USE_FOUNDRY',
6+
'CLAUDE_CODE_USE_GEMINI',
7+
'CLAUDE_CODE_USE_GITHUB',
8+
'CLAUDE_CODE_USE_MISTRAL',
9+
'CLAUDE_CODE_USE_OPENAI',
10+
'CLAUDE_CODE_USE_VERTEX',
11+
'CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED',
12+
'MISTRAL_BASE_URL',
13+
'OPENAI_BASE_URL',
14+
'OPENAI_MODEL',
15+
'OPENAI_API_BASE',
16+
'XAI_API_KEY',
17+
'MINIMAX_API_KEY',
18+
] as const
19+
20+
const originalEnv: Record<string, string | undefined> = {}
21+
type TestProvider = 'codex' | 'openai' | 'firstParty'
22+
23+
function resolveProviderFromEnv(): TestProvider {
24+
if (process.env.CLAUDE_CODE_USE_OPENAI) {
25+
const baseUrl = process.env.OPENAI_BASE_URL ?? ''
26+
const model = process.env.OPENAI_MODEL ?? ''
27+
return baseUrl.includes('/backend-api/codex') || model.startsWith('codex')
28+
? 'codex'
29+
: 'openai'
30+
}
31+
return 'firstParty'
32+
}
1433

15-
afterEach(() => {
34+
function installProviderMock(provider?: TestProvider): void {
35+
mock.module('./model/providers.js', () => ({
36+
getAPIProvider: () => provider ?? resolveProviderFromEnv(),
37+
getAPIProviderForStatsig: () => provider ?? resolveProviderFromEnv(),
38+
isFirstPartyAnthropicBaseUrl: () => true,
39+
isGithubNativeAnthropicMode: () => false,
40+
usesAnthropicAccountFlow: () =>
41+
(provider ?? resolveProviderFromEnv()) === 'firstParty',
42+
}))
43+
}
44+
45+
beforeEach(() => {
1646
mock.restore()
47+
installProviderMock()
48+
for (const key of ENV_KEYS) {
49+
originalEnv[key] = process.env[key]
50+
}
1751
})
1852

19-
async function importFreshEffortModule(options: {
20-
provider: 'codex' | 'openai'
21-
supportsCodexReasoningEffort: boolean
22-
}) {
23-
mock.module('./model/providers.js', () => ({
24-
...actualProviders,
25-
getAPIProvider: () => options.provider,
26-
}))
27-
mock.module('./model/modelSupportOverrides.js', () => ({
28-
...actualModelSupportOverrides,
29-
get3PModelCapabilityOverride: () => undefined,
30-
}))
31-
mock.module('../services/api/providerConfig.js', () => ({
32-
...actualProviderConfig,
33-
supportsCodexReasoningEffort: () => options.supportsCodexReasoningEffort,
34-
}))
35-
mock.module('./auth.js', () => ({
36-
...actualAuth,
37-
isProSubscriber: () => false,
38-
isMaxSubscriber: () => false,
39-
isTeamSubscriber: () => false,
40-
}))
41-
mock.module('./thinking.js', () => ({
42-
...actualThinking,
43-
isUltrathinkEnabled: () => false,
44-
}))
45-
mock.module('src/services/analytics/growthbook.js', () => ({
46-
...actualGrowthbook,
47-
getFeatureValue_CACHED_MAY_BE_STALE: (_key: string, fallback: unknown) =>
48-
fallback,
49-
}))
53+
afterEach(() => {
54+
for (const key of ENV_KEYS) {
55+
if (originalEnv[key] === undefined) {
56+
delete process.env[key]
57+
} else {
58+
process.env[key] = originalEnv[key]
59+
}
60+
}
61+
installProviderMock()
62+
})
63+
64+
async function importFreshEffortModule(options: { provider: TestProvider }) {
65+
for (const key of ENV_KEYS) {
66+
delete process.env[key]
67+
}
68+
installProviderMock(options.provider)
69+
if (options.provider === 'codex') {
70+
process.env.CLAUDE_CODE_USE_OPENAI = '1'
71+
process.env.OPENAI_MODEL = 'gpt-5.4'
72+
} else if (options.provider === 'openai') {
73+
process.env.CLAUDE_CODE_USE_OPENAI = '1'
74+
process.env.OPENAI_BASE_URL = 'https://api.openai.com/v1'
75+
process.env.OPENAI_MODEL = 'gpt-5.4'
76+
}
5077

5178
return import(`./effort.js?ts=${Date.now()}-${Math.random()}`)
5279
}
5380

54-
test('gpt-5.4 on the ChatGPT Codex backend supports effort selection', async () => {
55-
const { getAvailableEffortLevels, modelSupportsEffort } =
56-
await importFreshEffortModule({
57-
provider: 'codex',
58-
supportsCodexReasoningEffort: true,
59-
})
60-
61-
expect(modelSupportsEffort('gpt-5.4')).toBe(true)
62-
expect(getAvailableEffortLevels('gpt-5.4')).toEqual([
63-
'low',
64-
'medium',
65-
'high',
66-
'xhigh',
67-
])
68-
})
81+
test(
82+
'gpt-5.4 on the ChatGPT Codex backend supports effort selection',
83+
async () => {
84+
const { getAvailableEffortLevels, modelSupportsEffort } =
85+
await importFreshEffortModule({
86+
provider: 'codex',
87+
})
88+
89+
expect(modelSupportsEffort('gpt-5.4')).toBe(true)
90+
expect(getAvailableEffortLevels('gpt-5.4')).toEqual([
91+
'low',
92+
'medium',
93+
'high',
94+
'xhigh',
95+
])
96+
},
97+
15_000,
98+
)
6999

70100
test('gpt-5.4 on the OpenAI provider still supports effort selection', async () => {
71101
const { getAvailableEffortLevels, modelSupportsEffort } =
72102
await importFreshEffortModule({
73103
provider: 'openai',
74-
supportsCodexReasoningEffort: true,
75104
})
76105

77106
expect(modelSupportsEffort('gpt-5.4')).toBe(true)
@@ -87,7 +116,6 @@ test('gpt-5.3-codex-spark stays without effort controls', async () => {
87116
const { getAvailableEffortLevels, modelSupportsEffort } =
88117
await importFreshEffortModule({
89118
provider: 'codex',
90-
supportsCodexReasoningEffort: false,
91119
})
92120

93121
expect(modelSupportsEffort('gpt-5.3-codex-spark')).toBe(false)
@@ -97,7 +125,6 @@ test('gpt-5.3-codex-spark stays without effort controls', async () => {
97125
test('toPersistableEffort normalizes xhigh to max so it survives settings write', async () => {
98126
const { toPersistableEffort } = await importFreshEffortModule({
99127
provider: 'openai',
100-
supportsCodexReasoningEffort: true,
101128
})
102129

103130
expect(toPersistableEffort('xhigh')).toBe('max')
@@ -112,7 +139,6 @@ test('standardEffortToOpenAI maps max to xhigh for shim payload', async () => {
112139
const { standardEffortToOpenAI, openAIEffortToStandard } =
113140
await importFreshEffortModule({
114141
provider: 'openai',
115-
supportsCodexReasoningEffort: true,
116142
})
117143

118144
expect(standardEffortToOpenAI('max')).toBe('xhigh')
@@ -128,7 +154,6 @@ test('e2e: xhigh → persisted max → resolveAppliedEffort → wire xhigh on Op
128154
standardEffortToOpenAI,
129155
} = await importFreshEffortModule({
130156
provider: 'openai',
131-
supportsCodexReasoningEffort: true,
132157
})
133158

134159
// Picker writes the OpenAI-shaped value; toPersistableEffort normalizes.
@@ -147,19 +172,16 @@ test('e2e: xhigh → persisted max → resolveAppliedEffort → wire xhigh on Op
147172

148173
test('e2e: max on non-Opus Anthropic model still clamps to high', async () => {
149174
const { resolveAppliedEffort } = await importFreshEffortModule({
150-
provider: 'firstParty' as unknown as 'openai',
151-
supportsCodexReasoningEffort: false,
175+
provider: 'firstParty',
152176
})
153177

154178
expect(resolveAppliedEffort('claude-sonnet-4-6', 'max')).toBe('high')
155179
})
156180

157-
158181
test('getEffortSuffix shows the effective displayed effort for supported models', async () => {
159182
const { getDisplayedEffortLevel, getEffortSuffix } =
160183
await importFreshEffortModule({
161184
provider: 'openai',
162-
supportsCodexReasoningEffort: true,
163185
})
164186

165187
expect(getEffortSuffix('gpt-5.4', 'medium')).toBe(' with medium effort')
@@ -173,7 +195,6 @@ test('getEffortSuffix shows the effective displayed effort for supported models'
173195
test('getEffortSuffix stays hidden for models without effort controls', async () => {
174196
const { getEffortSuffix } = await importFreshEffortModule({
175197
provider: 'codex',
176-
supportsCodexReasoningEffort: false,
177198
})
178199

179200
expect(getEffortSuffix('gpt-5.3-codex-spark', 'medium')).toBe('')

0 commit comments

Comments
 (0)