Change:
<!-- Do Not Delete This! bugreporttemplate --<!-- Please read our Rules of Conduct: --<!-- Read our guide about submitting issues: --<!-- Search existing issues to avoid creating duplicates. --<!-- Test using the latest Insiders build to see if your issue has already been fixed: --<!-- Instead of creating your report here, use 'Report Issue' from the 'Help' menu in VS Code to pre-fill useful information. --<!-- Launch with to check. --Does this issue occur when all extensions are disabled?: Yes A code lens with no command shows up as . This is clearly deliberate, see the and the . However, this interacts badly with . If the resolve request fails (for example, if the document has been changed such that the old lens is now stale), then the code lens will still appear with , even though this is "normal" operation. The behaviour I would expect is for the code lens not to be displayed in this case. The current behaviour makes a lot of sense for simple code lens providers that don't rely on , since in that case not providing a command is questionable and it's useful for it to show up obviously. But in the case where we've resolved the lens and got a failure, I think it would make much more sense to just not display the lens. This was discovered by a user of the Haskell Language Server after we implemented : <!-- If you answered No above, use 'Help: Start Extension Bisect' from Command Palette to try to identify the cause. --<!-- Issues caused by an extension need to be reported directly to the extension publisher. The 'Help Report Issue' dialog can assist with this. --- VS Code Version: I don't know what the user was using, but the behaviour is clearly present in the source code on master OS Version: ditto Steps to Reproduce: a code lens resolve handler that fails ugly lenses
We don't remove these lenses to not introduce flicker (all lines would shift up by a little). I agree that today's fallback isn't great, a dash might be better, but it should also be clear that the extension should always try to resolve successfully and that failure is pretty bad here (because the UI space for code lense commands is already reserved)
I can see that it's awkward. Surely indeed the point of having two phases for code lenses is to reserve the UI space. It would be nice if the unresolved code lens could give some placeholder text or something :thinking:
Decided to go with the (existing) "no commands" rendering but we will also start to log extension errors for this <img width="644" alt="Screenshot 2023-12-04 at 16 23 29" src=""
Just one thought: what about simply blank space? So we'd have the space in the UI, which would look a little odd, but we wouldn't be forcing what is ultimately a diagnostic message into the user's face unless they look in the log.
I experimented with that but kinda looks like a rendering glitch and you keep staring at it waiting for an update

Discussion:
import { isCancellationError, NotImplementedError } from 'vs/base/common/errors'; import { raceCancellationError } from 'vs/base/common/async'; import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; <del> import { IExtHostTelemetry } from 'vs/workbench/api/common/extHostTelemetry'; </del> <ins> import { ExtHostTelemetry, IExtHostTelemetry } from 'vs/workbench/api/common/extHostTelemetry'; </ins> import { localize } from 'vs/nls'; import { IAutoClosingPairConditional } from 'vs/editor/common/languages/languageConfiguration';

class CodeLensAdapter { <del> private static _badCmd: vscode.Command = { command: 'missing', title: '!!MISSING: command!!' }; </del> private readonly _cache = new Cache<vscode.CodeLens>('CodeLens'); private readonly _disposables = new Map<number, DisposableStore>(); constructor( private readonly _documents: ExtHostDocuments, private readonly _commands: CommandsConverter, <del> private readonly _provider: vscode.CodeLensProvider </del> <ins> private readonly _provider: vscode.CodeLensProvider, private readonly _extension: IExtensionDescription, private readonly _extTelemetry: ExtHostTelemetry, private readonly _logService: ILogService, </ins> ) { } async provideCodeLenses(resource: URI, token: CancellationToken): Promise<extHostProtocol.ICodeLensListDto | undefined> {

// disposed in the meantime return undefined; } <del> symbol.command = this._commands.toInternal(resolvedLens.command ?? CodeLensAdapter._badCmd, disposables); </del> <ins> if (!resolvedLens.command) { const error = new Error('INVALID code lens resolved, lacks command: ' + this._extension.identifier.value); this._extTelemetry.onExtensionError(this._extension.identifier, error); this._logService.error(error); return undefined; } symbol.command = this._commands.toInternal(resolvedLens.command, disposables); </ins> return symbol; }

const handle = this._nextHandle(); const eventHandle = typeof provider.onDidChangeCodeLenses === 'function' ? this._nextHandle() : undefined; <del> this._adapter.set(handle, new AdapterData(new CodeLensAdapter(this._documents, this._commands.converter, provider), extension)); </del> <ins> this._adapter.set(handle, new AdapterData(new CodeLensAdapter(this._documents, this._commands.converter, provider, extension, this._extensionTelemetry, this._logService), extension)); </ins> this._proxy.$registerCodeLensSupport(handle, this._transformDocumentSelector(selector, extension), eventHandle); let result = this._createDisposable(handle);

assert.strictEqual(value.lenses.length, 1); const [data] = value.lenses; const symbol = await Promise.resolve(data.provider.resolveCodeLens!(model, data.symbol, CancellationToken.None)); <del> assert.strictEqual(symbol!.command!.id, 'missing'); assert.strictEqual(symbol!.command!.title, '!!MISSING: command!!'); </del> <ins> assert.strictEqual(symbol, undefined); </ins> value.dispose(); });