fix: start Claude init deadline after launch

This commit is contained in:
Brennan Benson
2026-08-10 04:25:39 -07:00
parent 5af3b7aca9
commit 2a0d5bc32f
2 changed files with 48 additions and 18 deletions
@@ -0,0 +1,39 @@
import type { ClaudeInitObservation } from './claude-structured-init-proof'
export type ClaudeInitDeadline = {
promise: Promise<ClaudeInitObservation>
resolve: (init: ClaudeInitObservation) => void
reject: (error: Error) => void
start: () => void
clear: () => void
}
export function createClaudeInitDeadline(sessionId: string, timeoutMs: number): ClaudeInitDeadline {
let resolve = (_init: ClaudeInitObservation): void => {}
let reject = (_error: Error): void => {}
const promise = new Promise<ClaudeInitObservation>((resolvePromise, rejectPromise) => {
resolve = resolvePromise
reject = rejectPromise
})
void promise.catch(() => {})
let timer: ReturnType<typeof setTimeout> | null = null
return {
promise,
resolve,
reject,
start: () => {
timer = setTimeout(
() => reject(new Error(`claude session ${sessionId} did not emit system/init`)),
timeoutMs
)
timer.unref?.()
},
clear: () => {
if (timer) {
clearTimeout(timer)
timer = null
}
}
}
}
@@ -15,9 +15,9 @@ import {
claudeAuthDiagnostic,
readClaudeFrameString,
readClaudeInit,
readClaudeModels,
type ClaudeInitObservation
readClaudeModels
} from './claude-structured-init-proof'
import { createClaudeInitDeadline } from './claude-structured-init-deadline'
import { supportsClaudeStructuredLocation } from './claude-structured-location-support'
import { CLAUDE_SPAWN_TOKEN_ENV, claudeProcessIdentity } from './claude-structured-owner-identity'
import { setClaudeStructuredOption } from './claude-structured-options'
@@ -66,22 +66,12 @@ export class ClaudeStructuredSessionAdapter implements StructuredAgentSessionAda
const { previous, attempt } = this.acquisitions.start(sessionId, prompts)
let liveSession: ClaudeSession | null = null
let observedLeafUuid: string | null = null
let settleInit = (_init: ClaudeInitObservation): void => {}
let rejectInit = (_error: Error): void => {}
const initialized = new Promise<ClaudeInitObservation>((resolve, reject) => {
settleInit = resolve
rejectInit = reject
})
const initTimer = setTimeout(
() => rejectInit(new Error(`claude session ${sessionId} did not emit system/init`)),
INIT_TIMEOUT_MS
)
initTimer.unref?.()
const initDeadline = createClaudeInitDeadline(sessionId, INIT_TIMEOUT_MS)
const onMessage = (message: Record<string, unknown>): void => {
const init = readClaudeInit(message)
if (init) {
settleInit(init)
initDeadline.resolve(init)
}
observedLeafUuid = readClaudeFrameString(message, 'uuid') ?? observedLeafUuid
if (liveSession) {
@@ -139,7 +129,7 @@ export class ClaudeStructuredSessionAdapter implements StructuredAgentSessionAda
},
onExit: (error) => {
if (!attempt.published) {
rejectInit(error)
initDeadline.reject(error)
}
this.handleExit(sessionId, attempt, error)
}
@@ -147,19 +137,20 @@ export class ClaudeStructuredSessionAdapter implements StructuredAgentSessionAda
)
attempt.connection = connection
this.acquisitions.assertCurrent(sessionId, attempt)
initDeadline.start()
const [initialization, init] = await Promise.all([
connection.request(
'initialize',
{ supportedDialogKinds: [] },
{ timeoutMs: this.deps.requestTimeoutMs }
),
initialized
initDeadline.promise
])
const models = readClaudeModels(initialization)
this.deliver(attempt, sessionId, () =>
this.emit(liveSession, input.events, { type: 'options', sessionId, models })
)
clearTimeout(initTimer)
initDeadline.clear()
this.acquisitions.assertCurrent(sessionId, attempt)
if (init.providerSessionId !== launch.providerSessionId) {
throw new Error(
@@ -208,7 +199,7 @@ export class ClaudeStructuredSessionAdapter implements StructuredAgentSessionAda
}
return acquired
} catch (error) {
clearTimeout(initTimer)
initDeadline.clear()
this.acquisitions.deleteIfCurrent(sessionId, attempt)
if (this.sessions.get(sessionId)?.connection !== attempt.connection) {
prompts.clear()