From 77c1fcc4e84d372a97b75aa024f58c90300d133c Mon Sep 17 00:00:00 2001 From: 777genius Date: Mon, 4 May 2026 21:17:27 +0300 Subject: [PATCH] fix(team): preserve opencode bootstrap stall snapshots --- .../services/team/TeamProvisioningService.ts | 4 + .../team/TeamProvisioningService.test.ts | 89 +++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/src/main/services/team/TeamProvisioningService.ts b/src/main/services/team/TeamProvisioningService.ts index 34456a58..e76d0832 100644 --- a/src/main/services/team/TeamProvisioningService.ts +++ b/src/main/services/team/TeamProvisioningService.ts @@ -20707,6 +20707,7 @@ export class TeamProvisioningService { primaryStatuses: this.buildRuntimeSpawnStatusRecord(run), secondaryMembers: mixedSecondaryLanes.map((secondaryLane) => { const evidenceEntry = secondaryLane.result?.members[secondaryLane.member.name]; + const currentSpawnStatus = run.memberSpawnStatuses.get(secondaryLane.member.name); const finishedWithoutRuntimeEvidence = secondaryLane.state === 'finished' && !secondaryLane.result; return { @@ -20739,6 +20740,7 @@ export class TeamProvisioningService { pidSource: evidenceEntry.pidSource, runtimeDiagnostic: evidenceEntry.runtimeDiagnostic, runtimeDiagnosticSeverity: evidenceEntry.runtimeDiagnosticSeverity, + bootstrapStalled: currentSpawnStatus?.bootstrapStalled === true ? true : undefined, diagnostics: evidenceEntry.diagnostics, } : finishedWithoutRuntimeEvidence @@ -20748,6 +20750,8 @@ export class TeamProvisioningService { runtimeAlive: false, bootstrapConfirmed: false, hardFailure: false, + bootstrapStalled: + currentSpawnStatus?.bootstrapStalled === true ? true : undefined, diagnostics: secondaryLane.diagnostics.length > 0 ? [...secondaryLane.diagnostics] diff --git a/test/main/services/team/TeamProvisioningService.test.ts b/test/main/services/team/TeamProvisioningService.test.ts index c213f7cb..31664715 100644 --- a/test/main/services/team/TeamProvisioningService.test.ts +++ b/test/main/services/team/TeamProvisioningService.test.ts @@ -12946,6 +12946,95 @@ describe('TeamProvisioningService', () => { ); }); + it('preserves OpenCode secondary bootstrapStalled through mixed launch snapshot rebuilds', () => { + const teamName = 'zz-opencode-bootstrap-stalled-snapshot-rebuild'; + const svc = new TeamProvisioningService(); + const acceptedAt = new Date(Date.now() - 6 * 60_000).toISOString(); + const run = createMemberSpawnRun({ + teamName, + expectedMembers: [], + memberSpawnStatuses: new Map([ + [ + 'alice', + createMemberSpawnStatusEntry({ + status: 'waiting', + launchState: 'runtime_pending_bootstrap', + agentToolAccepted: true, + runtimeAlive: false, + bootstrapConfirmed: false, + hardFailure: false, + firstSpawnAcceptedAt: acceptedAt, + livenessKind: 'registered_only', + bootstrapStalled: true, + runtimeDiagnostic: + 'OpenCode member_briefing completed, but runtime_bootstrap_checkin did not complete after 5 min.', + runtimeDiagnosticSeverity: 'warning', + }), + ], + ]), + }); + run.isLaunch = true; + run.effectiveMembers = []; + run.request = { + ...run.request, + teamName, + cwd: '/tmp/opencode-bootstrap-stalled-snapshot-rebuild', + providerId: 'codex', + providerBackendId: 'codex-native', + model: 'gpt-5.4', + skipPermissions: true, + }; + run.mixedSecondaryLanes = [ + { + laneId: 'secondary:opencode:alice', + providerId: 'opencode', + member: { + name: 'alice', + providerId: 'opencode', + model: 'openrouter/qwen/qwen3-coder', + }, + runId: 'opencode-run-alice', + state: 'finished', + result: { + runId: 'opencode-run-alice', + teamName, + launchPhase: 'active', + teamLaunchState: 'partial_pending', + members: { + alice: { + memberName: 'alice', + providerId: 'opencode', + launchState: 'runtime_pending_bootstrap', + agentToolAccepted: true, + runtimeAlive: false, + bootstrapConfirmed: false, + hardFailure: false, + sessionId: 'ses_alice_partial_bootstrap', + livenessKind: 'registered_only', + diagnostics: ['OpenCode runtime session materialized.'], + }, + }, + warnings: [], + diagnostics: [], + }, + warnings: [], + diagnostics: [], + }, + ]; + + const snapshot = (svc as any).buildMixedPersistedLaunchSnapshotForRun(run, 'active'); + + expect(snapshot?.members.alice).toMatchObject({ + launchState: 'runtime_pending_bootstrap', + runtimeAlive: false, + bootstrapConfirmed: false, + hardFailure: false, + runtimeSessionId: 'ses_alice_partial_bootstrap', + livenessKind: 'registered_only', + bootstrapStalled: true, + }); + }); + it('does not copy bootstrap-state success into OpenCode secondary runtime evidence', async () => { const teamName = 'zz-opencode-bootstrap-state-not-evidence'; const leadSessionId = 'lead-session';