Keep native contracts on Node and wait for Git upgrade completion (#26034)

* Keep native watcher and supervision contracts on Node

* Route real permission and new ledger contracts through Node

* Synchronize handshake cleanup with forced termination
This commit is contained in:
Neil
2026-10-06 23:09:33 -07:00
committed by GitHub
parent acea59c6d8
commit 0acf039b5d
3 changed files with 30 additions and 26 deletions
@@ -74,6 +74,8 @@ export const NODE_RUNTIME_INCLUDE = [
'src/shared/remote-runtime-connect-bound.test.ts',
'src/shared/remote-runtime-subscription-connect-bound.test.ts',
'src/main/codex/codex-shared-server-probe.test.ts',
'src/main/opencode/opencode-launch-model-context-supervision.test.ts',
'src/main/hermes/hermes-config-remote-adapter.test.ts',
'src/relay/relay-reconnect-listener-peer-write-error.test.ts',
'src/relay/relay-primary-channel.test.ts',
'src/renderer/src/lib/monaco-languages/monarch-upstream-mdx-recursion.test.ts',
@@ -81,6 +83,7 @@ export const NODE_RUNTIME_INCLUDE = [
'src/main/runtime/structured-agent-session-startup-tab-restore.test.ts',
'src/shared/remote-runtime-shared-control-connection.test.ts',
'src/main/runtime/relay/relay-control-client.test.ts',
'src/main/ipc/runtime-environment-preference.test.ts',
'src/main/ipc/runtime-environment-transport-routing-tailscale-hint.test.ts',
'src/renderer/src/components/emulator-pane/emulator-device-frame-visibility.test.tsx',
'src/main/claude/claude-agent-sdk-contract-pins.test.ts',
@@ -14,6 +14,7 @@ import {
} from './codex-app-server-connection'
import { PROVIDER_SUPERVISOR_MAX_STOP_MS } from '../provider-process/provider-process-supervisor'
import { isCodexAppServerUnsupportedError } from './codex-app-server-session'
import { isCodexAppServerHandshakeExitUnprovenError } from './codex-app-server-handshake-exit-proof'
// close() waits out the supervisor's own stop before forcing the tree.
const GRACEFUL_EXIT_MS = process.platform === 'win32' ? 1_500 : PROVIDER_SUPERVISOR_MAX_STOP_MS
@@ -126,18 +127,11 @@ async function flushStreams(): Promise<void> {
await new Promise((resolve) => setImmediate(resolve))
}
async function closeWithoutObservedExit(
connection: CodexAppServerConnection,
child: StubChild
): Promise<boolean> {
async function closeWithoutObservedExit<T>(start: () => Promise<T>, child: StubChild): Promise<T> {
const forcedKill = new Promise<void>((resolve) => {
child.kill.mockImplementation((signal) => {
if (signal === 'SIGKILL') {
resolve()
}
})
child.kill.mockImplementation((signal) => (signal === 'SIGKILL' ? resolve() : undefined))
})
const closing = connection.close()
const closing = start()
await flushStreams()
await vi.advanceTimersByTimeAsync(GRACEFUL_EXIT_MS)
await forcedKill
@@ -461,25 +455,28 @@ describe('openCodexAppServerConnection', () => {
})
it('exposes an unproven handshake child for later cleanup', async () => {
vi.useFakeTimers()
vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] })
const { child, spawnImpl } = stubChild({ exitOnStdinEnd: false })
child.stdin.once('data', () => {
child.stdout.write(
`${JSON.stringify({ id: 1, error: { code: -32602, message: 'initialize failed' } })}\n`
)
})
const opening = rejection(
openCodexAppServerConnection({ command: 'codex', args: ['app-server'] }, {}, spawnImpl)
const error = await closeWithoutObservedExit(
() =>
rejection(
openCodexAppServerConnection({ command: 'codex', args: ['app-server'] }, {}, spawnImpl)
),
child
)
await vi.advanceTimersByTimeAsync(GRACEFUL_EXIT_MS + 3_500)
const error = (await opening) as Error & { connection?: CodexAppServerConnection }
expect(error.name).toBe('CodexAppServerHandshakeExitUnprovenError')
if (!isCodexAppServerHandshakeExitUnprovenError(error)) {
throw error
}
expect(error.connection).toBeDefined()
child.emit('exit', 1, null)
child.emit('close', 1, null)
await expect(error.connection?.close()).resolves.toBe(true)
await expect(error.connection.close()).resolves.toBe(true)
})
it('times out one request without ending the connection', async () => {
@@ -531,7 +528,7 @@ describe('openCodexAppServerConnection', () => {
spawnImpl
)
await expect(closeWithoutObservedExit(connection, child)).resolves.toBe(false)
await expect(closeWithoutObservedExit(() => connection.close(), child)).resolves.toBe(false)
expect(vi.getTimerCount()).toBe(0)
}, 10_000)
@@ -567,7 +564,7 @@ describe('openCodexAppServerConnection', () => {
spawnImpl
)
await expect(closeWithoutObservedExit(connection, child)).resolves.toBe(false)
await expect(closeWithoutObservedExit(() => connection.close(), child)).resolves.toBe(false)
child.emit('exit', 0, null)
await expect(connection.close()).resolves.toBe(true)
+10 -6
View File
@@ -206,12 +206,16 @@ describe('folder repo git upgrade watch', () => {
pollIntervalMs: POLL_MS,
idlePollIntervalMs: IDLE_POLL_MS
})
await tick()
expect(store.updateRepo).toHaveBeenCalledWith('folder-repo', {
kind: 'git',
folderUpgradeGitRootPath: join(root, 'symlinked-project').replaceAll('\\', '/')
})
// A fixed tick wait can finish before the marker stat resolves under load.
await vi.waitFor(
() => {
expect(store.updateRepo).toHaveBeenCalledWith('folder-repo', {
kind: 'git',
folderUpgradeGitRootPath: join(root, 'symlinked-project').replaceAll('\\', '/')
})
},
{ timeout: 5_000, interval: POLL_MS }
)
})
it('retries after the last extra folder workspace is removed', async () => {