Repository navigation
fix: unstick the anti-pattern "Why?" explanation and use Copilot Auto - #301
Draft
Sanjay Singh (san360) wants to merge 3 commits into
Draft
Sanjay Singh (san360) wants to merge 3 commits into
Sanjay Singh (san360) wants to merge 3 commits into
Conversation
The Why? explainer wrote its loading text with textContent and then
called Preact render() on the same container. Preact never removes nodes
it did not create, so "Asking AI for an explanation..." stayed after the
answer, and a repeat request patched detached nodes and never appeared.
Every explanation state now renders through Preact, and empty answers
surface as errors.
AI features and the natural-language rule compiler now use only GitHub
Copilot's Auto model ({ vendor: 'copilot', id: 'auto' }) instead of
hard-coded model families. callLlm no longer retries timeouts, denied
access, quota or not-found errors, treats empty replies as failures, and
keeps each call within a 120s budget.
getCapabilities now probes Copilot availability (bounded, optimistic on
timeout) instead of always reporting llm: true, and the panel pushes
capability changes on lm.onDidChangeChatModels so Why?, Skill Finder,
Level Up and Context Review follow Copilot sign-in and sign-out.
The contributes.languageModels entry proposed in the issue is not added:
VS Code has no such contribution point (languageModelChatProviders is for
providing models) and consuming vscode.lm needs no contribution.
Resolves #278
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Capability-triggered page replacement silently discards unsaved Rule Editor drafts.
1 open issue
What changed in this PR
Fixes stuck anti-pattern explanations and standardizes optional AI features on GitHub Copilot Auto.
Changes:
- Renders explanation states consistently through Preact.
- Adds bounded model selection and improved plain-text request error handling.
- Updates AI feature visibility when Copilot availability changes, with regression coverage.
| File | Description |
|---|---|
| tests/e2e/harness.html | Adds AI navigation markers. |
| tests/e2e/antipatterns.spec.ts | Tests explanations and capability changes. |
| src/webview/styles.css | Hides unavailable AI navigation items. |
| src/webview/panel.ts | Probes and pushes Copilot availability. |
| src/webview/panel.test.ts | Tests host capability responses and updates. |
| src/webview/panel-llm.ts | Updates model access, retries, and timeouts. |
| src/webview/panel-llm-model.ts | Centralizes model selection and failure classification. |
| src/webview/panel-llm-call.test.ts | Tests model access and request failures. |
| src/webview/page-config.ts | Uses host-specific availability guidance. |
| src/webview/page-antipatterns.ts | Delegates explanation rendering. |
| src/webview/occurrence-explain.ts | Manages explanation states through Preact. |
| src/webview/occurrence-explain.test.ts | Tests explanation rendering and recovery. |
| src/webview/dashboard-shell.ts | Marks AI-dependent navigation items. |
| src/webview/capabilities.ts | Handles capability updates and subscriptions. |
| src/webview/capabilities.test.ts | Tests capability state and notifications. |
| src/webview/app.ts | Refreshes navigation and pages on capability changes. |
| src/core/types/rpc-types.ts | Defines shared capability message types. |
| src/core/rule-compiler.ts | Uses Copilot Auto and consent justification. |
| src/core/copilot-model.ts | Defines shared selector and justification. |
| README.md | Documents Copilot requirements and availability. |
| CHANGELOG.md | Records explanation and model-access changes. |
Review effort: Balanced
Addresses review feedback on #301. Re-rendering the current page after a capability push cleared #content, which closed the Rule Editor modal and discarded unsaved drafts, along with other in-page state such as expanded findings and loaded Context Health review results. Capability changes no longer rebuild the page. capabilities.ts toggles an llm-unavailable class on the root element, and AI controls are always rendered with llm-only or llm-fallback markers that CSS shows or hides: Why? buttons, the Dashboard Skill Finder card, the Skill Finder and Level Up nav entries, and the Context Health review controls. Refs #278 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Addresses the high-severity review finding on #301. callLlm started its 120s budget after model selection, which can take up to 15s, so a request could run for about 135s. Generate SKILL.md was not in the webview's LLM_METHODS, so its RPC gave up at 120s and discarded the late reply or the host's actionable error. - The 120s budget (COPILOT_REQUEST_BUDGET_MS, now in core/copilot-model) starts before model selection, and selection is capped at the smaller of 15s and the remaining budget. - callLlm accepts a shared deadline. generateRule makes up to three model calls for one request, so it now passes one deadline to all of them and keeps the latest draft if a follow-up runs out of time. Its retry loop moved into generateRuleMarkdown so it can be unit tested. - generateSkillContent uses the 300s LLM RPC timeout like explainOccurrence and generateRule. The list is type-checked against the RPC method names. Refs #278 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Aymen (aymenfurter)
approved these changes
Oct 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Description
Fixes the anti-pattern Why? button that stays on "Asking AI for an explanation…" forever (#278). It also makes every AI feature use GitHub Copilot's Auto model, fail fast with a clear message, and appear only when GitHub Copilot is available.
Code changes
src/core/copilot-model.ts(new){ vendor: 'copilot', id: 'auto' }selector, the consent-dialog justification text, and the 120s request budget (COPILOT_REQUEST_BUDGET_MS).src/webview/panel-llm-model.ts(new)selectModel()(Copilot Auto only; at most 15s and never past the request's deadline),isCopilotModelAvailable()probe,withTimeout(), and classification of errors that must not be retried.src/webview/panel-llm.tscallLlmgets a 120s budget that includes model selection and can be shared across calls (llmDeadline()and adeadlineoption), no retries for non-recoverable errors, empty reply treated as failure, and the justification.callLlmJsononly gains the justification.src/webview/panel-rpc.tsgenerateRulepasses one deadline to all of its model calls (up to three) and keeps the latest draft if a follow-up runs out of time. Its retry loop is now the testablegenerateRuleMarkdown.src/webview/shared.tsgenerateSkillContent) gets the 300s webview timeout used for Copilot-backed requests, like Why? and Generate rule. The list is type-checked against the RPC method names.src/core/rule-compiler.ts{ family: 'gpt-4.1' }replaced by the shared Auto selector, plus the justification.src/webview/occurrence-explain.ts(new)src/webview/page-antipatterns.tsexplainOccurrenceremoved; the page uses the new module. The Why? button is always rendered and markedllm-only.src/webview/panel.tsgetCapabilitiesactually probes Copilot. The panel pushescapabilitiesmessages onlm.onDidChangeChatModels.src/core/types/rpc-types.tsHostCapabilitiesandHostCapabilitiesMessagetypes.src/webview/capabilities.tsllm-unavailableclass on the root element, so CSS shows or hides AI controls without rebuilding the page. Adds a host-awarellmUnavailableNote()(replacesLLM_UNAVAILABLE_NOTE).src/webview/dashboard-shell.ts,page-dashboard.ts,page-config.ts,styles.cssllm-only: the Skill Finder and Level Up nav entries, the Dashboard Skill Finder card, and Review Context Files with its workspace count. A disabled Review Context Files carrying the sign-in hint is markedllm-fallback. Two CSS rules keyed on the root class show or hide them.panel-llm-call.test.ts,occurrence-explain.test.ts,capabilities.test.ts; extendedpanel.test.ts; newshared-rpc.test.ts; Playwright cases inantipatterns.spec.tsand the newcopilot-availability.spec.ts(harness.htmlmirrors the nav markers).Impacted functionality: before → after
<answer>Asking AI for an explanation…). An empty answer left only the loading text. Asking again after hiding the answer stuck on "Asking AI for an explanation…" forever, with no error.gpt-5.4-mini→gpt-5-mini→gpt-4.1-mini→gpt-4.1, then the first model from any vendor. A model could be selectable but disabled for the user's plan, which produced empty replies (#91).NoPermissions), quota or rate limits (Blocked) andNotFoundfail immediately with an actionable message (for example "Run 'Chat: Manage Language Model Access'…"). Transient errors and empty replies are retried within the budget.{ family: 'gpt-4.1' }only, falling back to the heuristic template when that model wasn't available.getCapabilitiesalways returnedllm: true. Why?, Skill Finder (nav entry and dashboard card), Level Up and Review Context Files were always shown, even without Copilot, and then failed or hung when used.lm.onDidChangeChatModelsand pushes the change. AI controls appear or disappear in place, with no need to reopen. The page isn't rebuilt, so an open Rule Editor and its unsaved draft, expanded findings, and loaded results are kept. If you're on Skill Finder or Level Up when Copilot becomes unavailable, you stay on the page and AI actions show the sign-in message.llm: false).call-failedfrom JSON calls.[panel-llm]lines:model-selected(vendor, id, family),model-unavailable,call-failed(with a retryable flag),empty-response,probe-inconclusive, plus[panel] capabilities-changed. Prompts and keys are never logged.Not changed:
callLlmJsonretry and JSON-repair logic;@aicoachchat participant, which uses the model selected in chat;Root cause (reproduced)
explainOccurrencewrote the loading text withtextContentand then called Preactrender()on the same element. Preact never removes DOM nodes it didn't create, and after atextContentwipe it keeps patching its old, detached nodes.The new tests reproduce the bug against the original code:
"The session ran long.Start a new chat per task.Asking AI for an explanation...""Asking AI for an explanation..."forever"First explanation.Asking AI for an explanation..."Not adopted from the issue
contributes.languageModels: VS Code has no such contribution point. The only one islanguageModelChatProviders, which is for providing models. Callingvscode.lmneeds no contribution; consent is an auth dialog shown on the firstsendRequest.gpt-4o/o1/o3-minifamily list: these models are retired too. Copilot Auto replaces hard-coded names.pendingMessagesflush: already fixed onmainbypublishReady()(68ceef6) and covered by an existingpanel.test.tscase.Validation
npm run check(CIverify)npm run check,build,check-size,packagenpm run check(local)src/core/github-app-analytics.test.tsfailures are specific to this machine: they also fail on unmodifiedmainand pass in CI.npm run test:e2enpm run check-sizeNew tests:
panel-llm-call.test.ts(18): selection, probe, retry, timeout and budget behaviour, including model selection counting toward the budget and deadlines shared across calls.shared-rpc.test.ts(4): every Copilot-backed RPC waits longer than the host budget plus a 60s delivery margin; other RPCs keep 120s.panel-rpc.test.ts(+5):generateRuleMarkdownretry feedback and keeping the latest draft.occurrence-explain.test.ts(6) andcapabilities.test.ts(5): jsdom.panel.test.ts.antipatterns.spec.ts, including a regression test that an unsaved Rule Editor draft survives a capability change.copilot-availability.spec.ts: the Dashboard Skill Finder card and the Context Health fallback button.Manual verification with real Copilot (still to do before marking ready)
Launch an isolated dev host from the repo root with
code --user-data-dir "$env:TEMP\aiec-test" --extensionDevelopmentPath "$PWD". It starts with fresh GitHub sign-in and consent state and still reads your real session logs. Watch View → Output → AI Engineer Coach.[panel-llm] model-selectedline withvendor=copilot id=auto. Hide it, then ask again: a new explanation appears.Related Issues
Resolves #278. Related: #91 (empty responses from selectable-but-disabled models). This PR supersedes the model-selection part of #253, while #253's
callLlmJsonempty-response handling is still complementary. It is also compatible with #276 / #277.Checklist
npm run checkpasses (typecheck + lint + spellcheck + knip + tests). Green in CIverify; see Validation for local notes.