* Initialize the session worker: * 1. Take worker_epoch from the argument, or fall back to * CLAUDE_CODE_WORKER_EPOCH (set by env-manager / bridge spawner) * 2. Report state as 'idle' * 3. Start heartbeat timer * * In-process callers (replBridge) pass the epoch directly — they
(epoch?: number)
| 457 | * setting env vars. |
| 458 | */ |
| 459 | async initialize(epoch?: number): Promise<Record<string, unknown> | null> { |
| 460 | const startMs = Date.now() |
| 461 | if (Object.keys(this.getAuthHeaders()).length === 0) { |
| 462 | throw new CCRInitError('no_auth_headers') |
| 463 | } |
| 464 | if (epoch === undefined) { |
| 465 | const rawEpoch = process.env.CLAUDE_CODE_WORKER_EPOCH |
| 466 | epoch = rawEpoch ? parseInt(rawEpoch, 10) : NaN |
| 467 | } |
| 468 | if (isNaN(epoch)) { |
| 469 | throw new CCRInitError('missing_epoch') |
| 470 | } |
| 471 | this.workerEpoch = epoch |
| 472 | |
| 473 | // Concurrent with the init PUT — neither depends on the other. |
| 474 | const restoredPromise = this.getWorkerState() |
| 475 | |
| 476 | const result = await this.request( |
| 477 | 'put', |
| 478 | '/worker', |
| 479 | { |
| 480 | worker_status: 'idle', |
| 481 | worker_epoch: this.workerEpoch, |
| 482 | // Clear stale pending_action/task_summary left by a prior |
| 483 | // worker crash — the in-session clears don't survive process restart. |
| 484 | external_metadata: { |
| 485 | pending_action: null, |
| 486 | task_summary: null, |
| 487 | }, |
| 488 | }, |
| 489 | 'PUT worker (init)', |
| 490 | ) |
| 491 | if (!result.ok) { |
| 492 | // 409 → onEpochMismatch may throw, but request() catches it and returns |
| 493 | // false. Without this check we'd continue to startHeartbeat(), leaking a |
| 494 | // 20s timer against a dead epoch. Throw so connect()'s rejection handler |
| 495 | // fires instead of the success path. |
| 496 | throw new CCRInitError('worker_register_failed') |
| 497 | } |
| 498 | this.currentState = 'idle' |
| 499 | this.startHeartbeat() |
| 500 | |
| 501 | // sessionActivity's refcount-gated timer fires while an API call or tool |
| 502 | // is in-flight; without a write the container lease can expire mid-wait. |
| 503 | // v1 wires this in WebSocketTransport per-connection. |
| 504 | registerSessionActivityCallback(() => { |
| 505 | void this.writeEvent({ type: 'keep_alive' }) |
| 506 | }) |
| 507 | |
| 508 | logForDebugging(`CCRClient: initialized, epoch=${this.workerEpoch}`) |
| 509 | logForDiagnosticsNoPII('info', 'cli_worker_lifecycle_initialized', { |
| 510 | epoch: this.workerEpoch, |
| 511 | duration_ms: Date.now() - startMs, |
| 512 | }) |
| 513 | |
| 514 | // Await the concurrent GET and log state_restored here, after the PUT |
| 515 | // has succeeded — logging inside getWorkerState() raced: if the GET |
| 516 | // resolved before the PUT failed, diagnostics showed both init_failed |
nothing calls this directly
no test coverage detected