mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 00:02:31 +00:00
Seed WSL Codex runtime config through the fresh-mirror preparation (#7343)
* Seed WSL Codex runtime config through the fresh-mirror preparation The WSL runtime home seed copied config.toml verbatim, so relative path-valued settings (model_instructions_file etc.) resolved against the runtime home inside the distro and aborted Codex config load with 'os error 2' -- the same failure class #7157 fixed for host runtime and managed account homes. Seed now applies the shared fresh-mirror preparation (deprecated codex_hooks normalization + relative-path rewrite anchored to the Linux-side source home + system hook-trust strip). Also fixes CRLF configs skipping deprecated codex_hooks normalization: the feature-section header regexes did not tolerate the trailing \r. Co-authored-by: Orca <help@stably.ai> * Extract prepareWslRuntimeSeedConfig and pin UNC->Linux anchor in tests The e2e seed test's mocked WSL home is a plain local path, so the parseWslUncPath linuxPath branch was never exercised. Extract the seed preparation as a pure exported function and cover both UNC spellings (wsl.localhost and wsl$) with hardcoded literals. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -1324,6 +1324,86 @@ describe('CodexRuntimeHomeService', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('seeds the WSL runtime config with rewritten paths and no system hook trust', async () => {
|
||||
const originalPlatform = Object.getOwnPropertyDescriptor(process, 'platform')
|
||||
Object.defineProperty(process, 'platform', { configurable: true, value: 'win32' })
|
||||
const wslHome = join(testState.userDataDir, 'wsl-home')
|
||||
vi.doMock('../wsl', () => ({
|
||||
getDefaultWslDistro: () => 'Ubuntu',
|
||||
getWslHome: () => wslHome
|
||||
}))
|
||||
const systemCodexHomePath = join(wslHome, '.codex')
|
||||
mkdirSync(systemCodexHomePath, { recursive: true })
|
||||
writeFileSync(
|
||||
join(systemCodexHomePath, 'config.toml'),
|
||||
[
|
||||
'model_instructions_file = "instructions.md"',
|
||||
'',
|
||||
'[hooks.state."system-hooks:stop:0:0"]',
|
||||
'enabled = true',
|
||||
'',
|
||||
'[projects."/home/alice/repo"]',
|
||||
'trust_level = "trusted"',
|
||||
''
|
||||
].join('\n'),
|
||||
'utf-8'
|
||||
)
|
||||
const store = createStore(createSettings())
|
||||
|
||||
try {
|
||||
const { CodexRuntimeHomeService } = await import('./runtime-home-service')
|
||||
const service = new CodexRuntimeHomeService(store as never)
|
||||
const wslRuntimeHomePath = join(
|
||||
wslHome,
|
||||
'.local',
|
||||
'share',
|
||||
'orca',
|
||||
'codex-runtime-home',
|
||||
'home'
|
||||
)
|
||||
|
||||
expect(service.prepareForCodexLaunch({ runtime: 'wsl', wslDistro: 'Ubuntu' })).toBe(
|
||||
wslRuntimeHomePath
|
||||
)
|
||||
const runtimeConfigPath = join(wslRuntimeHomePath, 'config.toml')
|
||||
const runtimeConfig = readFileSync(runtimeConfigPath, 'utf-8')
|
||||
expect(runtimeConfig).toContain(
|
||||
`model_instructions_file = '${join(systemCodexHomePath, 'instructions.md')}'`
|
||||
)
|
||||
expect(runtimeConfig).toContain('[projects."/home/alice/repo"]')
|
||||
expect(runtimeConfig).not.toContain('[hooks.state.')
|
||||
|
||||
// Why: WSL runtime configs are seeded once; Codex writes trust into them
|
||||
// afterwards, so a relaunch must not clobber the seeded file.
|
||||
writeFileSync(runtimeConfigPath, `${runtimeConfig}\n[projects."/tmp/x"]\n`, 'utf-8')
|
||||
service.prepareForCodexLaunch({ runtime: 'wsl', wslDistro: 'Ubuntu' })
|
||||
expect(readFileSync(runtimeConfigPath, 'utf-8')).toContain('[projects."/tmp/x"]')
|
||||
} finally {
|
||||
if (originalPlatform) {
|
||||
Object.defineProperty(process, 'platform', originalPlatform)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('anchors WSL seed rewrites to the Linux-side home parsed from the UNC source', async () => {
|
||||
const { prepareWslRuntimeSeedConfig } = await import('./runtime-home-service')
|
||||
|
||||
// Why: real UNC sources cannot back live fs operations in tests, so pin
|
||||
// the UNC -> Linux-side anchor translation on the extracted seed function.
|
||||
expect(
|
||||
prepareWslRuntimeSeedConfig(
|
||||
'model_instructions_file = "instructions.md"\n',
|
||||
'\\\\wsl.localhost\\Ubuntu\\home\\alice\\.codex'
|
||||
)
|
||||
).toContain("model_instructions_file = '/home/alice/.codex/instructions.md'")
|
||||
expect(
|
||||
prepareWslRuntimeSeedConfig(
|
||||
'model_instructions_file = "instructions.md"\n',
|
||||
'\\\\wsl$\\Ubuntu\\home\\alice\\.codex'
|
||||
)
|
||||
).toContain("model_instructions_file = '/home/alice/.codex/instructions.md'")
|
||||
})
|
||||
|
||||
it('switches WSL accounts by rewriting one stable WSL runtime home', async () => {
|
||||
const originalPlatform = Object.getOwnPropertyDescriptor(process, 'platform')
|
||||
Object.defineProperty(process, 'platform', { configurable: true, value: 'win32' })
|
||||
|
||||
@@ -40,7 +40,10 @@ import {
|
||||
syncSystemCodexResourcesIntoManagedHome
|
||||
} from '../codex/codex-home-paths'
|
||||
import { startSystemCodexSessionBridgeInBackground } from '../codex/codex-session-bridge'
|
||||
import { syncSystemConfigIntoManagedCodexHome } from '../codex/codex-config-mirror'
|
||||
import {
|
||||
prepareSystemConfigForFreshRuntimeMirror,
|
||||
syncSystemConfigIntoManagedCodexHome
|
||||
} from '../codex/codex-config-mirror'
|
||||
import { parseWslUncPath } from '../../shared/wsl-paths'
|
||||
import {
|
||||
getWslSelectionKey,
|
||||
@@ -705,7 +708,10 @@ export class CodexRuntimeHomeService {
|
||||
for (const homePath of candidateHomes) {
|
||||
const configPath = join(homePath, 'config.toml')
|
||||
if (existsSync(configPath)) {
|
||||
copyFileSync(configPath, runtimeConfigPath)
|
||||
writeFileAtomically(
|
||||
runtimeConfigPath,
|
||||
prepareWslRuntimeSeedConfig(readFileSync(configPath, 'utf-8'), homePath)
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -1562,3 +1568,16 @@ export class CodexRuntimeHomeService {
|
||||
rmSync(this.getSystemDefaultSnapshotPath(), { force: true })
|
||||
}
|
||||
}
|
||||
|
||||
// Why: the seed config is read over UNC but consumed by Codex inside WSL, so
|
||||
// relative path-valued settings must anchor to the Linux-side source home; a
|
||||
// verbatim copy breaks Codex config load (os error 2).
|
||||
export function prepareWslRuntimeSeedConfig(
|
||||
configContents: string,
|
||||
sourceHomePath: string
|
||||
): string {
|
||||
return prepareSystemConfigForFreshRuntimeMirror(
|
||||
configContents,
|
||||
parseWslUncPath(sourceHomePath)?.linuxPath ?? sourceHomePath
|
||||
)
|
||||
}
|
||||
|
||||
@@ -23,7 +23,10 @@ vi.mock('node:os', async () => {
|
||||
}
|
||||
})
|
||||
|
||||
import { syncSystemConfigIntoManagedCodexHome } from './codex-config-mirror'
|
||||
import {
|
||||
prepareSystemConfigForFreshRuntimeMirror,
|
||||
syncSystemConfigIntoManagedCodexHome
|
||||
} from './codex-config-mirror'
|
||||
|
||||
let fakeHomeDir: string
|
||||
let userDataDir: string
|
||||
@@ -397,3 +400,32 @@ describe('syncSystemConfigIntoManagedCodexHome', () => {
|
||||
expect(existsSync(getRuntimeConfigPath())).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('prepareSystemConfigForFreshRuntimeMirror', () => {
|
||||
it('rewrites relative paths against a Linux-side home and strips hook trust', () => {
|
||||
const prepared = prepareSystemConfigForFreshRuntimeMirror(
|
||||
[
|
||||
'model_instructions_file = "instructions.md"',
|
||||
'',
|
||||
'[features]',
|
||||
'codex_hooks = true',
|
||||
'',
|
||||
'[hooks.state."system-hooks:stop:0:0"]',
|
||||
'enabled = true',
|
||||
'',
|
||||
'[projects."/home/alice/repo"]',
|
||||
'trust_level = "trusted"',
|
||||
''
|
||||
].join('\r\n'),
|
||||
'/home/alice/.codex'
|
||||
)
|
||||
|
||||
// Why: WSL configs are consumed inside the distro, so rewrites must use
|
||||
// posix join semantics regardless of the host platform.
|
||||
expect(prepared).toContain("model_instructions_file = '/home/alice/.codex/instructions.md'")
|
||||
expect(prepared).toContain('hooks = true')
|
||||
expect(prepared).not.toContain('codex_hooks')
|
||||
expect(prepared).toContain('[projects."/home/alice/repo"]')
|
||||
expect(prepared).not.toContain('[hooks.state."system-hooks:stop:0:0"]')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -35,17 +35,19 @@ function syncSystemConfigIntoManagedCodexHomeUnsafe(): void {
|
||||
return
|
||||
}
|
||||
|
||||
const systemConfig = prepareSystemConfigForRuntimeMirror(
|
||||
systemConfigExists ? readFileSync(systemConfigPath, 'utf-8') : '',
|
||||
dirname(systemConfigPath)
|
||||
)
|
||||
const rawSystemConfig = systemConfigExists ? readFileSync(systemConfigPath, 'utf-8') : ''
|
||||
if (!runtimeConfigExists) {
|
||||
// Why: trust blocks reference a hooks.json path, so system-home hook trust
|
||||
// entries are not valid in Orca's runtime CODEX_HOME until install remaps them.
|
||||
writeFileAtomically(runtimeConfigPath, stripRuntimeOwnedTomlSections(systemConfig))
|
||||
writeFileAtomically(
|
||||
runtimeConfigPath,
|
||||
prepareSystemConfigForFreshRuntimeMirror(rawSystemConfig, dirname(systemConfigPath))
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const systemConfig = prepareSystemConfigForRuntimeMirror(
|
||||
rawSystemConfig,
|
||||
dirname(systemConfigPath)
|
||||
)
|
||||
const runtimeConfig = readFileSync(runtimeConfigPath, 'utf-8')
|
||||
const mergedConfig = mergeSystemCodexConfigIntoRuntime(runtimeConfig, systemConfig)
|
||||
if (mergedConfig !== runtimeConfig) {
|
||||
@@ -60,6 +62,17 @@ function prepareSystemConfigForRuntimeMirror(config: string, systemConfigDir: st
|
||||
)
|
||||
}
|
||||
|
||||
// Why: trust blocks reference a hooks.json path, so system-home hook trust
|
||||
// entries are not valid in a fresh runtime CODEX_HOME until install remaps
|
||||
// them. Also seeds WSL runtime homes, where systemConfigDir must be the
|
||||
// Linux-side ~/.codex the config resolves against inside the distro.
|
||||
export function prepareSystemConfigForFreshRuntimeMirror(
|
||||
config: string,
|
||||
systemConfigDir: string
|
||||
): string {
|
||||
return stripRuntimeOwnedTomlSections(prepareSystemConfigForRuntimeMirror(config, systemConfigDir))
|
||||
}
|
||||
|
||||
function normalizeDeprecatedCodexHookFeatureFlag(config: string): string {
|
||||
if (!config.includes('codex_hooks')) {
|
||||
return config
|
||||
@@ -71,7 +84,9 @@ function normalizeDeprecatedCodexHookFeatureFlag(config: string): string {
|
||||
|
||||
for (let index = 0; index <= lines.length; index += 1) {
|
||||
const line = lines[index]
|
||||
const isHeader = line === undefined || /^[ \t]*\[[^\]]+\][ \t]*(?:#.*)?$/.test(line)
|
||||
// Why: CRLF configs keep a trailing \r after the split, so header anchors
|
||||
// must tolerate it or Windows-shaped configs skip normalization entirely.
|
||||
const isHeader = line === undefined || /^[ \t]*\[[^\]]+\][ \t]*(?:#.*)?\r?$/.test(line)
|
||||
if (!isHeader) {
|
||||
continue
|
||||
}
|
||||
@@ -80,7 +95,7 @@ function normalizeDeprecatedCodexHookFeatureFlag(config: string): string {
|
||||
featureSections.push({ start: featureStart, end: index })
|
||||
featureStart = null
|
||||
}
|
||||
if (line !== undefined && /^[ \t]*\[features\][ \t]*(?:#.*)?$/.test(line)) {
|
||||
if (line !== undefined && /^[ \t]*\[features\][ \t]*(?:#.*)?\r?$/.test(line)) {
|
||||
featureStart = index
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user