Skip to content

Commit 5cdb511

Browse files
mshanemcclaude
andcommitted
fix(org): clear default org ref after logging out target org - W-23069610
QA: logging out the default org (via Log Out from All Orgs) left the status bar org picker showing the now-absent org. updateConfigAndStateAggregatorsEffect refreshes TargetOrgRef by re-fetching the connection, but swallowed the failure with Effect.void when the target org no longer exists. watchConfigFiles clears the ref on that same failure, but the workspace-only ('**/*') file watcher never fires for GLOBAL config changes, so the manual refresh path is the only signal after logout. Expose ConnectionService.clearDefaultOrg (delegates to clearDefaultOrgRef) and clear the ref when getConnection fails, mirroring watchConfigFiles. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 53c2f0e commit 5cdb511

8 files changed

Lines changed: 45 additions & 10 deletions

File tree

.claude/skills/services-extension-consumption/references/connection-service.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,18 @@ After auth files or tokens change on disk, drop cached JSForce connections so th
2222
yield* api.services.ConnectionService.invalidateCachedConnections();
2323
```
2424

25+
### clearDefaultOrg
26+
27+
Clear the reactive default-org state (preserving webUserId/cliId). Use after a config change removes the target org so the org picker / status bar stop showing the now-absent org:
28+
29+
```typescript
30+
yield* api.services.ConnectionService.getConnection().pipe(
31+
Effect.catchAll(() => api.services.ConnectionService.clearDefaultOrg())
32+
);
33+
```
34+
35+
Mirrors `watchConfigFiles` failure path for code that mutates GLOBAL config (e.g. logout) that workspace-only file watcher never observes.
36+
2537
## Errors
2638

2739
- `NoTargetOrgConfiguredError` - No target org

packages/salesforcedx-vscode-org-browser/test/jest/index.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,8 @@ const MockConnectionServiceLayer = Layer.succeed(
172172
new ConnectionService({
173173
getConnection: () => Effect.sync(() => mockConnection),
174174
invalidateCachedConnections: () => Effect.void,
175-
listAllAuthorizations: () => Effect.succeed([])
175+
listAllAuthorizations: () => Effect.succeed([]),
176+
clearDefaultOrg: () => Effect.void
176177
} as const)
177178
);
178179

packages/salesforcedx-vscode-org/src/util/orgUtil.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,12 @@ export const updateConfigAndStateAggregatorsEffect = Effect.fn('updateConfigAndS
137137
});
138138
yield* api.services.ConfigService.invalidateConfigAggregator();
139139
yield* api.services.ConnectionService.invalidateCachedConnections();
140-
yield* api.services.ConnectionService.getConnection().pipe(Effect.catchAll(() => Effect.void));
140+
// getConnection refreshes TargetOrgRef on success; on failure (e.g. the target org was just
141+
// logged out) clear the ref so the org picker / status bar drop the now-absent org. Mirrors
142+
// watchConfigFiles, which the workspace-only file watcher never fires for GLOBAL config changes.
143+
yield* api.services.ConnectionService.getConnection().pipe(
144+
Effect.catchAll(() => api.services.ConnectionService.clearDefaultOrg())
145+
);
141146
});
142147

143148
export const updateConfigAndStateAggregators = async (): Promise<void> => {

packages/salesforcedx-vscode-org/test/jest/util/orgUtil.test.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,7 @@ describe('updateConfigAndStateAggregators', () => {
394394
let getConnectionMock: jest.Mock;
395395
let invalidateCachedConnectionsMock: jest.Mock;
396396
let invalidateConfigAggregatorMock: jest.Mock;
397+
let clearDefaultOrgMock: jest.Mock;
397398

398399
beforeEach(() => {
399400
jest.restoreAllMocks();
@@ -408,6 +409,7 @@ describe('updateConfigAndStateAggregators', () => {
408409
getConnectionMock = jest.fn().mockReturnValue(Effect.succeed({}));
409410
invalidateCachedConnectionsMock = jest.fn().mockReturnValue(Effect.void);
410411
invalidateConfigAggregatorMock = jest.fn().mockReturnValue(Effect.void);
412+
clearDefaultOrgMock = jest.fn().mockReturnValue(Effect.void);
411413

412414
const mockServicesApi = {
413415
services: {
@@ -416,7 +418,8 @@ describe('updateConfigAndStateAggregators', () => {
416418
},
417419
ConnectionService: {
418420
getConnection: getConnectionMock,
419-
invalidateCachedConnections: invalidateCachedConnectionsMock
421+
invalidateCachedConnections: invalidateCachedConnectionsMock,
422+
clearDefaultOrg: clearDefaultOrgMock
420423
}
421424
}
422425
} as unknown as SalesforceVSCodeServicesApi;
@@ -440,14 +443,16 @@ describe('updateConfigAndStateAggregators', () => {
440443
expect(invalidateConfigAggregatorMock).toHaveBeenCalled();
441444
expect(invalidateCachedConnectionsMock).toHaveBeenCalled();
442445
expect(getConnectionMock).toHaveBeenCalled();
446+
expect(clearDefaultOrgMock).not.toHaveBeenCalled();
443447
});
444448

445-
it('should not throw when getConnection fails', async () => {
449+
it('should clear the default org ref when getConnection fails (target org logged out)', async () => {
446450
getConnectionMock.mockReturnValue(Effect.fail(new Error('No target org configured')));
447451

448452
await expect(updateConfigAndStateAggregators()).resolves.toBeUndefined();
449453
expect(invalidateConfigAggregatorMock).toHaveBeenCalled();
450454
expect(invalidateCachedConnectionsMock).toHaveBeenCalled();
451455
expect(getConnectionMock).toHaveBeenCalled();
456+
expect(clearDefaultOrgMock).toHaveBeenCalled();
452457
});
453458
});

packages/salesforcedx-vscode-services/src/core/connectionService.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { ExtensionContextService } from '../vscode/extensionContextService';
2020
import { SettingsService } from '../vscode/settingsService';
2121
import { AliasService } from './alias';
2222
import { ConfigService } from './configService';
23-
import { getDefaultOrgRef } from './defaultOrgRef';
23+
import { clearDefaultOrgRef, getDefaultOrgRef } from './defaultOrgRef';
2424
import { DefaultOrgInfoSchema } from './schemas/defaultOrgInfo';
2525
import { getOrgFromConnection, unknownToErrorCause } from './shared';
2626

@@ -269,7 +269,15 @@ export class ConnectionService extends Effect.Service<ConnectionService>()('Conn
269269
});
270270
});
271271

272-
return { getConnection, invalidateCachedConnections, listAllAuthorizations };
272+
/**
273+
* Clears the reactive default-org state (preserving webUserId/cliId). Callers use this after a
274+
* config change removes the target org (e.g. logout) so the org picker / status bar stop showing
275+
* the now-absent org. Mirrors the `watchConfigFiles` failure path for code paths that mutate the
276+
* GLOBAL config, which the workspace-only `**\/*` file watcher never observes.
277+
*/
278+
const clearDefaultOrg = clearDefaultOrgRef;
279+
280+
return { getConnection, invalidateCachedConnections, listAllAuthorizations, clearDefaultOrg };
273281
})
274282
}) {}
275283

packages/salesforcedx-vscode-services/test/jest/core/metadataDescribeService.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ const createMockConnectionService = (listResult: ListItem | ListItem[]): Layer.L
3333
}
3434
} as unknown as Connection),
3535
invalidateCachedConnections: () => Effect.void,
36-
listAllAuthorizations: () => Effect.succeed([])
36+
listAllAuthorizations: () => Effect.succeed([]),
37+
clearDefaultOrg: () => Effect.void
3738
})
3839
);
3940

packages/salesforcedx-vscode-services/test/jest/core/templateService.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@ const createMockConnectionService = (): Layer.Layer<ConnectionService> =>
9393
ConnectionService.make({
9494
getConnection: () => Effect.succeed({ version: '60.0' } as unknown as import('@salesforce/core').Connection),
9595
invalidateCachedConnections: () => Effect.void,
96-
listAllAuthorizations: () => Effect.succeed([])
96+
listAllAuthorizations: () => Effect.succeed([]),
97+
clearDefaultOrg: () => Effect.void
9798
})
9899
);
99100

packages/salesforcedx-vscode-services/test/jest/core/traceFlagService.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ const buildMockConnectionLayer = (opts: {
6767
query: querySpy
6868
} as unknown as Connection),
6969
invalidateCachedConnections: () => Effect.void,
70-
listAllAuthorizations: () => Effect.succeed([])
70+
listAllAuthorizations: () => Effect.succeed([]),
71+
clearDefaultOrg: () => Effect.void
7172
})
7273
);
7374
return { layer, toolingSpy, querySpy };
@@ -348,7 +349,8 @@ const buildToolingMutationLayer = (opts: {
348349
tooling: { create: createSpy, delete: deleteSpy }
349350
} as unknown as Connection),
350351
invalidateCachedConnections: () => Effect.void,
351-
listAllAuthorizations: () => Effect.succeed([])
352+
listAllAuthorizations: () => Effect.succeed([]),
353+
clearDefaultOrg: () => Effect.void
352354
})
353355
);
354356
return { layer, createSpy, deleteSpy };

0 commit comments

Comments
 (0)