Skip to content

Instantly share code, notes, and snippets.

@hediet
Created September 27, 2026 11:34
Show Gist options
  • Select an option

  • Save hediet/a3c33b5506f287e75dd4e567b6179e2f to your computer and use it in GitHub Desktop.

Select an option

Save hediet/a3c33b5506f287e75dd4e567b6179e2f to your computer and use it in GitHub Desktop.
Standalone visible-Chrome dbgjs repro for microsoft/vscode#338189 (synthetic data, no sign-in)
#requires -Version 7.0
<#
Reproduces microsoft/vscode#338189 in a new, visible Chrome/Edge window.
Requires global dbgjs on PATH, Chrome or Edge, and internet access.
Run: pwsh -File .\repro-vscode-338189.ps1
No VS Code checkout, sign-in, agent host, or actual command execution is needed.
Uses an isolated browser profile and synthetic in-memory session data.
Leaves the browser open with the bug visible; close it manually when finished.
Reload removes the fixture. The output folder contains the disposable profile.
The debugger capture is pinned to the verified VS Code Insiders build below.
The script fails explicitly if the deployed build changes.
#>
param(
[string]$BrowserPath,
[string]$OutputDirectory
)
$ErrorActionPreference = 'Stop'
Get-Command dbgjs -ErrorAction Stop | Out-Null
if (!$BrowserPath) {
$BrowserPath = @(
"$env:ProgramFiles\Google\Chrome\Application\chrome.exe"
"${env:ProgramFiles(x86)}\Google\Chrome\Application\chrome.exe"
"$env:LOCALAPPDATA\Google\Chrome\Application\chrome.exe"
"${env:ProgramFiles(x86)}\Microsoft\Edge\Application\msedge.exe"
"$env:ProgramFiles\Microsoft\Edge\Application\msedge.exe"
) | Where-Object { Test-Path -LiteralPath $_ -PathType Leaf } | Select-Object -First 1
}
if (!$BrowserPath -or !(Test-Path -LiteralPath $BrowserPath -PathType Leaf)) {
throw 'Chrome/Edge was not found. Pass -BrowserPath with its executable path.'
}
$contextName = 'vscode-338189-' + [guid]::NewGuid().ToString('N').Substring(0, 10)
$context = ":$contextName"
if (!$OutputDirectory) { $OutputDirectory = Join-Path $env:TEMP $contextName }
$OutputDirectory = [IO.Path]::GetFullPath($OutputDirectory)
if (Test-Path -LiteralPath $OutputDirectory) {
throw "Use a new output directory to avoid reusing browser state: $OutputDirectory"
}
New-Item -ItemType Directory -Path $OutputDirectory | Out-Null
function Invoke-Dbgjs {
param([string[]]$Arguments, [string]$Program)
if ($Program) { $Program | & dbgjs @Arguments }
else { & dbgjs @Arguments }
if ($LASTEXITCODE -ne 0) {
throw "dbgjs failed (exit $LASTEXITCODE): $($Arguments -join ' ')"
}
}
Write-Host "Launching isolated browser. Output: $OutputDirectory"
Invoke-Dbgjs -Arguments @('context', 'create', $context, 'VS Code approval-row repro') | Out-Host
Invoke-Dbgjs -Arguments @(
'connection', 'add', '--chrome', 'https://vscode.dev/agents?vscode-quality=insider',
'--connection', 'chrome', '--context', $context, '--executable', $BrowserPath,
'--user-data-dir', (Join-Path $OutputDirectory 'chrome-profile'),
'--headed', '--connect', '--set'
) | Out-Host
$scope = @('--context', $context, '--connection', 'chrome')
$targetText = Invoke-Dbgjs -Arguments (@('--json', 'target', 'list', '--type', 'page', '--url', 'https://vscode.dev/agents') + $scope)
$targets = @(($targetText -join "`n" | ConvertFrom-Json).targets)
if ($targets.Count -ne 1) { throw "Expected one Agents page, found $($targets.Count)." }
$scope += @('--target', $targets[0].targetId)
Invoke-Dbgjs -Arguments (@('target', 'attach', '--set') + $scope) | Out-Host
$prepare = @'
await page.waitForSelector('.sessions-list-control', { timeout: 60000 });
return await page.evaluate(() => {
const version = document.querySelector('meta[name="vscode:versionInfo"]')?.content;
if (!version?.endsWith(';4a6a3612a562c50f54c2bd5211f0ea42915b974e')) {
throw new Error('Capture is pinned to 4a6a3612a562c50f54c2bd5211f0ea42915b974e; deployed build is ' + version);
}
return { version };
});
'@
Invoke-Dbgjs -Arguments (@('playwright', '-') + $scope) -Program $prepare | Out-Host
# Capture real objects in SessionsList.layout without pausing the application.
$url = 'https://main.vscode-cdn.net/insider/4a6a3612a562c50f54c2bd5211f0ea42915b974e/out/vs/sessions/sessions.web.main.internal.js'
$parameters = @{
lineNumber = 4238
columnNumber = 58772
url = $url
condition = '(globalThis.__webList=this,globalThis.__reproObservable=ge,globalThis.__reproURI=M,false)'
} | ConvertTo-Json -Compress
$breakpointText = Invoke-Dbgjs -Arguments (@('target', 'cdp', 'Debugger.setBreakpointByUrl', '--params', $parameters) + $scope)
$breakpoint = ($breakpointText -join "`n") | ConvertFrom-Json
try {
$capture = @'
const height = await page.evaluate(() => innerHeight);
await page.setViewportSize({ width: 1250, height: height === 700 ? 701 : 700 });
await page.waitForFunction(() => globalThis.__webList && globalThis.__reproObservable && globalThis.__reproURI, null, { timeout: 10000 });
return { captured: true };
'@
Invoke-Dbgjs -Arguments (@('playwright', '-') + $scope) -Program $capture | Out-Host
} finally {
$remove = @{ breakpointId = $breakpoint.breakpointId } | ConvertTo-Json -Compress
Invoke-Dbgjs -Arguments (@('target', 'cdp', 'Debugger.removeBreakpoint', '--params', $remove) + $scope) | Out-Host
}
Write-Host 'Injecting fake sessions; testing original CSS, hotfix, then original CSS again.'
$fixture = @'
return await page.evaluate(async () => {
const list = __webList;
const obs = value => __reproObservable('height-repro', value);
const URI = __reproURI;
if (list.sessions.length) throw new Error('Expected an isolated tab with no real sessions.');
const sessions = Array.from({ length: 40 }, (_, index) => {
const id = `height-repro-${String(index).padStart(2, '0')}`;
const resource = URI.parse(`test-session://${id}`);
const now = new Date(Date.now() - index * 1000);
const status = obs(index === 0 ? 2 : 3);
const title = obs(index === 0 ? 'REPRO: three-line approval' : `Filler session ${index}`);
const main = {
resource: resource.with({ fragment: 'main' }), title, status,
createdAt: now, updatedAt: obs(now), workspace: obs(undefined),
changes: obs([]), changesets: obs([]), isArchived: obs(false),
isRead: obs(true), description: obs(undefined), lastTurnEnd: obs(undefined),
origin: { kind: 'user' }, interactivity: obs('interactive'),
};
return {
sessionId: id, resource, providerId: 'height-repro', sessionType: 'height-repro',
icon: { id: 'beaker' }, createdAt: now,
workspace: obs({
uri: URI.parse('file:///height-repro'), label: 'Synthetic repro only',
icon: { id: 'folder' }, folders: [], requiresWorkspaceTrust: false,
isVirtualWorkspace: false,
}),
isQuickChat: obs(false), title, updatedAt: obs(now), status,
changesets: obs([]), changes: obs([]), changesSummary: obs(undefined),
modelId: obs(undefined), mode: obs(undefined), loading: obs(false),
isArchived: obs(false), isRead: obs(true), description: obs(undefined),
lastTurnEnd: obs(undefined), chats: obs([main]), mainChat: obs(main),
capabilities: obs({ supportsMultipleChats: false, supportsRename: false }),
};
});
const target = sessions[0], main = target.mainChat.get();
target.capabilities.set({ supportsMultipleChats: true, supportsRename: false }, undefined);
target.chats.set([main, ...Array.from({ length: 30 }, (_, index) => ({
...main, resource: target.resource.with({ fragment: `child-${index}` }),
title: obs(`Nested chat ${index}`), status: obs(3),
}))], undefined);
list._approvalModel.getApproval(main.resource).set({
approvalId: 'fake-approval', kind: 'terminal',
label: 'echo one\necho two\necho three', languageId: 'shellscript',
since: new Date(), confirm() { console.info('Fake approval; nothing executes.'); },
}, undefined);
// Keep host discovery from replacing the fixture during background refreshes.
list._sessionsManagementService.getSessions = () => sessions;
list.sessions = sessions;
list.options.compact = () => true;
list.workspaceGroupCapped = false;
list.sessionGroupLimit.set(100, undefined);
list.setCompact();
list.tree.expand(target);
// Hide only the sign-in overlay for the signed-out synthetic demonstration.
// This does not authenticate or enable any backend functionality.
const demoStyle = document.createElement('style');
demoStyle.textContent = '.monaco-dialog-modal-block:has(.sessions-welcome-dialog) { display: none !important; }';
document.head.appendChild(demoStyle);
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
const findRow = () => [...list.listContainer.querySelectorAll('.session-title')]
.find(element => element.textContent === 'REPRO: three-line approval')?.closest('.monaco-list-row');
const expectedHeight = list._delegate.getHeight(target);
async function run(fixed) {
document.getElementById('web-approval-height-fix')?.remove();
if (fixed) {
const style = document.createElement('style');
style.id = 'web-approval-height-fix';
style.textContent = '.session-approval-row { line-height: 18px; }';
document.head.appendChild(style);
}
list.tree.scrollTop = 0;
list.tree.updateElementHeight(target, expectedHeight);
await delay(100);
const measurements = [], cycles = [];
const prototype = HTMLElement.prototype;
const descriptor = Object.getOwnPropertyDescriptor(prototype, 'offsetHeight');
Object.defineProperty(prototype, 'offsetHeight', {
...descriptor,
get() {
const height = descriptor.get.call(this);
if (this.matches('.monaco-list-row')
&& this.querySelector('.session-title')?.textContent === 'REPRO: three-line approval'
&& measurements.length < 30) {
measurements.push(height);
}
return height;
},
});
try {
for (let cycle = 0; cycle < 3; cycle++) {
for (let position = 0; position < 1500; position += 75) {
list.tree.scrollTop = position;
await delay(10);
}
list.tree.scrollTop = 0;
await delay(100);
const row = findRow();
if (!row) throw new Error('Target row disappeared.');
cycles.push(row.getBoundingClientRect().height);
}
} finally {
Object.defineProperty(prototype, 'offsetHeight', descriptor);
}
return { measurements, cycles };
}
const original = await run(false);
const fixed = await run(true);
const restored = await run(false);
if (!original.cycles.every(height => height > expectedHeight * 3)
|| !restored.cycles.every(height => height > expectedHeight * 3)) {
throw new Error('Bug did not reproduce: ' + JSON.stringify({ original, restored }));
}
if (!fixed.cycles.every(height => Math.abs(height - expectedHeight) <= 1)) {
throw new Error('Hotfix did not stabilize height: ' + JSON.stringify(fixed));
}
list.tree.setSelection([target]);
// Show the displaced title in the final screenshot while retaining the bad height.
list.tree.scrollTop = 600;
globalThis.agentsRowRepro = { run, sessions, results: { original, fixed, restored } };
return { expectedHeight, original, fixed, restored };
});
'@
$resultText = Invoke-Dbgjs -Arguments (@('playwright', '-') + $scope) -Program $fixture
$result = ($resultText -join "`n") | ConvertFrom-Json
$result | ConvertTo-Json -Depth 10 | Set-Content -LiteralPath (Join-Path $OutputDirectory 'results.json')
foreach ($name in 'original', 'fixed', 'restored') {
Write-Host "$name measurements: $($result.$name.measurements -join ', ') px; final heights: $($result.$name.cycles -join ', ') px"
}
Invoke-Dbgjs -Arguments (@('playwright', '-') + $scope) -Program 'await page.mouse.move(1000, 100); return { ready: true };' | Out-Host
Invoke-Dbgjs -Arguments (@('screenshot', 'capture', '--output', (Join-Path $OutputDirectory 'bug.png')) + $scope) | Out-Host
Write-Host "PASS. Screenshot and results: $OutputDirectory"
Write-Host "The browser remains open. Close it when done. Debug context: $context"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment