| name | contextrelay-vs-options-debugging |
| description | Debug Visual Studio Tools > Options registration issues for any Visual Studio extension. Use when agents need to investigate missing option pages, ActivityLog collection, VSIX/pkgdef registration, Experimental Instance mismatches, package activation failures, or in-proc package loading problems. |
Visual Studio Options Registration Debugging
This skill is a reusable playbook for diagnosing why a Visual Studio extension's Tools > Options category/page does not appear or cannot load.
It is designed for any extension architecture (VSSDK in-proc, VisualStudio.Extensibility out-of-proc, or hybrid VSIX).
Primary workflow:
- Identify the exact Visual Studio instance being inspected.
- Collect a deterministic ActivityLog (
/log).
- Validate VSIX and pkgdef/package registration artifacts.
- Separate "category invisible" failures from "page load failed" failures.
- For tool window/panel failures, separate provider-construction failures from command-triggered cancellation failures.
- Only then modify packaging, registration, or panel initialization flow.
- When a new VS extension bug is confirmed, append a "Known ... Failure" section in this skill with:
- symptom signature,
- root cause,
- remediation,
- and verification steps.
Use these sample scripts first (they are examples and can be adapted to your extension/repo):
Key Concepts
1) Instance accuracy comes first
Do not assume the visible Visual Studio window is /RootSuffix Exp.
Always inspect the active devenv.exe command line before checking registry/profile folders.
2) /log must be explicit when logs are missing
Activity logs are only written for sessions launched with /log.
If no log file exists, relaunch with an explicit output path and use that session as your evidence source.
3) Manifest/image discovery and pkgdef discovery are different pipelines
An extension can be visible to one discovery path (for example image/manifest scanning) and still be absent from pkgdef merge/registration.
Always verify both:
- where VS searches manifests/images
- where VS searches pkgdefs/packages
4) Prefer one authoritative install path
When debugging Options registration, avoid mixing install paths (for example "main VSIX + separate hand-placed sidecar") unless you can prove both are supported for your VS version.
If one path is unsupported or ignored, keep registration on the path Visual Studio actually installs and merges.
5) Generated pkgdef is usually authoritative
For VSSDK package registration, prefer VSSDK-generated pkgdef output over static handwritten pkgdef files.
Static copies often drift and can hide build/runtime mismatches.
6) Distinguish two failure classes
- Options category does not appear → usually registration/discovery/VSIX payload problem.
- Category appears but page fails to load → usually package activation/dependency/load-context problem.
Treat these as separate branches with separate evidence.
Common Patterns
Pattern 1: Identify the active Visual Studio target
Get-CimInstance Win32_Process -Filter "name = 'devenv.exe'" |
Select-Object ProcessId, ExecutablePath, CommandLine
Pattern 2: Start Visual Studio with deterministic /log
$logPath = Join-Path $env:APPDATA 'Microsoft\VisualStudio\ExtensionDebug-ActivityLog.xml'
$devenv = 'C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\devenv.exe'
Start-Process -FilePath $devenv -ArgumentList '/RootSuffix', 'Exp', '/Log', $logPath
Pattern 3: Inspect produced VSIX payload
$vsix = Get-ChildItem '.\src' -Filter *.vsix -Recurse |
Sort-Object LastWriteTime -Descending |
Select-Object -First 1
Add-Type -AssemblyName System.IO.Compression.FileSystem
$zip = [System.IO.Compression.ZipFile]::OpenRead($vsix.FullName)
try {
$zip.Entries |
Where-Object { $_.FullName -match 'extension\.vsixmanifest|pkgdef|Package\.dll' } |
Select-Object -ExpandProperty FullName
}
finally {
$zip.Dispose()
}
Pattern 4: Check for page-load signatures in ActivityLog
$logPath = Join-Path $env:APPDATA 'Microsoft\VisualStudio\ExtensionDebug-ActivityLog.xml'
Select-String -Path $logPath -Pattern 'No InprocServer32 registered for package|CreateInstance failed|FileNotFoundException' -Context 2,4
Investigation Checklist
- Active instance
- Confirm command line and root suffix.
- Confirm exact VS SKU/version/channel.
- Log availability
- Use explicit
/log and retain that file as evidence.
- Profile alignment
- Match local/roaming paths to the exact instance under test.
- Build artifact validation
- Check generated manifest and final VSIX payload.
- Check pkgdef generation output from build artifacts.
- Runtime diagnostics
- Review ActivityLog for package registration/load failures.
- If category appears but page load fails, prioritize package activation diagnostics.
- Registration strategy validation
- Confirm your chosen install path is actually supported by current VS behavior.
- Avoid mixing unsupported "manual copy" paths with installer-managed paths.
Known Installer Failure: invalid [installdir]\Common7\IDE\VSExtensions\... path
Symptom signature
- VSIX installation fails before payload deployment with
System.InvalidOperationException.
- VSIXInstaller log contains a message similar to:
manifest.json ... path '[installdir]\Common7\IDE\VSExtensions\<hash>' is invalid
- Failure appears during
PackageInstaller.ValidatePackage(...) and installation rolls back.
Why this happens
This usually indicates a packaging-level manifest/path mismatch for a hybrid extension (VisualStudio.Extensibility + VSSDK payload), not a runtime code issue.
In observed cases, the installer operated with PerMachine: False while the generated installer manifest path used a machine-rooted token form that was rejected during validation.
What to check first
- Confirm this is an installer validation failure (not ActivityLog runtime activation failure).
- Inspect final produced VSIX metadata and any generated install manifest traces.
- Map the invalid path entry back to your manifest patch/repack logic (for example custom post-
CreateVsixContainer tasks).
- Reconcile install scope and manifest path/token generation rules for the targeted VS channel/version.
Remediation direction
- Keep one authoritative packaging strategy and ensure generated install paths are valid for the selected install scope.
- If you patch
extension.vsixmanifest and inject Microsoft.VisualStudio.VsPackage assets, verify that the resulting installer metadata does not emit invalid [installdir]...\VSExtensions\... entries.
- Re-test installation on all supported SKUs/channels (for example Enterprise + Insiders), not just one instance.
Known Installer Failure: cross-manifest Version mismatch
Symptom signature
- VSIXInstaller fails with an error similar to:
Version value mismatch. VsixManifest value: 'X'; Catalog manifest value: 'Y'; Package manifest value: 'X'
InvalidOperationException: ... property 'Version' is not consistent across manifests
Why this happens
Hybrid repack flows often patch extension.vsixmanifest and manifest.json but forget catalog.json.
Setup Engine validates all of them together, so stale catalog.json values (for example 0.0.0.0) cause install rollback.
What to verify
Ensure all three are aligned:
extension.vsixmanifest <Identity ... Version="...">
manifest.json root "version"
catalog.json package/info version values
Quick audit pattern
$vsix = Get-ChildItem '.\src' -Filter *.vsix -Recurse |
Sort-Object LastWriteTime -Descending |
Select-Object -First 1
Add-Type -AssemblyName System.IO.Compression.FileSystem
$zip = [System.IO.Compression.ZipFile]::OpenRead($vsix.FullName)
try {
foreach ($name in 'extension.vsixmanifest','manifest.json','catalog.json') {
$entry = $zip.Entries | Where-Object { $_.FullName -ieq $name } | Select-Object -First 1
if (-not $entry) {
Write-Host "Missing: $name"
continue
}
$reader = New-Object System.IO.StreamReader($entry.Open())
try {
$text = $reader.ReadToEnd()
Write-Host "---- $name ----"
if ($name -eq 'extension.vsixmanifest') {
($text | Select-String 'Identity .* Version=\"([^\"]+)\"').Matches.Value
} else {
$text | Select-String '"version"\s*:\s*"[^"]+"' -AllMatches | ForEach-Object { $_.Matches.Value }
}
}
finally {
$reader.Dispose()
}
}
}
finally {
$zip.Dispose()
}
Remediation direction
- If you patch one manifest, patch all three (
extension.vsixmanifest, manifest.json, catalog.json) in the same post-pack step.
- Add build assertions that fail when:
- any installer-facing manifest still contains
0.0.0.0,
- or these files disagree on extension version.
Known CI Failure: xUnit v3 test process startup failure
Symptom signature
Why this happens
Setting <OutputType>Exe</OutputType> and <UseAppHost>true</UseAppHost> in the test project forces it into an executable host shape.
The xunit.runner.visualstudio VSTest adapter (version 3.x) launches the test binary as a subprocess and expects to receive a multi-object JSON stream back.
When the binary runs as a standalone executable, it emits only initial metadata JSON and then exits; the adapter rejects this non-streaming output with the above error.
What to check first
- Open
tests/ContextRelay.Core.Tests/ContextRelay.Core.Tests.csproj.
- Confirm neither
<OutputType>Exe</OutputType> nor <UseAppHost>true</UseAppHost> is set.
Remediation
Remove both properties from the test project <PropertyGroup>:
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
- <OutputType>Exe</OutputType>
- <UseAppHost>true</UseAppHost>
<RootNamespace>ContextRelay.Core.Tests</RootNamespace>
</PropertyGroup>
The xunit.runner.visualstudio package version in use (3.1.5) remains compatible once the executable host forcing is removed.
Do not replace it with the non-existent xunit.runner.visualstudio.v3 package — that package ID does not exist on NuGet.
Known Tool Window Failure: frame construction canceled (OperationCanceledException)
Symptom signature
- Visual Studio shows:
Construction of frame content failed
- frame caption is your extension panel/tool window
- ActivityLog stack is mostly VS internals, for example:
ThreadingTools.WithCancellationSlow
RemoteToolWindow.EnsureToolWindowProviderAsync
RemoteToolWindow.GetContentAsync
WindowFrame.ConstructContent...
- Exception type:
System.OperationCanceledException
Why this happens
Panel/tool window creation can inherit cancellation tokens from command execution or shell lifecycle transitions.
If panel initialization depends on those transient tokens, cancellation can occur during provider/content construction and surface as a user-visible frame construction failure.
What to check first
- In your
ToolWindow.GetContentAsync(...), verify whether deferred initialization is started with the incoming cancellationToken.
- In command handlers that call
ShowToolWindowAsync(...), verify whether command cancellation token is passed through.
- Inspect catch filters that swallow cancellation only when a specific token is canceled (
when (token.IsCancellationRequested)), which can miss linked/transient cancellation sources.
Remediation direction
- Decouple deferred panel initialization from transient shell/command cancellation:
- run deferred initialization with
CancellationToken.None (or a dedicated lifetime token).
- For open-panel commands, avoid passing short-lived command tokens directly into
ShowToolWindowAsync(...) when this causes intermittent panel creation aborts.
- Treat initialization-time
OperationCanceledException as non-fatal unless the extension is being disposed intentionally.
Verification
- Rebuild/reinstall VSIX.
- Open panel repeatedly from command + menu paths.
- Confirm ActivityLog no longer records frame-construction errors for the panel.
- Confirm normal disposal/shutdown still works (no leaked background work).
Known Menu Failure: unresolved %ContextRelay.*.DisplayName% tokens
Symptom signature
- Visual Studio Tools menu shows literal resource tokens such as:
%ContextRelay.Menu.DisplayName%
%ContextRelay.Command.<Name>.DisplayName%
- The affected extension's
.vsextension/extension.json contains those %...% strings in controlContainers[].displayName, tooltipText, or commandSets[].commands[].
.vsextension/string-resources.json may still be present in the VSIX, so the package looks correct at first glance.
Why this happens
Microsoft.VisualStudio.Extensibility requires command/menu metadata strings in source to reference string-resources.json keys; otherwise analyzer CEE0027 fails the build. In some Visual Studio channels, menu metadata token resolution can fail at runtime and the shell displays the raw %...% key.
Remediation
- Keep source
CommandConfiguration and MenuConfiguration strings in %string-resource-key% form so the SDK analyzer remains satisfied.
- Patch the generated
.vsextension/extension.json after the SDK emits it, replacing only known extension-owned display tokens with stable display text.
- Patch both:
- the bin output
.vsextension/extension.json, for local build/test reflection checks
- the produced VSIX
.vsextension/extension.json, for installer/runtime metadata
- Add tests and build assertions that fail if produced extension metadata still contains
%ContextRelay. tokens.
Verification
- Build the extension.
- Inspect bin output
.vsextension/extension.json for unresolved extension-owned tokens.
- Inspect VSIX
.vsextension/extension.json for unresolved extension-owned tokens.
- Reinstall the VSIX into a clean Visual Studio instance or clear the target instance's extension metadata cache before checking Tools menu UI.
Known Publish Failure: publisher name mismatch
Symptom signature
- Marketplace publish step (for example
VsixPublisher.exe publish) fails with an error similar to:
The publisher '<name>' is not the publisher of identity '<registered-publisher>'
- or
The publisher field value does not match the authenticated publisher account
- Or the VSIX installs locally but shows the wrong publisher/author in VS Extension Manager.
Why this happens
The publisher/author display name in the VSIX metadata must exactly match the Publisher display name registered on Visual Studio Marketplace. This is distinct from the Marketplace Publisher ID used by publishing tools:
| File | Field |
|---|
source.extension.vsixmanifest | <Identity ... Publisher="..."> |
extension.vsixmanifest (output) | same after detokenization |
ContextRelayExtension.cs | ExtensionConfiguration.Metadata.publisherName |
vs-publish.json | "publisher" is the Marketplace Publisher ID, not the display name |
For this repository, the Marketplace Publisher ID is KazushiKamegawa and the correct publisher/author display name is kkamegawa.
What to check
- Confirm
source.extension.vsixmanifest contains Publisher="kkamegawa".
- Confirm
vs-publish.json contains Publisher ID "publisher": "KazushiKamegawa".
- After any repack/patch step, open the produced VSIX and verify the display name is preserved.
- If you use a build task that patches publisher metadata, ensure it does not substitute the Publisher ID for the display name.
Quick audit pattern
$vsix = Get-ChildItem '.\src' -Filter *.vsix -Recurse |
Sort-Object LastWriteTime -Descending |
Select-Object -First 1
Add-Type -AssemblyName System.IO.Compression.FileSystem
$zip = [System.IO.Compression.ZipFile]::OpenRead($vsix.FullName)
try {
foreach ($name in 'extension.vsixmanifest') {
$entry = $zip.Entries | Where-Object { $_.FullName -ieq $name } | Select-Object -First 1
if (-not $entry) { Write-Host "Missing: $name"; continue }
$reader = New-Object System.IO.StreamReader($entry.Open())
try {
$text = $reader.ReadToEnd()
Write-Host "---- $name ----"
$text | Select-String -Pattern 'Publisher|publisher' -AllMatches |
ForEach-Object { $_.Matches.Value }
} finally { $reader.Dispose() }
}
} finally { $zip.Dispose() }
Expected output: VSIX publisher/author display-name metadata references kkamegawa.
Remediation direction
- Keep
Publisher="kkamegawa" in source.extension.vsixmanifest as the display-name source of truth.
- Keep
"publisher": "KazushiKamegawa" in vs-publish.json as the Marketplace Publisher ID.
- Ensure any post-pack manifest patch task preserves the VSIX display-name metadata.
Guardrails
- Do not assume behavior from an older Visual Studio version applies to current preview/insiders builds.
- Do not use
VsRegEdit.exe as a substitute for full pkgdef install flow unless you explicitly need registry diagnostics.
- Do not accept a one-SKU-only fix if your extension targets Community/Pro/Enterprise.
- Do not merge changes without validating both:
- Options node visibility
- property page load behavior
- Do not set
OutputType=Exe or UseAppHost=true in the test project; this causes xUnit v3 catastrophic startup failure in CI.
- Do not conflate the Marketplace Publisher ID
KazushiKamegawa with the publisher display name kkamegawa.
- Prefer VSSDK / VisualStudio.Extensibility APIs over direct Win32 API usage whenever both are available (for example, prefer VS shell-owned file dialog APIs over
Microsoft.Win32.OpenFileDialog), because VS-owned APIs preserve proper IDE ownership, modality, and compatibility.
- For VS extension panel/tool window fixes, invoke this skill first and follow its panel-cancellation checklist before changing runtime code.
How to Adapt This Skill to a Specific Extension
Replace these placeholders in your investigation notes:
- Extension/package IDs and GUIDs
- Expected VSIX entries (dll/pkgdef/asset paths)
- Known-good baseline commit/PR
- Known error signatures for your package
- Required supported VS SKUs/architectures
You can then keep this same workflow and checklist unchanged.
Learn More
| Topic | How to Find |
|---|
devenv /log syntax | microsoft_docs_search(query="/Log devenv Visual Studio") |
| Activity log usage | microsoft_docs_search(query="Visual Studio use the activity log VSPackage") |
| Experimental Instance behavior | microsoft_docs_search(query="Visual Studio experimental instance /RootSuffix Exp") |
| Package registration and pkgdef | microsoft_docs_search(query="Visual Studio registering VSPackages pkgdef") |
| Tools > Options pages | microsoft_docs_search(query="Visual Studio ProvideOptionPage Tools Options") |
CLI Alternative
If the Learn MCP server is not available, use the mslearn CLI instead:
| MCP Tool | CLI Command |
|---|
microsoft_docs_search(query: "...") | mslearn search "..." |
microsoft_code_sample_search(query: "...", language: "...") | mslearn code-search "..." --language ... |
microsoft_docs_fetch(url: "...") | mslearn fetch "..." |
Run directly with npx @microsoft/learn-cli <command> or install globally with npm install -g @microsoft/learn-cli.