Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion src/vs/workbench/contrib/chat/browser/chatTipService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -521,7 +521,18 @@ export class ChatTipService extends Disposable implements IChatTipService {
return;
}

if (this._isEligible(this._shownTip, this._contextKeyService)) {
let eligible: boolean;
try {
eligible = this._isEligible(this._shownTip, this._contextKeyService);
} catch (err) {
// The stored scoped context key service may have been disposed
// (e.g. its owning chat widget was torn down). Drop the stale
// reference and bail out — there is nothing meaningful to hide.
this._contextKeyService = undefined;
return;
}

if (eligible) {
return;
}

Expand Down
31 changes: 31 additions & 0 deletions src/vs/workbench/contrib/chat/test/browser/chatTipService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1725,6 +1725,37 @@ suite('ChatTipService', () => {
assert.strictEqual(tracker.isExcluded(tip), true, 'Should be excluded after refresh finds instruction files');
});

test('does not throw when submitted while stored context key service has been disposed', () => {
const submitRequestEmitter = testDisposables.add(new Emitter<{ readonly chatSessionResource: URI; readonly message?: IParsedChatRequest }>());
instantiationService.stub(IChatService, {
onDidSubmitRequest: submitRequestEmitter.event,
getSession: () => undefined,
} as Partial<IChatService> as IChatService);

const service = createService();

// Acquire a tip so the service stashes the (scoped) context key service.
const tip = service.getWelcomeTip(contextKeyService);
assert.ok(tip);

// Simulate the owning chat widget being torn down, which disposes its
// scoped context key service. Subsequent contextMatchesRules calls then
// throw "AbstractContextKeyService has been disposed".
const originalContextMatchesRules = contextKeyService.contextMatchesRules.bind(contextKeyService);
contextKeyService.contextMatchesRules = () => {
throw new Error('AbstractContextKeyService has been disposed');
};

try {
assert.doesNotThrow(() => submitRequestEmitter.fire({
chatSessionResource: URI.parse('chat:session-disposed'),
message: { text: 'hello', parts: [] },
}));
} finally {
contextKeyService.contextMatchesRules = originalContextMatchesRules;
}
});

});

suite('CreateSlashCommandsUsageTracker', () => {
Expand Down
Loading