| name | live-inspect |
| description | Use for one-off / single-question inspection of the running GitLens extension — examining UI state, reading logs, checking feature flags, dispatching a command, or asking "what does the live DOM look like right now". Reference for `vscode-inspector` MCP primitives. For iterative debug-and-fix loops on UI bugs (sweep → fix → re-verify), use `/live-exercise` instead. |
/live-inspect — Live Extension Inspection
Launch a real VS Code instance with GitLens loaded, then inspect UI elements, read logs, interact with views, and evaluate runtime values — all programmatically via Playwright.
MCP Server (Preferred for Iterative Inspection)
The vscode-inspector MCP server provides a persistent, interactive session. It launches VS Code once and exposes tools for screenshot/click/inspect/rebuild cycles — much faster than the batch CLI for agentic feedback loops.
The server is auto-discovered via .mcp.json when Claude Code starts in this repo. When connected, these MCP tools are available:
| Tool | Purpose |
|---|
launch | Start VS Code with GitLens loaded (persistent session) |
teardown | Close VS Code and clean up |
get_status | Check if session is running |
screenshot | Capture window or webview as inline image (capped at 1920px) |
execute_command | Run any VS Code command by ID |
click | Click element by CSS selector (main UI or webview) |
type_text | Type text into inputs |
press_key | Press keyboard shortcuts |
inspect_dom | Query DOM elements for text/HTML/attributes/shadowDOM |
aria_snapshot | Get accessibility tree as YAML (supports webview iframes) |
evaluate | Run JS in extension host with vscode API |
evaluate_in_webview | Run JS in webview renderer (DOM, shadow DOM, computed styles) |
list_webviews | Discover all open webviews with titles, dimensions, content status |
wait_for_webview | Wait for a webview to finish loading and Lit hydration |
read_logs | Search extension output logs |
read_console | Read browser console messages/errors from the main process |
resize_window | Resize VS Code window content area — only for explicit responsive-breakpoint testing |
rebuild_and_reload | Build extension + restart extension host |
Typical Workflow
- Call
launch (once per session — takes ~10s)
- Call
execute_command to open the view you want to inspect
- Call
list_webviews to discover open webviews and their exact titles
- Call
wait_for_webview { webview_title: "<title>" } to wait for Lit hydration
- Call
screenshot { target: "webview", webview_title: "<title>" } or aria_snapshot { webview_title: "<title>" } to see the current state
- Make code changes, then rebuild and reload (see below)
- Call
screenshot again to verify changes
- Repeat steps 6-7 as needed
- Call
teardown when done
Rebuilding After Code Changes
Extension host code (commands, providers, services, models, parsers — anything under src/ outside src/webviews/apps/):
rebuild_and_reload { build_command: "pnpm run build:extension" }
This restarts the extension host with the new code. All tools continue to work on the same VS Code instance.
Webview code (Lit components, CSS, templates under src/webviews/apps/): No extension host restart needed. Build the webviews, then use the view's refresh command:
rebuild_and_reload { build_command: "pnpm run build:webviews" }
execute_command { command: "gitlens.views.home.refresh" }
Every GitLens webview has a gitlens.views.<name>.refresh command (e.g. gitlens.views.welcome.refresh, gitlens.views.graph.refresh, gitlens.views.commitDetails.refresh). These fully reload the webview with fresh JS/CSS.
Both changed: Use pnpm run build:quick (builds extension + webviews, no linting), then refresh the relevant view.
Quick Examples
Extension host code change:
launch {}
execute_command { command: "gitlens.showHomeView" }
screenshot {}
# ... edit extension host code ...
rebuild_and_reload { build_command: "pnpm run build:extension" }
screenshot {}
teardown
Webview code change:
launch {}
execute_command { command: "gitlens.showWelcomeView" }
inspect_dom { selector: "h1", in_webview: true }
# ... edit webview code ...
rebuild_and_reload { build_command: "pnpm run build:webviews" }
execute_command { command: "gitlens.views.welcome.refresh" }
inspect_dom { selector: "h1", in_webview: true }
teardown
Batch CLI (Fallback for One-Shot Inspection)
scripts/e2e-dev-inspect.mjs — a general-purpose CLI that supports ordered, repeatable actions. Use this when the MCP server is not available or for quick one-off inspections.
Two Modes
| Mode | Flag | ExtensionMode | container.debugging | gitkraken.env | evaluate() |
|---|
| Development (default) | (none) | Development | true | ✅ respected | ❌ |
| Test | --with-evaluator | Test | false | ❌ ignored | ✅ |
Use Development mode when you need gitkraken.env (e.g. testing feature flags against dev API).
Use Test mode when you need evaluate() to inspect runtime values (e.g. vscode.env.machineId).
Common Recipes
Inspect any view's DOM content
node scripts/e2e-dev-inspect.mjs --command gitlens.showWelcomeView --query-frame h1
The --query-frame action searches all frames (including nested webview iframes) for matching elements and prints their text content.
Get the full accessibility tree of a view
node scripts/e2e-dev-inspect.mjs --command gitlens.showHomeView --aria
Inspect a specific DOM element
node scripts/e2e-dev-inspect.mjs --command gitlens.showWelcomeView --aria-selector "[class*='header']"
Click something, then inspect the result
node scripts/e2e-dev-inspect.mjs \
--command gitlens.showHomeView \
--click-frame "button.start-work" \
--pause 2000 \
--query-frame ".dialog-content h2"
Read runtime values (requires --with-evaluator)
node scripts/e2e-dev-inspect.mjs --with-evaluator \
--eval "vscode.env.machineId" \
--eval "vscode.version" \
--eval "vscode.env.appName"
Check feature flag behavior with dev environment
node scripts/e2e-dev-inspect.mjs --env dev \
--command gitlens.showWelcomeView \
--query-frame h1 \
--logs FeatureFlagService
Search extension logs for any pattern
node scripts/e2e-dev-inspect.mjs --logs "error"
node scripts/e2e-dev-inspect.mjs --env dev --logs ConfigCat
Take a screenshot
node scripts/e2e-dev-inspect.mjs --command gitlens.showGraphView --screenshot /tmp/graph.png
Keep VS Code open for manual interaction
node scripts/e2e-dev-inspect.mjs --env dev --keep-open
Add custom settings
node scripts/e2e-dev-inspect.mjs \
--setting "gitlens.currentLine.enabled=true" \
--setting "gitlens.hovers.currentLine.over=line" \
--command gitlens.showWelcomeView --aria
WSL / SSH / Headless Linux
If VS Code is not installed natively in your Linux environment, use --download-vscode
to download a portable binary. Xvfb is started automatically if no $DISPLAY is set.
node scripts/e2e-dev-inspect.mjs --download-vscode --command gitlens.showHomeView --aria
Requires xvfb package for headless environments: sudo apt-get install xvfb
How AI Agents Should Use This
Prefer the MCP server for iterative work. Call launch once (use download_vscode: true on WSL/SSH/headless Linux), then use tools in a loop. No output parsing needed — tools return structured results directly.
Choosing the right tool
| I want to... | MCP tool | CLI flag |
|---|
| Discover open webviews | list_webviews | (N/A) |
| Wait for a webview to load | wait_for_webview | (N/A) |
| Read text from a webview | inspect_dom with in_webview: true | --query-frame <selector> |
| See all UI elements and their states | aria_snapshot with in_webview: true | --aria |
| Inspect Lit shadow DOM content | inspect_dom with property: "shadowDOM" and in_webview | (N/A) |
| Run JS in a webview (DOM/styles/state) | evaluate_in_webview | (N/A) |
| Read text from the main VS Code UI | inspect_dom | --query <selector> |
| Click a button/link in a webview | click with in_webview: true | --click-frame <selector> |
| Read a runtime value (extension host) | evaluate | --with-evaluator --eval "expr" |
| Execute a VS Code command | execute_command | --command <id> |
| Check extension logs | read_logs | --logs <pattern> |
| Check main process console errors | read_console { level: "error" } | (N/A) |
| See what the UI looks like | screenshot | --screenshot <path> |
| Test a specific responsive breakpoint | resize_window | (N/A) |
GitLens Webview Reference
| Command | Webview Title | Root Element | Refresh Command |
|---|
gitlens.showHomeView | Home | gl-home-app | gitlens.views.home.refresh |
gitlens.showWelcomeView | Welcome | gl-welcome-page | (N/A) |
gitlens.showGraphPage | Commit Graph | gl-graph-app | gitlens.graph.refresh |
gitlens.showGraphView | Commit Graph | gl-graph-app | gitlens.views.graph.refresh |
gitlens.showCommitDetailsView | Inspect | gl-commit-details-app | gitlens.views.commitDetails.refresh |
| (sidebar) | Commit Graph Inspect | gl-commit-details-app | gitlens.views.graphDetails.refresh |
gitlens.showTimelinePage | Visual History | gl-timeline-app | gitlens.timeline.refresh |
gitlens.showTimelineView | Visual File History | gl-timeline-app | gitlens.views.timeline.refresh |
gitlens.showComposerPage | Commit Composer | gl-composer-app | gitlens.composer.refresh |
gitlens.showPatchDetailsPage | Patch | gl-patch-details-app | gitlens.patchDetails.refresh |
gitlens.showSettingsPage | GitLens Settings | gl-settings-app | gitlens.settings.refresh |
Root element tag convention: gl-<name>-app. Use these for inspect_dom selectors and evaluate_in_webview queries.
Inspecting Webview Content
GitLens webviews use Lit web components with Shadow DOM. Here's the recommended approach:
- Discover:
list_webviews to find open webviews. Output includes index, id (e.g. gitlens.views.commitDetails), title, url, dimensions, and content status. (Or use the reference table above.)
- Wait:
wait_for_webview { webview_title: "Home" } to ensure Lit hydration is complete
- Structure:
aria_snapshot { webview_title: "Home" } for the accessibility tree
- Shadow DOM:
inspect_dom { selector: "gl-home-app", property: "shadowDOM", in_webview: true, webview_title: "Home" } to see rendered Lit templates
- JS state:
evaluate_in_webview { expression: "document.querySelector('gl-home-app').shadowRoot.querySelector('.my-element').textContent" } to read shadow DOM content. Use .shadowRoot.querySelector() to reach elements inside Lit shadow roots — plain document.querySelector() cannot cross shadow boundaries.
- Styles:
evaluate_in_webview { expression: "getComputedStyle(document.querySelector('gl-home-app').shadowRoot.querySelector('.my-element')).color" } for computed styles
- Errors:
read_console { level: "error" } to check for JS errors in the main process. For webview-specific errors, use evaluate_in_webview to inspect state directly.
Targeting a specific webview when multiple are open
When more than one webview is visible (e.g. Graph + Commit Details), webview_title matching can silently fail because outer-frame titles are often empty for unfocused webviews, and the tool falls back to "first webview with content" — which is usually the wrong one. All webview-targeting tools (evaluate_in_webview, wait_for_webview, inspect_dom, aria_snapshot, screenshot, click) accept three matchers:
webview_title — outer-frame title (best when titles are reliable; matches the table above)
webview_url — case-insensitive substring of the webview URL. Matches the id/purpose/extensionId query params in vscode-webview:// URLs. Use a fragment like "commitDetails" or "graph".
webview_index — 0-based index from list_webviews. Deterministic fallback when title and URL matching are insufficient.
Precedence: index → url → title → first-with-content. Pick webview_url first (intent-revealing); fall back to webview_index if needed.
list_webviews
# returns [{ index: 0, id: "gitlens.views.graph", ... }, { index: 1, id: "gitlens.views.commitDetails", ... }]
evaluate_in_webview { webview_url: "commitDetails", expression: "performance.now()" }
Screenshot Best Practices
Always target a specific webview instead of taking full-window screenshots:
screenshot { target: "webview", webview_title: "Home" }
This captures just the webview content instead of the entire VS Code window. All screenshots are automatically capped at 1920px to stay within Claude's 2000px multi-image limit — no configuration needed.
Use resize_window only when explicitly testing a responsive breakpoint — it resizes the actual Electron window and is clamped by the host display. For larger headless render surfaces, use launch({ screen_resolution }) instead.
Troubleshooting
Webview frame access fails / "not found" errors: Try launch { disable_site_isolation: true }. This disables OOPIF site isolation so Playwright can access webview iframes directly. Note: CORS/CSP are also disabled, so webview behavior may differ slightly from production.
Headless screenshots too small: Use launch { screen_resolution: "2560x1440x24" } for a larger Xvfb display (default: 1920x1080x24).
All Options
| Flag | Description |
|---|
--env <env> | Set gitkraken.env (e.g. dev, staging) |
--with-evaluator | Enable HTTP evaluator bridge (Test mode) |
--keep-open | Keep VS Code running (Ctrl+C to stop) |
--setting <key=value> | Custom VS Code setting (repeatable) |
--wait <ms> | Default wait between actions (default 3000) |
--activation-wait <ms> | Wait time for GitLens activation (default 8000) |
--workspace <path> | Path to open as workspace |
--vscode-path <path> | Path to VS Code Electron binary |
--download-vscode | Download a portable VS Code binary (WSL/SSH/CI) |
--flavor <stable|insiders> | VS Code variant to auto-detect (default: stable) |
--command <cmd> | Execute VS Code command |
--aria | Print full window aria snapshot |
--aria-selector <sel> | Print aria snapshot of specific element |
--query <sel> | Print textContent matching selector |
--query-frame <sel> | Search all frames for selector |
--click <sel> | Click element |
--click-frame <sel> | Click inside webview iframe |
--screenshot <path> | Save screenshot |
--logs [pattern] | Search extension logs |
--eval <expr> | Evaluate JS expression in extension host |
--pause <ms> | Wait specified duration |
Exercising Pro-gated features
Pro-gated features (the Commit Graph beyond local repos, Launchpad, Worktrees beyond 1, Cloud Patches, Composer, all AI features, Drafts, Workspaces, etc.) check the user's subscription before unlocking. You can't exercise these without a Paid/Trial subscription on the session.
The extension ships a subscription simulator in DEBUG builds that overrides the session's subscription state without touching the real account.
Setup
execute_command { command: "gitlens.plus.simulate.subscription", args: [{ "state": "Paid", "planId": "pro", "dismissOnboarding": true }] }
Returns true on start, false on stop. Pass dismissOnboarding: true to also pre-dismiss every GitLens onboarding tour/banner (composer welcome, home walkthrough, MCP banner, rebase-editor warning, integration banner, SCM-grouped welcome) — they're full-screen overlays that intercept clicks during automation. State is restored on stop.
State reference
The state field accepts the friendly subscription name (resolves to the numeric SubscriptionState enum at runtime). Most-common picks first:
state | Plan options (planId) | What it simulates |
|---|
"Paid" | "pro", "advanced", "student", "teams", "enterprise" | Active paid subscription — unlocks all Pro features |
"Trial" | "pro" (default), "advanced", "student" | Active trial — unlocks all Pro features for the trial duration |
"Community" | — | No account, Community tier (Pro features locked) |
"TrialExpired" | — | Account exists, trial used up, no longer eligible |
"TrialReactivationEligible" | — | Account exists, trial used up, eligible to reactivate |
"VerificationRequired" | — | Account created but email not verified |
Optional modifiers:
| Modifier | Pairs with | Effect |
|---|
reactivatedTrial: true | state: "Trial" | Reactivated trial (vs. fresh) |
expiredPaid: true | state: "Paid" | Expired paid (downgrades to Community at the gate) |
featurePreviews: { day, durationSeconds } | state: "Community" | Pro Preview window. day: 0 = day 1, 1 = day 2, 2 = day 3, proFeaturePreviewUsages = expired. durationSeconds: window timeout |
dismissOnboarding: true | any | Pre-dismiss onboarding overlays (see Setup) |
Common recipes
For other states, swap state/planId/modifiers per the tables above — the start-command shape is the same.
Pro user (default for most pro-feature testing):
execute_command { command: "gitlens.plus.simulate.subscription", args: [{ "state": "Paid", "planId": "pro", "dismissOnboarding": true }] }
Community gate (paywall UX):
execute_command { command: "gitlens.plus.simulate.subscription", args: [{ "state": "Community", "dismissOnboarding": true }] }
Feature-preview countdown (Community + temporary Pro Preview):
execute_command { command: "gitlens.plus.simulate.subscription", args: [{ "state": "Community", "featurePreviews": { "day": 0, "durationSeconds": 30 }, "dismissOnboarding": true }] }
# day: 1 → starts on day 2, day: 2 → day 3, day: 3 → preview expired
Stop simulation (mandatory teardown)
execute_command { command: "gitlens.plus.simulate.subscription", args: [{ "state": null }] }
Restores the prior subscription, feature previews, and any onboarding flags that were pre-dismissed via dismissOnboarding: true. Re-calling the start command also clears any prior simulation state.
Exercising AI features
GitLens AI features (Generate Commit Message, Explain *, Generate Changelog, Composer, Review Changes, Generate Search Query) cannot use real provider calls during automated inspection — they cost money, require keys, and produce non-deterministic outputs you can't assert against. The extension ships a deterministic AI simulator in DEBUG builds that you control via VS Code commands.
AI features are also Pro-gated, so two simulators must be enabled in order:
- Subscription simulator — see Exercising Pro-gated features above. Use
state: "Paid", planId: "pro".
- AI simulator (
gitlens.plus.simulate.ai) — replaces the AI provider with a stub that returns content the agent injects. Suppresses the first-run ToS modal and the AI All-Access promo notification automatically.
The AI simulator dispatches on a discriminated op arg: enable, disable, inject, clear, lastMessages. Calling without args opens a QuickPick.
Setup (one-time per session)
execute_command { command: "gitlens.plus.simulate.subscription", args: [{ "state": "Paid", "planId": "pro", "dismissOnboarding": true }] }
execute_command { command: "gitlens.plus.simulate.ai", args: [{ "op": "enable" }] }
dismissOnboarding: true is documented in the Pro-gated features section — only needs to be set on one of the two commands. Both share the same snapshot/restore pattern.
Inject-then-trigger pattern
The agent authors the response content, pushes it onto the simulator's stash, then triggers the AI command. The next sendRequest for that action consumes the inject:
execute_command { command: "gitlens.plus.simulate.ai", args: [{
"op": "inject",
"action": "generate-commitMessage",
"content": "<summary>Stable test summary</summary><body>Deterministic body content.</body>"
}] }
execute_command { command: "gitlens.ai.generateCommitMessage:scm" }
# read SCM input — assert it contains "Stable test summary"
inject payload: { op: "inject"; action?: AIActionType; content: string; sticky?: boolean }. Omit action to inject for the next call regardless of action. Set sticky: true to keep the same content for every call of that action.
Authoring response content
| Action | Format the content must satisfy |
|---|
generate-commitMessage, generate-stashMessage, generate-changelog, generate-create-{cloudPatch|codeSuggestion|pullRequest} | <summary>...</summary><body>...</body> |
explain-changes (commit / branch / stash / wip / unpushed) | Same summary/body XML |
generate-searchQuery | Plain string (the search query) |
review-changes | <overview>...</overview> followed by <area severity="..." files="..."><label>...</label><rationale>...</rationale><findings><finding severity="..." file="..." lines="..."><title>...</title><description>...</description></finding></findings></area> blocks |
generate-commits (Composer) | JSON tool-call output that conserves hunk indices from the prompt — see "Composer authoring" below |
The schemas are enforced in packages/plus/ai/src/utils/results.utils.ts — read it once for the canonical tag set. The simulator returns a sensible default per action when no inject is queued, so smoke calls without explicit injects still produce predictable output.
Mode shortcuts (negative-path UX)
Without injecting per-call, you can globally force a failure mode by re-enabling with a different mode:
execute_command { command: "gitlens.plus.simulate.ai", args: [{ "op": "enable", "mode": "error" }] } # provider error UX
execute_command { command: "gitlens.plus.simulate.ai", args: [{ "op": "enable", "mode": "cancel" }] } # cancellation UX
execute_command { command: "gitlens.plus.simulate.ai", args: [{ "op": "enable", "mode": "slow" }] } # progress-indicator UX
execute_command { command: "gitlens.plus.simulate.ai", args: [{ "op": "enable", "mode": "invalid" }] } # composer 4-attempt validation-failure UX
Mode invalid only triggers retry behavior for generate-commits (the Composer) — other actions tolerate malformed content and just render it.
Composer authoring (reflection workflow)
The Composer's generate-commits validator demands every input hunk index appear exactly once in the response. The agent can't author a valid response without first seeing the hunks the prompt sent:
- Trigger Composer with no inject queued (default response will fail validation predictably)
- Read the messages the simulator received:
execute_command { command: "gitlens.plus.simulate.ai", args: [{ "op": "lastMessages" }] }
Returns AIChatMessage[]. Parse the user message's hunks JSON to learn the hunk index space.
- Author a valid
{"commits":[{"message":"...","explanation":"...","hunks":[{"hunk":0},{"hunk":1},...]}, ...]} response covering every hunk index exactly once
- Inject for action
generate-commits. The Composer's automatic 4-attempt retry loop will consume the inject on the next attempt.
Cleanup
Mandatory between scenarios — leftover injects from one test will leak into the next:
execute_command { command: "gitlens.plus.simulate.ai", args: [{ "op": "clear" }] }
Mandatory teardown (restores the prior gitlens.ai.model, gitlens.ai.enabled, confirm:ai:tos, and AI All-Access flags):
execute_command { command: "gitlens.plus.simulate.ai", args: [{ "op": "disable" }] }
{op: "enable"} always clears the stash first, so re-enabling between tests is also safe.
Build requirement (both simulators)
Both simulators are DEBUG-only (gitlens:debugging context). Standard dev launch via launch {} and pnpm run build:extension includes them. Production bundles strip them.
Related skills
/live-exercise — agent-driven iterative working rhythm for UI-bearing work (audit + fix loop), which uses this skill's tools as its primitive.
/live-perf — agent-driven performance measurement + improvement skill, also built on these primitives.
/live-pair — user-driven interactive pair-programming session; the user gives feedback, the agent edits/rebuilds/refreshes live.
Use /live-exercise, /live-perf, or /live-pair when touching UI; use this skill on its own for one-off inspection.