Skip to content

Commit 6b4b63e

Browse files
committed
perf(boot): overlap independent startup file reads
1 parent 039042f commit 6b4b63e

9 files changed

Lines changed: 540 additions & 143 deletions

File tree

‎src/config.test.ts‎

Lines changed: 75 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,19 @@ import {
3737
clearSourceCredentials,
3838
peekSourceCredentialSecret,
3939
} from "./config/source-credentials.js";
40-
import type {
41-
Config,
42-
LoadConfigOptions,
43-
UnconfiguredConfig,
44-
} from "./config/index.js";
4540
import {
41+
ensureTelemetrySettings,
42+
resetSettingsDocumentReadCountForTests,
43+
settingsDocumentReadCountForTests,
4644
mergeProviderIntoSettings,
4745
type ResolvedProvider,
4846
type Settings,
4947
} from "./config/settings.js";
48+
import type {
49+
Config,
50+
LoadConfigOptions,
51+
UnconfiguredConfig,
52+
} from "./config/index.js";
5053
import {
5154
OPENCODE_GO_BASE_URL,
5255
OPENCODE_GO_MODEL_IDS,
@@ -1583,6 +1586,73 @@ describe("loadConfig", () => {
15831586
expect(config.apiKey).toBe("b-key");
15841587
});
15851588

1589+
test("CLI selection wins over local, and local wins over global", async () => {
1590+
const cwd = await emptyCwd();
1591+
await mkdir(join(cwd, ".corbits"), { recursive: true });
1592+
await writeFile(
1593+
join(cwd, ".corbits", "settings.json"),
1594+
JSON.stringify({ provider: "local-provider", model: "local-model" }),
1595+
);
1596+
const globalPath = join(cwd, "global.json");
1597+
await writeFile(
1598+
globalPath,
1599+
JSON.stringify({
1600+
defaultProvider: "global-provider",
1601+
providers: {
1602+
"global-provider": {
1603+
baseURL: "https://global.example/v1",
1604+
apiKey: "global-key",
1605+
models: ["global-model"],
1606+
},
1607+
"local-provider": {
1608+
baseURL: "https://local.example/v1",
1609+
apiKey: "local-key",
1610+
models: ["local-model"],
1611+
},
1612+
},
1613+
}),
1614+
);
1615+
const localWins = await loadConfig(["--cwd", cwd, "task"], {
1616+
globalSettingsPath: globalPath,
1617+
});
1618+
assertConfigured(localWins);
1619+
expect(localWins.providerName).toBe("local-provider");
1620+
expect(localWins.model).toBe("local-model");
1621+
1622+
const cliWins = await loadConfig(
1623+
[
1624+
"--cwd",
1625+
cwd,
1626+
"--provider",
1627+
"global-provider",
1628+
"--model",
1629+
"global-model",
1630+
"task",
1631+
],
1632+
{ globalSettingsPath: globalPath },
1633+
);
1634+
assertConfigured(cliWins);
1635+
expect(cliWins.providerName).toBe("global-provider");
1636+
expect(cliWins.model).toBe("global-model");
1637+
});
1638+
1639+
test("boot reuses the settings document instead of reading it again", async () => {
1640+
const cwd = await emptyCwd();
1641+
const globalPath = await writeGlobalSettings(cwd, {
1642+
telemetry: { installationId: "boot-id" },
1643+
});
1644+
resetSettingsDocumentReadCountForTests();
1645+
const config = await loadConfig(["--cwd", cwd, "task"], {
1646+
globalSettingsPath: globalPath,
1647+
});
1648+
assertConfigured(config);
1649+
const settings = await ensureTelemetrySettings(globalPath, {
1650+
reuseBootSnapshot: true,
1651+
});
1652+
expect(settingsDocumentReadCountForTests()).toBe(1);
1653+
expect(settings.telemetry?.installationId).toBe("boot-id");
1654+
});
1655+
15861656
test("rejects a local reasoningEffort unsupported by the selected model", async () => {
15871657
const cwd = await emptyCwd();
15881658
await mkdir(join(cwd, ".corbits"), { recursive: true });

‎src/config/index.ts‎

Lines changed: 76 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ import {
2424
setProviderContextWindowOverrides,
2525
setReservedCompletionTokens,
2626
} from "../provider/context-window.js";
27-
import { bootstrapPricingMetadata } from "../cost/pricing-metadata.js";
27+
import {
28+
bootstrapPricingMetadata,
29+
startPricingCacheRead,
30+
} from "../cost/pricing-metadata.js";
2831
import {
2932
defaultPricingCachePath,
3033
type PricingFetcherOptions,
@@ -89,6 +92,8 @@ import {
8992
loadLocalSettingsResult,
9093
type SettingsLoadDiagnostic,
9194
loadSettingsRecoveringClobberedOAuthSelection,
95+
readSettingsDocument,
96+
rememberBootGlobalSettings,
9297
resolveLocalSettingsPath,
9398
normalizeOpenAICompatibleBaseURL,
9499
resolveProvider,
@@ -1075,21 +1080,6 @@ export async function loadConfig(
10751080
positional.push(arg);
10761081
}
10771082

1078-
if (permissionsPath !== undefined) {
1079-
try {
1080-
await loadPermissionsFile(permissionsPath);
1081-
} catch (err) {
1082-
throw new CliUserError(err instanceof Error ? err.message : String(err));
1083-
}
1084-
}
1085-
1086-
const pricingCachePath = defaultPricingCachePath();
1087-
1088-
await bootstrapPricingMetadata({
1089-
cachePath: pricingCachePath,
1090-
...options.pricing,
1091-
});
1092-
10931083
// Resolve both settings targets from the same active source. The local schema
10941084
// must never be read from or written to the active global-schema target.
10951085
const effectiveSettingsPath =
@@ -1106,22 +1096,74 @@ export async function loadConfig(
11061096
// unauthenticated. Only the programmatic `globalSettingsPath` test override
11071097
// (never a CLI flag) opts out — tests want no home-directory reads.
11081098
const useOAuthProfiles = options.globalSettingsPath === undefined;
1109-
const [codexProfiles, xaiProfiles]: [CodexProfile[], XaiProfile[]] =
1099+
1100+
// These files do not decide precedence for each other. Overlap the reads,
1101+
// then apply results in the previous order: permissions error, pricing
1102+
// install, OAuth error, settings warnings, local settings, profile.
1103+
const permissionsTask: Promise<void> =
1104+
permissionsPath !== undefined
1105+
? loadPermissionsFile(permissionsPath).then(
1106+
() => undefined,
1107+
(err: unknown) => {
1108+
throw new CliUserError(
1109+
err instanceof Error ? err.message : String(err),
1110+
);
1111+
},
1112+
)
1113+
: Promise.resolve();
1114+
const pricingOptions: PricingFetcherOptions = {
1115+
cachePath: defaultPricingCachePath(),
1116+
...options.pricing,
1117+
};
1118+
startPricingCacheRead(pricingOptions.cachePath);
1119+
const oauthTask: Promise<readonly [CodexProfile[], XaiProfile[]]> =
11101120
useOAuthProfiles
1111-
? await Promise.all([listCodexProfiles(), listXaiProfiles()])
1112-
: [[], []];
1121+
? Promise.all([listCodexProfiles(), listXaiProfiles()])
1122+
: Promise.resolve([[], []]);
1123+
const settingsPath = configPath ?? effectiveSettingsPath;
1124+
const settingsDocumentTask = readSettingsDocument(settingsPath);
1125+
const localTask =
1126+
localSettingsFile === null
1127+
? Promise.resolve({
1128+
settings: null,
1129+
diagnostics: [] as SettingsLoadDiagnostic[],
1130+
})
1131+
: loadLocalSettingsResult(localSettingsFile);
1132+
const profileTask = resolveProfile(cwd, profileFlag);
1133+
1134+
const [
1135+
permissionsResult,
1136+
oauthResult,
1137+
settingsDocumentResult,
1138+
localResultSettled,
1139+
profileResult,
1140+
] = await Promise.allSettled([
1141+
permissionsTask,
1142+
oauthTask,
1143+
settingsDocumentTask,
1144+
localTask,
1145+
profileTask,
1146+
]);
1147+
1148+
if (permissionsResult.status === "rejected") throw permissionsResult.reason;
1149+
await bootstrapPricingMetadata(pricingOptions);
1150+
if (oauthResult.status === "rejected") throw oauthResult.reason;
1151+
if (settingsDocumentResult.status === "rejected") {
1152+
throw settingsDocumentResult.reason;
1153+
}
1154+
1155+
const [codexProfiles, xaiProfiles] = oauthResult.value;
11131156
let projectedOAuthProviders = {
11141157
...codexProvidersAsSettings(codexProfiles),
11151158
...xaiProvidersAsSettings(xaiProfiles),
11161159
};
1160+
const parsedDocument = { value: settingsDocumentResult.value };
11171161
const settings =
11181162
configPath !== undefined
11191163
? await loadSettingsRecoveringClobberedOAuthSelection(
11201164
configPath,
11211165
projectedOAuthProviders,
1122-
{
1123-
persist: false,
1124-
},
1166+
{ persist: false, parsedDocument },
11251167
).then((s) => {
11261168
if (s === null)
11271169
throw new Error(`--config file not found or empty: ${configPath}`);
@@ -1130,8 +1172,15 @@ export async function loadConfig(
11301172
: await loadSettingsRecoveringClobberedOAuthSelection(
11311173
effectiveSettingsPath,
11321174
projectedOAuthProviders,
1133-
{ persist: true },
1175+
{ persist: true, parsedDocument },
11341176
);
1177+
// --config does not load the global file, so do not reuse that document for
1178+
// the later global telemetry read. Do not overlap that read with the heal
1179+
// write above: both target settings.json and the later read must see the
1180+
// persisted document, which is what this snapshot is.
1181+
if (configPath === undefined) {
1182+
rememberBootGlobalSettings(effectiveSettingsPath, settings);
1183+
}
11351184

11361185
// True when the value came from settings, not this invocation's
11371186
// --dangerously-skip-permissions flag (Config.skipPermissionsFromSettings).
@@ -1156,14 +1205,12 @@ export async function loadConfig(
11561205
// supplies provider definitions, .corbits/settings.json supplies the
11571206
// provider/model selection; CLI --provider/--model override both. Fail open
11581207
// on unknown/invalid local keys — never crash startup.
1159-
const localResult =
1160-
localSettingsFile === null
1161-
? { settings: null, diagnostics: [] }
1162-
: await loadLocalSettingsResult(localSettingsFile);
1208+
if (localResultSettled.status === "rejected") throw localResultSettled.reason;
1209+
if (profileResult.status === "rejected") throw profileResult.reason;
1210+
const localResult = localResultSettled.value;
11631211
const local = localResult.settings;
11641212
const settingsDiagnostics = localResult.diagnostics;
1165-
1166-
const profile = await resolveProfile(cwd, profileFlag);
1213+
const profile = profileResult.value;
11671214

11681215
// Apply profile as a fallback layer: profile.model fills in when neither CLI
11691216
// nor local settings specify a model. This sits below local in precedence.

‎src/config/profiles.ts‎

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,14 +82,34 @@ export async function resolveProfile(
8282
profileName?: string,
8383
home: string = homedir(),
8484
): Promise<ProfileConfig> {
85-
const projectProfile = await loadProfile(projectProfilePath(cwd));
85+
const projectPromise = loadProfile(projectProfilePath(cwd));
86+
// The named file is known only once the CLI flag or the project profile
87+
// says so. Overlap the named read when the flag already names it. When the
88+
// name comes from project profile.json, reading a guessed file would not
89+
// change the winner — there is nothing to guess — so that read stays after
90+
// the project file.
91+
const namedPromise =
92+
profileName !== undefined
93+
? loadProfile(join(profilesDir(home), `${profileName}.json`))
94+
: undefined;
95+
96+
let projectProfile: ProfileConfig | null;
97+
try {
98+
projectProfile = await projectPromise;
99+
} catch (err) {
100+
if (namedPromise !== undefined) await namedPromise.catch(() => undefined);
101+
throw err;
102+
}
86103

87104
const namedProfileName = profileName ?? projectProfile?.profile;
88105

89106
let namedProfile: ProfileConfig | null = null;
90107
if (namedProfileName !== undefined) {
91108
const namedPath = join(profilesDir(home), `${namedProfileName}.json`);
92-
namedProfile = await loadProfile(namedPath);
109+
namedProfile =
110+
namedPromise !== undefined
111+
? await namedPromise
112+
: await loadProfile(namedPath);
93113
if (namedProfile === null) {
94114
throw new Error(`Profile not found: ${namedPath}`);
95115
}

‎src/config/settings.ts‎

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -986,7 +986,22 @@ function recoverClobberedOAuthSelection(
986986
};
987987
}
988988

989+
let settingsDocumentReads = 0;
990+
let bootGlobalSettings: { path: string; settings: Settings | null } | undefined;
991+
992+
/** How many times settings JSON has been read from disk. */
993+
export function settingsDocumentReadCountForTests(): number {
994+
return settingsDocumentReads;
995+
}
996+
997+
/** Test-only: clear the boot settings snapshot and the read counter. */
998+
export function resetSettingsDocumentReadCountForTests(): void {
999+
settingsDocumentReads = 0;
1000+
bootGlobalSettings = undefined;
1001+
}
1002+
9891003
async function loadSettingsJSON(path: string): Promise<unknown | null> {
1004+
settingsDocumentReads += 1;
9901005
let raw: string;
9911006
try {
9921007
raw = await readFile(path, "utf8");
@@ -1001,6 +1016,37 @@ async function loadSettingsJSON(path: string): Promise<unknown | null> {
10011016
}
10021017
}
10031018

1019+
/** Read a settings file once so boot can overlap the disk read with other I/O. */
1020+
export async function readSettingsDocument(
1021+
path: string,
1022+
): Promise<unknown | null> {
1023+
return loadSettingsJSON(path);
1024+
}
1025+
1026+
/**
1027+
* Remember the on-disk settings document loadConfig just resolved so startup
1028+
* telemetry does not read settings.json again. The value is the persisted
1029+
* document, not the OAuth overlay.
1030+
*/
1031+
export function rememberBootGlobalSettings(
1032+
path: string,
1033+
settings: Settings | null,
1034+
): void {
1035+
bootGlobalSettings = {
1036+
path,
1037+
settings: settings === null ? null : structuredClone(settings),
1038+
};
1039+
}
1040+
1041+
function takeBootGlobalSettings(path: string): Settings | null | undefined {
1042+
if (bootGlobalSettings === undefined || bootGlobalSettings.path !== path) {
1043+
return undefined;
1044+
}
1045+
const settings = bootGlobalSettings.settings;
1046+
bootGlobalSettings = undefined;
1047+
return settings;
1048+
}
1049+
10041050
function settingsSchemaError(path: string): Error {
10051051
return new Error(
10061052
`Invalid settings schema in ${path}: expected { providers: { <name>: { baseURL, apiKey, models: [...] } } }`,
@@ -1160,10 +1206,13 @@ export async function loadSettings(path: string): Promise<Settings | null> {
11601206
export async function loadSettingsRecoveringClobberedOAuthSelection(
11611207
path: string,
11621208
recoverableOAuthProviders: Record<string, ProviderSettings>,
1163-
options: { persist: boolean },
1209+
options: { persist: boolean; parsedDocument?: { value: unknown | null } },
11641210
): Promise<Settings | null> {
1165-
const parsed = await loadSettingsJSON(path);
1166-
if (parsed === null) return null;
1211+
const parsed =
1212+
options.parsedDocument !== undefined
1213+
? options.parsedDocument.value
1214+
: await loadSettingsJSON(path);
1215+
if (parsed === null || parsed === undefined) return null;
11671216
if (isClobberedLocalSelection(parsed)) {
11681217
const recovered = recoverClobberedOAuthSelection(
11691218
parsed,
@@ -1466,9 +1515,18 @@ export async function markLastChangelogVersion(
14661515
}
14671516

14681517
// Ensure a persisted telemetry installationId exists, generating one on first
1469-
// use. Reads on-disk settings fresh (same rationale as markOnboarded).
1470-
export async function ensureTelemetrySettings(path: string): Promise<Settings> {
1471-
const onDisk = await loadSettings(path);
1518+
// use. Callers that already loaded this path at boot pass reuseBootSnapshot so
1519+
// the file is not read again. The snapshot is the persisted document, never the
1520+
// OAuth overlay. Other callers still read disk (same rationale as markOnboarded).
1521+
export async function ensureTelemetrySettings(
1522+
path: string,
1523+
options?: { reuseBootSnapshot?: boolean },
1524+
): Promise<Settings> {
1525+
const cached =
1526+
options?.reuseBootSnapshot === true
1527+
? takeBootGlobalSettings(path)
1528+
: undefined;
1529+
const onDisk = cached !== undefined ? cached : await loadSettings(path);
14721530
const base: Settings = onDisk ?? { providers: {} };
14731531
// Read-then-write, not read-then-lock: concurrent first launches could each
14741532
// generate a different installationId and the second save wins. Accepted —

0 commit comments

Comments
 (0)