From 80beb3c87740d3acf8d253d52e9696da80aa95ba Mon Sep 17 00:00:00 2001 From: 777genius Date: Thu, 23 Apr 2026 01:44:55 +0300 Subject: [PATCH] fix(team): clear stale permission launch state --- .../opencode/permissions/RuntimePermission.ts | 26 ++++++++++++++++--- .../services/team/RuntimePermission.test.ts | 9 +++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/main/services/team/opencode/permissions/RuntimePermission.ts b/src/main/services/team/opencode/permissions/RuntimePermission.ts index a5e03bb9..ae628428 100644 --- a/src/main/services/team/opencode/permissions/RuntimePermission.ts +++ b/src/main/services/team/opencode/permissions/RuntimePermission.ts @@ -375,8 +375,8 @@ export class RuntimePermissionRequestStore { teamName: string; visibleProviderRequestIds: Set; now: string; - }): Promise { - const expired: string[] = []; + }): Promise>> { + const expired: Array> = []; await this.store.updateLocked((records) => records.map((record) => { if ( @@ -387,7 +387,7 @@ export class RuntimePermissionRequestStore { ) { return record; } - expired.push(record.appRequestId); + expired.push({ appRequestId: record.appRequestId, memberName: record.memberName }); return { ...record, state: 'provider_missing' as const, @@ -648,6 +648,26 @@ export class RuntimePermissionReconciler { }); } + const clearedMembers = new Set( + expired + .map((record) => record.memberName) + .filter((memberName) => memberName.trim().length > 0) + .filter((memberName) => !pendingByMember.has(memberName)) + ); + for (const memberName of clearedMembers) { + await this.launchStateStore.updateMember(input.teamName, memberName, (member) => ({ + ...member, + launchState: + member.launchState === 'confirmed_alive' + ? member.launchState + : member.bootstrapConfirmed + ? 'confirmed_alive' + : 'runtime_pending_bootstrap', + pendingPermissionRequestIds: [], + lastRuntimeEventAt: now, + })); + } + for (const [memberName, requestIds] of pendingByMember) { await this.launchStateStore.updateMember(input.teamName, memberName, (member) => ({ ...member, diff --git a/test/main/services/team/RuntimePermission.test.ts b/test/main/services/team/RuntimePermission.test.ts index 6451e2df..cad88a03 100644 --- a/test/main/services/team/RuntimePermission.test.ts +++ b/test/main/services/team/RuntimePermission.test.ts @@ -349,6 +349,11 @@ describe('RuntimePermissionRequestStore and services', () => { it('expires local pending requests that disappeared from provider', async () => { await store.upsertPending(permissionRecord()); + await launchState.updateMember('team-a', 'alice', (member) => ({ + ...member, + launchState: 'runtime_pending_permission', + pendingPermissionRequestIds: ['opencode:run-1:perm_1'], + })); client.pending = []; const reconciler = new RuntimePermissionReconciler( client, @@ -368,6 +373,10 @@ describe('RuntimePermissionRequestStore and services', () => { state: 'provider_missing', lastError: 'Provider no longer lists this permission request', }); + expect(launchState.members.get('alice')).toMatchObject({ + launchState: 'runtime_pending_bootstrap', + pendingPermissionRequestIds: [], + }); expect(diagnostics.append).toHaveBeenCalledWith( expect.objectContaining({ type: 'opencode_permission_requests_expired',