fix(wsl): restore Windows interop after another distro wipes it (#27682)

* fix(wsl): detect and restore a wiped WSLInterop entry in running distros

On WSL < 3.0.1 a graceful stop of any systemd distro deletes every
binfmt_misc entry VM-wide, so Windows .exe launches (code ., explorer.exe,
the orca WSL CLI bridge) fail with Exec format error in every other distro.

Check, never boot: 5 s after a WSL terminal env build (gated on the distro
being in the confirmed running list, at most once a minute per distro), and
whenever a confirmed running-distro probe shows another distro stopped.
Repair is automatic and idempotent as root, skipped when the distro's
wsl.conf sets [interop] enabled=false, single-flight per distro, with a
10 minute backoff after a failed repair.

* fix(wsl): repair interop with a single register write matching WSL's flags

Busybox printf writes a trailing newline separately; the kernel registers the
entry, then rejects the lone newline, so the repair reported failure after
succeeding. Write without the newline and let the re-check decide. Use WSL's
own P flags rather than PF, and treat an entry restored between probe and
repair as intact. Add a spawn-budget test.
This commit is contained in:
Neil
2026-10-11 19:16:47 -07:00
committed by GitHub
parent a3e97184f4
commit d8ebe75efe
10 changed files with 724 additions and 4 deletions
+2
View File
@@ -8,6 +8,7 @@ import { ensureOpenCodeStartupPromptForLaunch } from '../../../opencode/opencode
import { mimoCodeHookService } from '../../../mimo/hook-service'
import { agentHookServer } from '../../../agent-hooks/server'
import { wslHookRelayManager } from '../../../agent-hooks/wsl-hook-relay-manager'
import { wslInteropHealthMonitor } from '../../../wsl/wsl-interop-health'
import { piTitlebarExtensionService } from '../../../pi/titlebar-extension-service'
import { prependOrcaCliDirToChildPath } from '../../../cli/orca-cli-child-path'
import { getManagedWslCliDir, getWslCliCommandName } from '../../../cli/wsl-managed-cli'
@@ -266,6 +267,7 @@ export function buildPtyHostEnv(
delete baseEnv.ORCA_WSL_CLI_DIR
// Why: WSL shells need the managed userData root for shell-ready wrappers; dev-mode terminals need the same export so `orca` targets the live dev instance.
if (opts.isWsl) {
wslInteropHealthMonitor.noteActivity(opts.wslDistro ?? null)
baseEnv.ORCA_USER_DATA_PATH = opts.userDataPath
// Why: managed WSL registration uses `orca-ide`; exposing that literal scopes agent guidance to WSL without a bare-orca shim.
baseEnv.ORCA_CLI_COMMAND = getWslCliCommandName(opts.isPackaged)
+28
View File
@@ -10,6 +10,32 @@ let retryAfterMs = 0
let failureStreak = 0
type RunningDistroObservation = { distros: string[]; confirmed: boolean }
let inFlightProbe: Promise<RunningDistroObservation> | null = null
type RunningDistroStopListener = (stopped: string[], running: string[]) => void
const stopListeners = new Set<RunningDistroStopListener>()
/** Notified when a confirmed probe no longer lists a distro the previous one did. */
export function onRunningWslDistrosStopped(listener: RunningDistroStopListener): () => void {
stopListeners.add(listener)
return () => stopListeners.delete(listener)
}
function notifyStopped(previous: string[] | null, running: string[]): void {
if (!previous || stopListeners.size === 0) {
return
}
const current = new Set(running.map((distro) => distro.toLowerCase()))
const stopped = previous.filter((distro) => !current.has(distro.toLowerCase()))
if (stopped.length === 0) {
return
}
for (const listener of stopListeners) {
try {
listener(stopped, running)
} catch (error) {
console.warn('[wsl] running-distro stop listener failed', error)
}
}
}
function armRetryAfterFailure(): void {
const now = Date.now()
@@ -51,9 +77,11 @@ function observeRunningWslDistros(
}
const result = probe()
.then((distros) => {
const previous = cache
cache = distros
retryAfterMs = 0
failureStreak = 0
notifyStopped(previous, distros)
return { distros, confirmed: true }
})
.catch((error: unknown) => {
+35
View File
@@ -0,0 +1,35 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import {
_resetRunningWslDistroCacheForTests,
onRunningWslDistrosStopped,
resolveRunningWslDistros
} from './wsl-running-distro-cache'
describe('onRunningWslDistrosStopped', () => {
afterEach(() => {
_resetRunningWslDistroCacheForTests()
vi.restoreAllMocks()
})
it('reports distros a confirmed probe no longer lists', async () => {
const listener = vi.fn()
const unsubscribe = onRunningWslDistrosStopped(listener)
await resolveRunningWslDistros(async () => ['Ubuntu', 'docker-desktop'])
expect(listener).not.toHaveBeenCalled()
await resolveRunningWslDistros(async () => ['ubuntu'])
expect(listener).toHaveBeenCalledWith(['docker-desktop'], ['ubuntu'])
unsubscribe()
})
it('ignores failed probes', async () => {
const listener = vi.fn()
const unsubscribe = onRunningWslDistrosStopped(listener)
await resolveRunningWslDistros(async () => ['Ubuntu'])
vi.spyOn(console, 'warn').mockImplementation(() => undefined)
await resolveRunningWslDistros(async () => {
throw new Error('wsl.exe wedged')
})
expect(listener).not.toHaveBeenCalled()
unsubscribe()
})
})
+243
View File
@@ -0,0 +1,243 @@
import { describe, expect, it, vi } from 'vitest'
vi.mock('../wsl', () => ({
listRunningWslDistrosAsync: vi.fn(),
listWslDistrosAsync: vi.fn()
}))
vi.mock('./wsl-runner', () => ({ runWslProcess: vi.fn() }))
import {
ACTIVITY_CHECK_DELAY_MS,
ACTIVITY_CHECK_INTERVAL_MS,
REPAIR_FAILURE_BACKOFF_MS,
STOP_CHECK_FLOOR_MS,
WslInteropHealthMonitor,
type WslInteropHealthDeps
} from './wsl-interop-health'
import { WSL_INTEROP_PROBE_SCRIPT, WSL_INTEROP_REPAIR_SCRIPT } from './wsl-interop-registration'
const PRESENT = 'orca-wsl-interop:present\n'
const MISSING = 'orca-wsl-interop:missing\norca-wsl-interop:conf\n'
const MISSING_DISABLED = `${MISSING}[interop]\nenabled=false\n`
function harness(overrides: Partial<WslInteropHealthDeps> = {}) {
let now = 1_000_000
const timers: { fn: () => void; delayMs: number }[] = []
let stopListener: ((stopped: string[], running: string[]) => void) | null = null
const deps: WslInteropHealthDeps = {
platform: () => 'win32',
listRunningDistros: vi.fn(async () => ['Ubuntu']),
resolveDefaultDistro: vi.fn(async () => 'Ubuntu'),
runScript: vi.fn(async () => ({ code: 0, stdout: PRESENT })),
subscribeStops: (listener) => {
stopListener = listener
return () => {
stopListener = null
}
},
now: () => now,
schedule: (fn, delayMs) => {
timers.push({ fn, delayMs })
},
log: vi.fn(),
...overrides
}
const monitor = new WslInteropHealthMonitor(deps)
return {
deps,
monitor,
timers,
advance: (ms: number) => {
now += ms
},
runTimers: () => {
for (const timer of timers.splice(0)) {
timer.fn()
}
},
emitStop: (stopped: string[], running: string[]) => stopListener?.(stopped, running)
}
}
const flush = () => new Promise((resolve) => setTimeout(resolve, 0))
describe('WslInteropHealthMonitor.check', () => {
it('reports intact without repairing', async () => {
const h = harness()
await expect(h.monitor.check('Ubuntu', { confirmRunning: true })).resolves.toBe('intact')
expect(h.deps.runScript).toHaveBeenCalledTimes(1)
expect(h.deps.runScript).toHaveBeenCalledWith('Ubuntu', WSL_INTEROP_PROBE_SCRIPT)
})
it('repairs as root when the entry is missing', async () => {
const runScript = vi
.fn()
.mockResolvedValueOnce({ code: 0, stdout: MISSING })
.mockResolvedValueOnce({ code: 0, stdout: 'orca-wsl-interop:repaired\n' })
const h = harness({ runScript })
await expect(h.monitor.check('Ubuntu', { confirmRunning: true })).resolves.toBe('repaired')
expect(runScript).toHaveBeenLastCalledWith('Ubuntu', WSL_INTEROP_REPAIR_SCRIPT, 'root')
expect(h.deps.log).toHaveBeenCalledWith(expect.stringContaining('wsl --update'))
})
it('respects interop disabled in wsl.conf', async () => {
const runScript = vi.fn(async () => ({ code: 0, stdout: MISSING_DISABLED }))
const h = harness({ runScript })
await expect(h.monitor.check('Ubuntu', { confirmRunning: true })).resolves.toBe(
'interop-disabled'
)
expect(runScript).toHaveBeenCalledTimes(1)
})
it('never runs inside a distro that is not running', async () => {
const h = harness({ listRunningDistros: vi.fn(async () => ['Debian']) })
await expect(h.monitor.check('Ubuntu', { confirmRunning: true })).resolves.toBe('not-running')
expect(h.deps.runScript).not.toHaveBeenCalled()
})
it('treats an unconfirmed running list as not running', async () => {
const h = harness({
listRunningDistros: vi.fn(async () => {
throw new Error('unavailable')
})
})
await expect(h.monitor.check('Ubuntu', { confirmRunning: true })).resolves.toBe('not-running')
expect(h.deps.runScript).not.toHaveBeenCalled()
})
it('is single-flight per distro', async () => {
let release: (value: { code: number; stdout: string }) => void = () => undefined
const runScript = vi.fn(
() =>
new Promise<{ code: number; stdout: string }>((resolve) => {
release = resolve
})
)
const h = harness({ runScript })
const first = h.monitor.check('Ubuntu', { confirmRunning: false })
const second = h.monitor.check('ubuntu', { confirmRunning: false })
expect(second).toBe(first)
await flush()
release({ code: 0, stdout: PRESENT })
await expect(first).resolves.toBe('intact')
expect(runScript).toHaveBeenCalledTimes(1)
})
it('backs off after a failed repair', async () => {
const runScript = vi.fn(async (_distro: string, script: string) =>
script === WSL_INTEROP_PROBE_SCRIPT ? { code: 0, stdout: MISSING } : { code: 1, stdout: '' }
)
const h = harness({ runScript })
await expect(h.monitor.check('Ubuntu', { confirmRunning: false })).resolves.toBe(
'repair-failed'
)
await expect(h.monitor.check('Ubuntu', { confirmRunning: false })).resolves.toBe(
'repair-failed'
)
expect(runScript).toHaveBeenCalledTimes(3)
h.advance(REPAIR_FAILURE_BACKOFF_MS)
await h.monitor.check('Ubuntu', { confirmRunning: false })
expect(runScript).toHaveBeenCalledTimes(5)
})
it('reports unknown when the probe throws', async () => {
const h = harness({
runScript: vi.fn(async () => {
throw new Error('timeout')
})
})
await expect(h.monitor.check('Ubuntu', { confirmRunning: false })).resolves.toBe('unknown')
})
})
describe('WslInteropHealthMonitor triggers', () => {
it('does nothing off Windows', async () => {
const h = harness({ platform: () => 'darwin' })
h.monitor.noteActivity('Ubuntu')
await flush()
expect(h.timers).toHaveLength(0)
})
it('checks once after a delay and throttles repeated activity', async () => {
const h = harness()
h.monitor.noteActivity(null)
h.monitor.noteActivity('Ubuntu')
await flush()
expect(h.timers).toEqual([{ fn: expect.any(Function), delayMs: ACTIVITY_CHECK_DELAY_MS }])
expect(h.deps.runScript).not.toHaveBeenCalled()
h.runTimers()
await flush()
expect(h.deps.listRunningDistros).toHaveBeenCalledTimes(1)
expect(h.deps.runScript).toHaveBeenCalledTimes(1)
h.monitor.noteActivity('Ubuntu')
await flush()
expect(h.timers).toHaveLength(0)
h.advance(ACTIVITY_CHECK_INTERVAL_MS)
h.monitor.noteActivity('Ubuntu')
await flush()
expect(h.timers).toHaveLength(1)
})
it('re-checks used distros that are still running when another distro stops', async () => {
const h = harness()
h.monitor.noteActivity('Ubuntu')
await flush()
h.runTimers()
await flush()
expect(h.deps.runScript).toHaveBeenCalledTimes(1)
h.advance(10_000)
h.emitStop(['docker-desktop'], ['Ubuntu'])
await flush()
expect(h.deps.runScript).toHaveBeenCalledTimes(2)
// The confirmed list that reported the stop already proves Ubuntu is up.
expect(h.deps.listRunningDistros).toHaveBeenCalledTimes(1)
h.advance(10_000)
h.emitStop(['Ubuntu'], [])
await flush()
expect(h.deps.runScript).toHaveBeenCalledTimes(2)
})
it('bounds probes under bursts of activity and stop events', async () => {
const h = harness()
for (let i = 0; i < 50; i++) {
h.monitor.noteActivity('Ubuntu')
}
await flush()
h.runTimers()
await flush()
for (let elapsed = 0; elapsed < ACTIVITY_CHECK_INTERVAL_MS - 1_000; elapsed += 1_000) {
h.advance(1_000)
h.monitor.noteActivity('Ubuntu')
await flush()
h.runTimers()
await flush()
}
expect(h.deps.runScript).toHaveBeenCalledTimes(1)
// Stop-triggered checks are rate-floored and skip distros the list no longer shows.
h.emitStop(['docker-desktop'], ['Ubuntu'])
await flush()
expect(h.deps.runScript).toHaveBeenCalledTimes(2)
h.advance(STOP_CHECK_FLOOR_MS - 1)
h.emitStop(['Debian'], ['Ubuntu'])
h.emitStop(['Debian'], ['Ubuntu'])
await flush()
expect(h.deps.runScript).toHaveBeenCalledTimes(2)
h.advance(1)
h.emitStop(['Ubuntu'], [])
await flush()
expect(h.deps.runScript).toHaveBeenCalledTimes(2)
})
it('treats an entry restored between probe and repair as intact', async () => {
const h = harness({
runScript: vi.fn(async (_distro: string, script: string) => ({
code: 0,
stdout: script === WSL_INTEROP_PROBE_SCRIPT ? MISSING : PRESENT
}))
})
await expect(h.monitor.check('Ubuntu', { confirmRunning: false })).resolves.toBe('intact')
expect(h.deps.log).not.toHaveBeenCalled()
})
})
+220
View File
@@ -0,0 +1,220 @@
import { listRunningWslDistrosAsync, listWslDistrosAsync } from '../wsl'
import { onRunningWslDistrosStopped } from '../wsl-running-distro-cache'
import { runWslProcess } from './wsl-runner'
import {
parseWslInteropProbeOutput,
parseWslInteropRepairOutput,
WSL_INTEROP_PROBE_SCRIPT,
WSL_INTEROP_REPAIR_SCRIPT
} from './wsl-interop-registration'
export type WslInteropCheckOutcome =
| 'intact'
| 'repaired'
| 'interop-disabled'
| 'unsupported'
| 'repair-failed'
| 'not-running'
| 'unknown'
export type WslInteropHealthDeps = {
platform(): NodeJS.Platform
listRunningDistros(): Promise<string[]>
resolveDefaultDistro(): Promise<string | null>
runScript(
distro: string,
script: string,
user?: 'root'
): Promise<{ code: number | null; stdout: string }>
subscribeStops(listener: (stopped: string[], running: string[]) => void): () => void
now(): number
schedule(fn: () => void, delayMs: number): void
log(message: string): void
}
// Why delayed: the spawn that reported activity is what boots the distro, and the check must never be the one that does.
export const ACTIVITY_CHECK_DELAY_MS = 5_000
export const ACTIVITY_CHECK_INTERVAL_MS = 60_000
// A stop-triggered check bypasses the activity interval but not this floor.
export const STOP_CHECK_FLOOR_MS = 5_000
export const REPAIR_FAILURE_BACKOFF_MS = 10 * 60_000
const PROBE_TIMEOUT_MS = 10_000
type DistroHealth = {
name: string
lastCheckAt: number
repairFailedAt: number
scheduled: boolean
inFlight: Promise<WslInteropCheckOutcome> | null
}
/**
* Detects and restores a wiped `WSLInterop` entry in distros Orca is using.
*
* Why automatic: the missing entry is a WSL bug state, not a user choice; the
* repair re-registers exactly what WSL's init registers at boot, it is
* idempotent, and a prompt would ask users to approve something they cannot
* diagnose. `[interop] enabled=false` in the distro's wsl.conf is respected.
*/
export class WslInteropHealthMonitor {
private readonly distros = new Map<string, DistroHealth>()
private unsubscribe: (() => void) | null = null
constructor(private readonly deps: WslInteropHealthDeps) {}
/** A WSL terminal is starting in `distro` (null: the default distro). */
noteActivity(distro: string | null): void {
if (this.deps.platform() !== 'win32') {
return
}
this.unsubscribe ??= this.deps.subscribeStops((stopped, running) =>
this.onDistrosStopped(stopped, running)
)
void this.resolveName(distro).then((name) => {
if (!name) {
return
}
const health = this.healthFor(name)
if (health.scheduled || this.deps.now() - health.lastCheckAt < ACTIVITY_CHECK_INTERVAL_MS) {
return
}
health.scheduled = true
this.deps.schedule(() => {
health.scheduled = false
void this.check(name, { confirmRunning: true })
}, ACTIVITY_CHECK_DELAY_MS)
})
}
/** A stopping systemd distro is exactly when the wipe happens; re-check the ones still up. */
private onDistrosStopped(_stopped: string[], running: string[]): void {
const runningKeys = new Set(running.map((name) => name.toLowerCase()))
for (const [key, health] of this.distros) {
if (runningKeys.has(key) && this.deps.now() - health.lastCheckAt >= STOP_CHECK_FLOOR_MS) {
// The confirmed list that reported the stop also proves this distro is up.
void this.check(health.name, { confirmRunning: false })
}
}
}
check(distro: string, options: { confirmRunning: boolean }): Promise<WslInteropCheckOutcome> {
const health = this.healthFor(distro)
if (health.inFlight) {
return health.inFlight
}
health.lastCheckAt = this.deps.now()
const run = this.runCheck(health, options.confirmRunning)
.catch((error: unknown) => {
this.deps.log(`[wsl] interop check failed for ${distro}: ${String(error)}`)
return 'unknown' as const
})
.finally(() => {
if (health.inFlight === run) {
health.inFlight = null
}
})
health.inFlight = run
return run
}
private async runCheck(
health: DistroHealth,
confirmRunning: boolean
): Promise<WslInteropCheckOutcome> {
const distro = health.name
if (confirmRunning && !(await this.isRunning(distro))) {
return 'not-running'
}
const probe = await this.deps.runScript(distro, WSL_INTEROP_PROBE_SCRIPT)
const result = parseWslInteropProbeOutput(probe.stdout)
if (result.state === 'present') {
return 'intact'
}
if (result.state === 'unmounted') {
return 'unsupported'
}
if (result.state === 'unknown') {
return 'unknown'
}
if (result.interopDisabled) {
return 'interop-disabled'
}
if (this.deps.now() - health.repairFailedAt < REPAIR_FAILURE_BACKOFF_MS) {
return 'repair-failed'
}
const repair = await this.deps.runScript(distro, WSL_INTEROP_REPAIR_SCRIPT, 'root')
const repaired = repair.code === 0 ? parseWslInteropRepairOutput(repair.stdout) : 'unknown'
if (repaired === 'unknown') {
health.repairFailedAt = this.deps.now()
this.deps.log(`[wsl] could not restore Windows interop in ${distro} (exit ${repair.code})`)
return 'repair-failed'
}
if (repaired === 'present') {
// Another repair (or WSL itself) restored it between probe and repair.
return 'intact'
}
this.deps.log(
`[wsl] restored the WSLInterop binfmt entry from ${distro}; another distro's systemd ` +
'shutdown removed it (fixed in WSL 3.0.1, run `wsl --update`)'
)
return 'repaired'
}
private async isRunning(distro: string): Promise<boolean> {
try {
const running = await this.deps.listRunningDistros()
return running.some((name) => name.toLowerCase() === distro.toLowerCase())
} catch {
// Unconfirmed: `wsl -d` would boot a stopped distro, so skip.
return false
}
}
private async resolveName(distro: string | null): Promise<string | null> {
const trimmed = distro?.trim()
if (trimmed) {
return trimmed
}
try {
return await this.deps.resolveDefaultDistro()
} catch {
return null
}
}
private healthFor(distro: string): DistroHealth {
const key = distro.toLowerCase()
let health = this.distros.get(key)
if (!health) {
health = {
name: distro,
lastCheckAt: -Infinity,
repairFailedAt: -Infinity,
scheduled: false,
inFlight: null
}
this.distros.set(key, health)
}
return health
}
dispose(): void {
this.unsubscribe?.()
this.unsubscribe = null
this.distros.clear()
}
}
export const wslInteropHealthMonitor = new WslInteropHealthMonitor({
platform: () => process.platform,
listRunningDistros: () => listRunningWslDistrosAsync({ requireConfirmed: true }),
resolveDefaultDistro: async () => (await listWslDistrosAsync())[0] ?? null,
runScript: (distro, script, user) =>
runWslProcess({ distro, user, loginPath: 'none', script, timeoutMs: PROBE_TIMEOUT_MS }),
subscribeStops: onRunningWslDistrosStopped,
now: () => Date.now(),
schedule: (fn, delayMs) => {
setTimeout(fn, delayMs).unref?.()
},
log: (message) => console.warn(message)
})
@@ -0,0 +1,71 @@
import { describe, expect, it } from 'vitest'
import {
isWslConfInteropDisabled,
parseWslInteropProbeOutput,
parseWslInteropRepairOutput,
WSL_INTEROP_PROBE_SCRIPT,
WSL_INTEROP_REPAIR_SCRIPT
} from './wsl-interop-registration'
describe('parseWslInteropProbeOutput', () => {
it('reads present, unmounted and garbage', () => {
expect(parseWslInteropProbeOutput('orca-wsl-interop:present\n')).toEqual({ state: 'present' })
expect(parseWslInteropProbeOutput('motd\r\norca-wsl-interop:unmounted\r\n')).toEqual({
state: 'unmounted'
})
expect(parseWslInteropProbeOutput('')).toEqual({ state: 'unknown' })
expect(parseWslInteropProbeOutput('Exec format error')).toEqual({ state: 'unknown' })
})
it('carries the wsl.conf interop setting when the entry is missing', () => {
expect(
parseWslInteropProbeOutput(
'orca-wsl-interop:missing\norca-wsl-interop:conf\n[boot]\nsystemd=true\n'
)
).toEqual({ state: 'missing', interopDisabled: false })
expect(
parseWslInteropProbeOutput(
'orca-wsl-interop:missing\norca-wsl-interop:conf\n[interop]\nenabled=false\n'
)
).toEqual({ state: 'missing', interopDisabled: true })
})
})
describe('isWslConfInteropDisabled', () => {
it.each([
['[interop]\nenabled=false', true],
['[Interop]\n Enabled = FALSE # off', true],
['[interop]\nenabled="false"', true],
['[interop]\nenabled=0', true],
['[interop]\nenabled=true', false],
['[interop]\nappendWindowsPath=false', false],
['[automount]\nenabled=false', false],
['# [interop]\n# enabled=false', false],
['[interop]\nenabled=false\nenabled=true', false],
['', false]
])('%j -> %s', (conf, expected) => {
expect(isWslConfInteropDisabled(conf)).toBe(expected)
})
})
describe('scripts', () => {
it('accepts either interop entry name and never flushes binfmt', () => {
for (const script of [WSL_INTEROP_PROBE_SCRIPT, WSL_INTEROP_REPAIR_SCRIPT]) {
expect(script).toContain('/proc/sys/fs/binfmt_misc/WSLInterop-late')
expect(script).not.toMatch(/systemd-binfmt|\/status/)
// --exec passes argv verbatim, but keep the payload free of guest expansions anyway.
expect(script).not.toContain('$')
}
expect(WSL_INTEROP_REPAIR_SCRIPT).toContain(
"printf ':WSLInterop:M::MZ::/init:P' > /proc/sys/fs/binfmt_misc/register"
)
// A failed write must not decide the verdict; only the re-check does.
expect(WSL_INTEROP_REPAIR_SCRIPT).not.toContain('|| exit')
})
it('parses repair results', () => {
expect(parseWslInteropRepairOutput('orca-wsl-interop:repaired\n')).toBe('repaired')
expect(parseWslInteropRepairOutput('orca-wsl-interop:present\n')).toBe('present')
expect(parseWslInteropRepairOutput('sh: permission denied')).toBe('unknown')
})
})
+105
View File
@@ -0,0 +1,105 @@
/**
* Guest-side detection and repair of the `WSLInterop` binfmt_misc entry.
*
* Why: on WSL < 3.0.1 a graceful stop of ANY systemd distro (including the
* ~15 s idle stop) writes -1 to binfmt_misc/status, which deletes every entry
* VM-wide. Every other running distro then fails to exec Windows binaries
* (`code .`, `explorer.exe`, Orca's WSL CLI bridge) with "Exec format error".
* Fixed upstream in microsoft/WSL#13885. The registry is shared by all
* distros, so one repair restores every one of them.
*/
export type WslInteropProbeResult =
| { state: 'present' }
/** binfmt_misc is not mounted, so there is nothing Orca can safely restore. */
| { state: 'unmounted' }
| { state: 'missing'; interopDisabled: boolean }
| { state: 'unknown' }
const MARKER = 'orca-wsl-interop:'
const BINFMT_DIR = '/proc/sys/fs/binfmt_misc'
// Why both names: some WSL builds register `WSLInterop-late` after systemd starts.
const PRESENT_TEST = `[ -e ${BINFMT_DIR}/WSLInterop ] || [ -e ${BINFMT_DIR}/WSLInterop-late ]`
/** Read-only; runs as the default user. wsl.conf is only printed when it matters. */
export const WSL_INTEROP_PROBE_SCRIPT = [
`if [ ! -e ${BINFMT_DIR}/register ]; then echo '${MARKER}unmounted'`,
`elif ${PRESENT_TEST}; then echo '${MARKER}present'`,
`else echo '${MARKER}missing'; echo '${MARKER}conf'; cat /etc/wsl.conf 2>/dev/null; fi`
].join('\n')
/**
* Idempotent; runs as root. Re-checks so a concurrent repair (or WSL itself)
* never gets a duplicate entry. Never `systemctl restart systemd-binfmt`:
* that flushes every entry first. Flags match WSL's own `P` registration; `F`
* would pin the repairing distro's /init open for the life of the VM.
*/
export const WSL_INTEROP_REPAIR_SCRIPT = [
`if ${PRESENT_TEST}; then echo '${MARKER}present'; exit 0; fi`,
// No trailing newline: busybox printf writes it separately and the kernel rejects that write
// with EINVAL after registering. The re-check below, not the write status, is the verdict.
`printf ':WSLInterop:M::MZ::/init:P' > ${BINFMT_DIR}/register`,
`if ${PRESENT_TEST}; then echo '${MARKER}repaired'; else exit 1; fi`
].join('\n')
function markerLines(stdout: string): string[] {
return stdout
.split(/\r?\n/)
.map((line) => line.trim())
.filter((line) => line.startsWith(MARKER))
}
export function parseWslInteropProbeOutput(stdout: string): WslInteropProbeResult {
const [first] = markerLines(stdout)
if (first === `${MARKER}present`) {
return { state: 'present' }
}
if (first === `${MARKER}unmounted`) {
return { state: 'unmounted' }
}
if (first === `${MARKER}missing`) {
const confStart = stdout.indexOf(`${MARKER}conf`)
const conf = confStart !== -1 ? stdout.slice(confStart + `${MARKER}conf`.length) : ''
return { state: 'missing', interopDisabled: isWslConfInteropDisabled(conf) }
}
return { state: 'unknown' }
}
export function parseWslInteropRepairOutput(stdout: string): 'present' | 'repaired' | 'unknown' {
const [first] = markerLines(stdout)
if (first === `${MARKER}present`) {
return 'present'
}
return first === `${MARKER}repaired` ? 'repaired' : 'unknown'
}
const FALSE_VALUES = new Set(['false', '0', 'no', 'off'])
/** True when /etc/wsl.conf sets `[interop] enabled = false`. */
export function isWslConfInteropDisabled(conf: string): boolean {
let section = ''
let disabled = false
for (const rawLine of conf.split(/\r?\n/)) {
const line = rawLine.replace(/[#;].*$/, '').trim()
const header = /^\[([^\]]*)\]$/.exec(line)
if (header) {
section = header[1].trim().toLowerCase()
continue
}
if (section !== 'interop') {
continue
}
const entry = /^([^=]+)=(.*)$/.exec(line)
if (!entry || entry[1].trim().toLowerCase() !== 'enabled') {
continue
}
// Last assignment wins, as in any INI reader.
const value = entry[2]
.trim()
.replace(/^["']|["']$/g, '')
.toLowerCase()
disabled = FALSE_VALUES.has(value)
}
return disabled
}
+5
View File
@@ -73,6 +73,11 @@ describe('separator', () => {
await runWslProcess({ loginPath: 'preferred', distro: 'Ubuntu', program: '/bin/true' })
expect(lastArgv().slice(0, 3)).toEqual(['-d', 'Ubuntu', '--exec'])
})
it('passes the guest user before --exec', async () => {
await runWslProcess({ loginPath: 'none', distro: 'Ubuntu', user: 'root', program: '/bin/true' })
expect(lastArgv().slice(0, 5)).toEqual(['-d', 'Ubuntu', '-u', 'root', '--exec'])
})
})
describe('probe lane', () => {
+7 -2
View File
@@ -64,6 +64,8 @@ export type WslCommand =
export type WslSpec = WslCommand & {
/** Undefined selects the distro's default. */
distro?: string
/** Guest user; undefined runs as the distro's default user. */
user?: 'root'
/** Required, with no default: the wrong answer here is the defect this file exists to prevent. */
loginPath: WslLoginPath
/** Guest (POSIX) path. */
@@ -224,7 +226,10 @@ export async function runWslProcess(spec: WslSpec): Promise<WslResult> {
const argvForm = buildGuestArgv(environment, spec, 'argv')
// Measure what is actually spawned: `wsl.exe` and `-d <distro> --exec` are
// prepended after this point and are part of the same budget.
const fullLine = [resolveWslExecutablePath(), ...buildWslExecArgs(spec.distro, argvForm)]
const fullLine = [
resolveWslExecutablePath(),
...buildWslExecArgs(spec.distro, argvForm, { user: spec.user })
]
// Argv is the default, but it has a hard ceiling that stdin does not. A user's
// `orca.yaml` hook is the one unbounded script Orca runs, so past the cap the
// choice is between failing to spawn at all and accepting the stdin caveat.
@@ -239,7 +244,7 @@ export async function runWslProcess(spec: WslSpec): Promise<WslResult> {
const remainingMs = Math.max(1, deadline - Date.now())
const result = await runProcess({
program: resolveWslExecutablePath(),
args: buildWslExecArgs(spec.distro, argv),
args: buildWslExecArgs(spec.distro, argv, { user: spec.user }),
// Name a Windows directory rather than inheriting one: an inherited cwd that
// is later deleted (the worktree Orca launched from) fails every later spawn
// (#16463). Never the guest cwd -- withGuestCwd still cds inside.
+8 -2
View File
@@ -17,9 +17,15 @@ export function quotePosixShell(value: string): string {
*/
export function buildWslExecArgs(
distro: string | undefined,
shellArgs: readonly string[]
shellArgs: readonly string[],
options: { user?: string } = {}
): string[] {
return [...(distro ? ['-d', distro] : []), '--exec', ...shellArgs]
return [
...(distro ? ['-d', distro] : []),
...(options.user ? ['-u', options.user] : []),
'--exec',
...shellArgs
]
}
export function buildWslLoginShellCommand(command: string): string {