Troubleshooting
Common issues and solutions for VS Code extension development.
Installation Versus Navigation
- Clarify what is missing: the browser's Install handoff, the Extensions search result, the installed extension, or an Activity Bar icon. These are separate paths; an installed extension need not contribute a sidebar.
- For a browser handoff, compare the registered
vscode:handler and the process that received--open-urlwith the intended VS Code executable/profile. Multiple installed, portable or test builds can route to a different window. Do not close shared windows or change associations without approval. - For an empty
@id:<publisher>.<name>result, compare the public gallery's exact-ID response, engine/platform compatibility and the running build's gallery/resource endpoints. A working public page or one CDN's 404 does not establish the search failure's cause without matching request/log evidence. - With installation approval, use the intended build's CLI to install the extension ID, then confirm that exact ID/version using its
--list-extensions --show-versionsand the same profile. A successful CLI install proves that route, not that search or the browser handoff was repaired. Never trigger real extension tasks merely to test installation. - For a missing Activity Bar icon, inspect view-container contributions, packaged SVG and saved view placement; see TreeView. Avoid reinstalling or resetting unrelated UI when the entry point was never contributed.
Extension Not Loading
| Symptom | Cause | Solution |
|---|---|---|
| Extension never activates | Missing activationEvents |
Add to package.json: "activationEvents": ["onStartupFinished"] |
| "Extension is not active" | Wrong activation trigger | Use "*" to always activate (dev only) or specific event |
| Works in dev, not installed | Build output not included | Check .vscodeignore, ensure out/ is included |
Debug Activation
// Add at top of activate() while debugging, or route this through your logger.
const output = vscode.window.createOutputChannel("My Extension");
output.appendLine("Extension activating...");
output.show(true);Prefer Output Channel logs for extension diagnostics. Use Help → Toggle Developer Tools → Console only for temporary investigation or webview/runtime errors that are not reaching your logger.
Command Not Found
| Symptom | Cause | Solution |
|---|---|---|
| "command not found" | ID mismatch | Ensure same ID in package.json and registerCommand() |
| Command not in palette | Missing contributes.commands | Add command definition to package.json |
| Command defined but fails | Extension not activated | Check activationEvents includes the command |
Verify Command Registration
// In activate()
const output = vscode.window.createOutputChannel("My Extension");
const commands = await vscode.commands.getCommands();
output.appendLine(
`Registered: ${commands.filter((c) => c.includes("myExt")).join(", ")}`,
);Keyboard Shortcuts Not Working
| Symptom | Cause | Solution |
|---|---|---|
| Shortcut does nothing | when clause too restrictive |
Remove or broaden when condition |
| Works sometimes | Context-dependent when |
Check active editor, focus state |
| Conflict with other | Another extension/VS Code uses it | Use unique key combination |
Check for Conflicts
- Ctrl+K Ctrl+S → Open Keyboard Shortcuts
- Search for your key combination
- Look for conflicts (multiple entries)
Common when Issues
// ❌ Doesn't work in editor
"when": "!inputFocus"
// ✅ Works everywhere
"when": "" // or omit entirely
// ✅ Only in editor with text focus
"when": "editorTextFocus"Packaging Issues
| Symptom | Cause | Solution |
|---|---|---|
| VSIX too large (100MB+) | node_modules shipped, incl. a huge transitive dep |
Exclude node_modules/** in .vscodeignore when out/ needs no external runtime packages (see below) |
| Files missing in VSIX | Over-aggressive ignore | Use npx @vscode/vsce ls to check |
| Listing icon missing | Missing PNG or incorrect manifest path | Check the top-level icon and packaged PNG |
| Activity Bar entry missing | Missing container, SVG or hidden/relocated view | Check viewsContainers.activitybar, views[containerId], packaged SVG and saved view placement |
End of central directory record signature not found on install |
Truncated / corrupt VSIX (build interrupted) | Re-run vsce package; verify with code --install-extension <vsix> --force before publish |
Inspect VSIX Contents
# List what will be packaged
npx @vscode/vsce ls
# Extract and inspect VSIX
unzip -l my-extension-1.0.0.vsixWhen it is safe to exclude node_modules/** entirely
A bundled extension (esbuild/webpack) needs no node_modules in the VSIX. An
unbundled extension only needs the packages its compiled out/ actually
requires at runtime. Check before trusting dependencies:
# What does the compiled output actually require at runtime?
Select-String -Path out\*.js -Pattern 'require\("([^.][^"]+)"\)' -AllMatches |
ForEach-Object { $_.Matches } | ForEach-Object { $_.Groups[1].Value } |
Sort-Object -Unique
# Also scan for dynamic import("pkg")If the only externals are vscode (provided by the host) and Node built-ins
(fs, path, http, child_process, ...), add node_modules/** to
.vscodeignore and ship none of it. A dependency that is only reached through a
guarded dynamic import() disabled inside the extension host is dead weight
— e.g. @github/copilot-sdk pulls a ~285MB @github/copilot tree that kept one
VSIX at 181MB; excluding node_modules produced an identical-functioning ~45KB
build.
Always list every entry, not just the size
# Enumerate all VSIX entries and flag leaked temp files
Add-Type -AssemblyName System.IO.Compression.FileSystem
$z = [System.IO.Compression.ZipFile]::OpenRead((Resolve-Path my-extension-1.0.0.vsix))
$z.Entries | Sort-Object FullName | ForEach-Object { '{0,8} {1}' -f $_.Length, $_.FullName }
$z.Dispose()Block the build if temp runner scripts (_*.ps1), logs, or stray *.vsix
leaked in, and add the matching ignore patterns (*.ps1, *.log, *.vsix) to
.vscodeignore. Compare the new VSIX size against the previous version: an
unexpectedly large or unchanged-huge size means .vscodeignore is not excluding
node_modules.
Publishing Errors
| Symptom | Cause | Solution |
|---|---|---|
| PAT invalid | Wrong scope or expired | Regenerate with Marketplace Manage scope |
| Publisher not found | ID mismatch | Verify publisher ID matches exactly |
| Version exists | Already published | Increment version number |
| README not showing | Wrong filename case | Must be README.md not README.MD |
Runtime Errors
| Symptom | Cause | Solution |
|---|---|---|
| "Cannot find module" | Dependency not bundled | Add to dependencies (not devDependencies) or bundle |
| API undefined | Wrong VS Code version | Check engines.vscode matches API used |
| Permission denied | Restricted API | Check extension permissions/capabilities |
CancellationToken.None does not exist |
Assumed from other SDKs | Create and dispose a CancellationTokenSource, then pass its token |
Persistent State Across Windows
When an extension mirrors state between a file and globalState, treat persistence as one invariant rather than separate patches:
- replace file snapshots atomically with same-directory temporary write/sync/rename;
- write payload before revision metadata and serialize foreground saves with mirrors per destination;
- use a proven heartbeat/stale-aware cross-process lock, then re-read revision inside the lock before writing;
- on revision conflict, reload the winning snapshot and ask the user to retry instead of merging deletes heuristically;
- do not timeout an uncancellable
globalState.update()and release the lock while that write can still land later.
Test empty/corrupt/meta-less snapshots, valid revision-backed empty deletes, mirror failure ordering, stale-window conflicts, lock recovery, and failed atomic replacement preserving the previous target.
Check VS Code API Version
// package.json - specify minimum VS Code version
"engines": {
"vscode": "^1.80.0"
}Do not assume the installed editor version exists as an @types/vscode package. Stable/preview editor builds can be ahead of the npm type release and npm install then fails with ETARGET. Check npm view @types/vscode version, use the newest published types that contain the APIs you need, and set engines.vscode to the lowest supported API version, not automatically to the developer's current editor version.
Debug Tips
Enable Verbose Logging
const outputChannel = vscode.window.createOutputChannel("My Extension");
outputChannel.appendLine("Debug message");
outputChannel.show();Keep runtime diagnostics behind a small logger wrapper so tests can assert the logging route and production code does not accumulate stray console.log calls.
Extension Host Logs
- Help → Toggle Developer Tools
- Console tab
- Filter by your extension name
Reload Without Restart
- Ctrl+Shift+P → "Developer: Reload Window"
Driving Copilot Chat From an Extension
Extensions that launch chat via the internal workbench.action.chat.open command (passing mode, modelSelector, or a settings-based reasoning effort) hit a recurring confusion: writing the setting correctly is not the same as the host honoring it.
- Separate the two failure surfaces: (1) does your extension pass the right argument / write the right config, and (2) does the host act on it. Confirm (1) from your own Output Channel logs, then judge (2) from host behavior — do not conclude (1) is broken from a (2) symptom.
- Inspect host runtime logs (and the VS Code source for the internal command) early. Do not theorize about model-specific differences (e.g. "Opus ignores it") before verifying the same
mode/reasoningEffortis actually sent for every model. In practice the arguments are identical across models and the difference is host-side handling. - Reasoning effort applied through the settings path (e.g. an experimental
chatLanguageModels.jsonentry in global storage) is written by the extension but honored by the platform. If the chosen depth is not reflected, the extension can still be correct — surface a short UI note ("applied depending on platform support") instead of chasing a non-existent bug. - These internal commands are undocumented and version-sensitive. Guard the argument shape and add fallback tiers (e.g. retry without reasoning effort before a legacy fallback); never let a fallback silently drop both the agent and the reasoning effort.
Discovering .agent.md Custom Agents
If your extension lists custom agents in a picker, exclude agents whose frontmatter sets user-invocable: false — those cannot be directly invoked and only confuse users when shown. A surprising "agent can't be selected" report is usually this flag, not a model or API limitation.
- Cache agent discovery and invalidate it from a file watcher on
**/*.agent.md(and**/AGENTS.md); raise any scan cap well above a handful of files, and warm the cache on activation so the first picker open is not empty.
Quick Fixes Summary
# Clean rebuild
rm -rf out/ node_modules/
npm install
npm run compile
# Reset installed extension
code --uninstall-extension publisher.extension-id
npx @vscode/vsce package
code --install-extension ./extension-1.0.0.vsix
# Check what's in your VSIX
npx @vscode/vsce lsWebview 真っ白 / SyntaxError
| 症状 | 原因 | 解決策 |
|---|---|---|
| 画面真っ白 | JavaScript SyntaxError | Webview DevTools Console でエラー確認 |
Invalid regular expression: /^*/ |
正規表現のバックスラッシュが消えた | テンプレート内で二重エスケープ (\\d, \\s) |
Unexpected token |
minify時にクォートが崩れた | data-action + イベント委譲パターンに変更 |
| ボタンが反応しない | innerHTML後の onclick が効かない | document.addEventListener で委譲 |
デバッグ手順
- Developer: Open Webview Developer Tools を実行
- Console タブでエラーを確認
- ビルド出力
out/extension.jsで該当行を検索 - ソースの正規表現/クォートを修正し再ビルド
命名の不一致
| 症状 | 原因 | 解決策 |
|---|---|---|
| 設定が効かない | 設定キーがコードと不一致 | package.json と getConfiguration() を統一 |
| コマンドが見つからない | コマンドIDがpackage.jsonと不一致 | 全箇所で同じIDを使用 |
命名一貫性チェック
# package.json のコマンド/設定キーを抽出
grep -E '"myExt\.' package.json
# ソースコードの使用箇所を検索
grep -r "myExt\." src/公開前に統一することを強く推奨(公開後は既存ユーザーの設定が壊れる)。