Преглед изворни кода

fix(subagent): sample Codex diagnostics after cleanup

pku-xht пре 1 месец
родитељ
комит
dbea39a125

+ 3 - 3
packages/subagent/subagent-codex/src/run.ts

@@ -197,19 +197,19 @@ export async function startCodexRun(
     await Promise.race([wire.startThread(spec.cwd, request.signal), processFailure])
   } catch (error: unknown) {
     request.signal.removeEventListener('abort', onAbort)
-    const startupError = withMissingPayloadDiagnostic(thrown(error), child)
+    const startupCause = thrown(error)
     try {
       await disposeProcess()
     } catch (disposeError: unknown) {
       throw new AggregateError(
-        [startupError, thrown(disposeError)],
+        [withMissingPayloadDiagnostic(startupCause, child), thrown(disposeError)],
         'subagent-codex: startup failed and app-server cleanup also failed',
       )
     }
     if (runAbort.signal.aborted) {
       throw new Error('subagent-codex: request was aborted before run publication')
     }
-    throw startupError
+    throw withMissingPayloadDiagnostic(startupCause, child)
   }
 
   const collectOutput = (): ContentBlock[] => wire.collectOutput()

+ 19 - 2
packages/subagent/subagent-codex/tests/subagent-codex.spec.ts

@@ -108,6 +108,7 @@ interface FakeChildOptions {
   readonly pid?: number
   readonly exitOnTerminate?: boolean
   readonly doneError?: Error
+  readonly collectStderr?: boolean
   readonly stderr?: string
 }
 
@@ -118,6 +119,7 @@ interface FakeChild {
   readonly toChild: PassThrough
   readonly settle: (outcome?: SubprocessOutcome) => void
   readonly fail: (error: Error) => void
+  readonly setStderr: (text: string) => void
   readonly terminate: () => void
   readonly waitForExit: (signal?: AbortSignal) => Promise<boolean>
 }
@@ -126,7 +128,7 @@ function fakeChild(options: FakeChildOptions = {}): FakeChild {
   const fromChild = new PassThrough()
   const toChild = new PassThrough()
   const peer = new ProtocolPeer(toChild, fromChild)
-  const stderrText = options.stderr
+  let stderrText = options.stderr ?? ''
   let exited = false
   let resolveDone!: (outcome: SubprocessOutcome) => void
   let rejectDone!: (error: Error) => void
@@ -176,7 +178,7 @@ function fakeChild(options: FakeChildOptions = {}): FakeChild {
     stdin: toChild,
     stdout: fromChild,
     stderr: undefined,
-    collected: stderrText === undefined
+    collected: options.stderr === undefined && options.collectStderr !== true
       ? {}
       : {
         stderr: {
@@ -198,6 +200,7 @@ function fakeChild(options: FakeChildOptions = {}): FakeChild {
     toChild,
     settle,
     fail,
+    setStderr: (text: string): void => { stderrText = text },
     terminate,
     waitForExit,
   }
@@ -1088,6 +1091,20 @@ describe('run lifecycle and quiescence', () => {
     expect(child.terminate).toHaveBeenCalledTimes(1)
   })
 
+  it('waits for process settlement before sampling the missing-payload diagnostic', async () => {
+    const child = fakeChild({ collectStderr: true, exitOnTerminate: false })
+    const starting = startCodexRun(request(), runSpec(child))
+    child.fromChild.end()
+    await vi.waitFor(() => { expect(child.terminate).toHaveBeenCalledTimes(1) })
+
+    child.setStderr('Error: Missing optional dependency @openai/codex-linux-x64.')
+    child.settle({ exitCode: 1, signal: null })
+
+    await expect(starting).rejects.toThrow(
+      'Missing optional dependency @openai/codex-linux-x64',
+    )
+  })
+
   it('keeps overlapping runs isolated', async () => {
     const first = fakeChild()
     const second = fakeChild()