Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ the schema's nested

| Schema property (path) | Type in schema | Composition (`x-composition.strategy`) |
|------------------------|----------------|----------------------------------------|
| `permissions.disableBypassPermissionsMode` | string enum `"disable"` | most-restrictive-wins (sticky once set) |
| `permissions.disableBypassPermissionsMode` | string enum `"disable"` \| `"allow-auto-only"` | most-restrictive-wins (sticky once set) |
| `model` | string (`auto`, a model family name, or a full model id) | — |
| `permissions.model` | string (legacy location for `model`) | — |
| `forceRemoteSettingsRefresh` | boolean | MDM wins; controls the server cache rather than a configuration setting |
Expand Down
117 changes: 105 additions & 12 deletions .github/skills/update-screenshots/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,120 @@
---
name: update-screenshots
description: Download screenshot baselines from the latest CI run and commit them. Use when asked to update, accept, or refresh component screenshot baselines from CI, or after the screenshot-test GitHub Action reports differences. This skill should be run as a subagent.
description: Update the committed blocks-ci screenshot hashes after the "Screenshots & Tests" check fails, or investigate a screenshot diff reported on a PR. Use when asked to update, accept, or refresh component screenshot baselines from CI. This skill should be run as a subagent.
---

# Update Component Screenshots from CI

Screenshot baselines are **no longer stored in the repository**. They are managed by an external screenshot service (`hediet-screenshots.azurewebsites.net`). The CI workflow uploads screenshots to this service and diffs them automatically.
Screenshot **images** are not stored in the repository — they live in an external service
(`hediet-screenshots.azurewebsites.net`), keyed by commit SHA. But a subset of fixtures is
pinned by **hash** in [`test/componentFixtures/blocks-ci-screenshots.md`](../../../test/componentFixtures/blocks-ci-screenshots.md),
and that file **is** committed. When those hashes change, CI fails and you must update the file.

When the `Checking Component Screenshots` GitHub Action detects changes, it posts a PR comment with before/after comparisons. No manual baseline updates are needed — the screenshots on the `main` branch commit become the new baselines automatically after merge.
## Two different outcomes, only one of which blocks

## What Changed
The `Screenshots & Tests` job in [`.github/workflows/component-fixtures.yml`](../../workflows/component-fixtures.yml)
produces two independent results:

- Baseline images were removed from `test/componentFixtures/.screenshots/baseline/`.
- Git LFS is no longer used for screenshot storage.
- The screenshot service stores images keyed by commit SHA and handles diffing.
| Result | Blocking? | Action |
| --- | --- | --- |
| Screenshot **diff report** (PR comment with before/after images) | No — informational | Review the visuals. Nothing to commit. |
| **blocks-ci hash mismatch** | **Yes — fails the check** | Update `blocks-ci-screenshots.md` and commit. |

## If Screenshots Need Investigation
A fixture opts into the blocking gate with `labels: { kind: 'screenshot', blocksCi: true }`.
Only those fixtures appear in `blocks-ci-screenshots.md`.

1. Check the PR comment posted by the CI workflow for visual diffs.
2. Download the `screenshots` artifact from the CI run for the raw captured images:
The failure looks like this:

```
##[error]blocks-ci screenshot hashes do not match committed file. See PR comment or job summary for the updated content.
```

## Step 1: Get the expected hashes from CI

> **Never regenerate the hashes locally.** They are hashes of the rendered PNG bytes, produced
> on `ubuntu-latest`. Rendering on macOS or Windows yields different bytes and therefore
> different hashes, so locally generated values will fail CI. Always copy the values from the
> CI job.

Three surfaces carry the same content — use whichever is handy:

- The **PR comment** titled "blocks-ci screenshots changed" (non-fork PRs only) — contains the
full updated file plus a patch.
- The **job summary**, which gets the identical body and is the only surface fork PRs receive.
- The **job log**, whose final step prints a unified diff:

```bash
gh run download <run-id> --name screenshots --dir .tmp/screenshots
gh api repos/microsoft/vscode/actions/jobs/<JOB_ID>/logs > "$TMPDIR/ci-job-log.txt"
grep -n '##\[error\]' "$TMPDIR/ci-job-log.txt"
```

3. Compare locally if needed. The artifact contains the full set of captured screenshots.
Find the failed job id with:

```bash
gh pr checks <PR> --json name,link,bucket --jq '.[] | select(.name == "Screenshots & Tests")'
```

## Step 2: Verify the change is intentional before accepting it

This gate exists to catch **unintended** layout regressions, so accepting new hashes without
looking at the images defeats its purpose. The images are publicly fetchable by hash, so pull
both the old (committed) and new (from CI) versions and compare:

```bash
curl -sL -o old.png "https://hediet-screenshots.azurewebsites.net/images/<OLD_HASH>"
curl -sL -o new.png "https://hediet-screenshots.azurewebsites.net/images/<NEW_HASH>"
```

Then view them, and localize the change rather than eyeballing full screenshots — the delta is
often only a pixel or two:

```bash
python3 -c "
from PIL import Image, ImageChops
a = Image.open('old.png').convert('RGB'); b = Image.open('new.png').convert('RGB')
print('diff bbox:', ImageChops.difference(a, b).getbbox())
"
```

Confirm the delta matches what the PR intends. If the fixture is unrelated to the change, or
the shift is larger than expected, treat it as a regression and fix the code instead of the
hashes.

## Step 3: Apply and commit

Edit only the changed lines in `test/componentFixtures/blocks-ci-screenshots.md`, replacing the
old hash in the image URL with the new one:

```md
#### editor/inlineChatZoneWidget/InlineChatZoneWidget/Dark
![screenshot](https://hediet-screenshots.azurewebsites.net/images/<NEW_HASH>)
```

The file is generated by [`build/lib/screenshotBlocksCi.ts`](../../../build/lib/screenshotBlocksCi.ts)
and compared **byte-for-byte**, so keep the `<!-- auto-generated by CI — do not edit manually -->`
header, the `#### <fixtureId>` / image-link pairing, the blank line between entries, and the
`fixtureId` sort order intact. Verify your edit is the exact inverse of the diff CI reported:

```bash
git diff test/componentFixtures/blocks-ci-screenshots.md
```

Then commit and push. The check re-runs and should pass; hashes on `main` become the new
baseline after merge.

## Investigating further

Raw captured images and the manifest for a run are uploaded as an artifact:

```bash
gh run download <RUN_ID> --name screenshots --dir .tmp/screenshots
```

`manifest.json` maps each `fixtureId` to its `imageHash` and any render errors.

## Related failures from the same job

The check also fails if a fixture **failed to render** (`Fail if fixtures had errors`) or if the
Playwright fixture tests failed. Those are genuine bugs — updating hashes will not help. Look
for `::error::<fixtureId>:` in the log, and download the `playwright-test-results` artifact for
test failures.
3 changes: 3 additions & 0 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ jobs:
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Prepare Electron types
run: node build/npm/electronTypes.ts

- name: Type check /build/ scripts
run: npm run typecheck
working-directory: build
Expand Down
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
.cache
npm-debug.log
Thumbs.db
node_modules/
# No trailing slash, so a symlinked node_modules is ignored too
node_modules
.tmp/
.build/
/extensionsCG/
Expand Down Expand Up @@ -32,7 +33,6 @@ product.overrides.json
vscode-telemetry-docs/
test-output.json
test/componentFixtures/.screenshots/*
!test/componentFixtures/.screenshots/baseline/
dist
.playwright-cli
.playwright-mcp
Expand Down
3 changes: 3 additions & 0 deletions build/azure-pipelines/alpine/product-build-alpine.yml
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,9 @@ jobs:

- template: ../common/install-builtin-extensions.yml@self

- script: node build/npm/electronTypes.ts
displayName: Prepare Electron types

- template: ../common/agent-sdk-produce.yml@self
parameters:
vscodePlatform: alpine
Expand Down
1 change: 0 additions & 1 deletion build/azure-pipelines/common/computeNodeModulesCacheKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ shasum.update(fs.readFileSync(path.join(ROOT, 'build/.cachesalt')));
shasum.update(fs.readFileSync(path.join(ROOT, '.npmrc')));
shasum.update(fs.readFileSync(path.join(ROOT, 'build', '.npmrc')));
shasum.update(fs.readFileSync(path.join(ROOT, 'remote', '.npmrc')));
shasum.update(fs.readFileSync(path.join(import.meta.dirname, 'listNodeModules.ts')));

// Add `package.json` and `package-lock.json` files
for (const dir of dirs) {
Expand Down
3 changes: 0 additions & 3 deletions build/azure-pipelines/common/listNodeModules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

import fs from 'fs';
import path from 'path';
import { ensureElectronTypes } from '../../npm/electronTypes.ts';

if (process.argv.length !== 3) {
console.error('Usage: node listNodeModules.ts OUTPUT_FILE');
Expand Down Expand Up @@ -41,7 +40,5 @@ function findNodeModulesFiles(location: string, inNodeModules: boolean, result:
}

const result: string[] = [];
await ensureElectronTypes();
findNodeModulesFiles('', false, result);
result.push('.build/typings/electron.d.ts');
fs.writeFileSync(process.argv[2], result.join('\n') + '\n');
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,9 @@ steps:

- template: ../../common/install-builtin-extensions.yml@self

- script: node build/npm/electronTypes.ts
displayName: Prepare Electron types

- template: ../../common/agent-sdk-produce.yml@self
parameters:
vscodePlatform: darwin
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,9 @@ steps:

- template: ../../common/install-builtin-extensions.yml@self

- script: node build/npm/electronTypes.ts
displayName: Prepare Electron types

- ${{ if ne(parameters.VSCODE_ARCH, 'armhf') }}:
- template: ../../common/agent-sdk-produce.yml@self
parameters:
Expand Down
3 changes: 3 additions & 0 deletions build/azure-pipelines/product-quality-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@ jobs:
- script: node build/azure-pipelines/distro/mixin-quality.ts
displayName: Mixin distro quality

- script: node build/npm/electronTypes.ts
displayName: Prepare Electron types

- script: node build/azure-pipelines/common/checkDistroCommit.ts
displayName: Check distro commit
env:
Expand Down
3 changes: 3 additions & 0 deletions build/azure-pipelines/web/product-build-web.yml
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,9 @@ jobs:

- template: ../common/install-builtin-extensions.yml@self

- script: node build/npm/electronTypes.ts
displayName: Prepare Electron types

- script: npx deemon --detach --wait -- node build/azure-pipelines/common/downloadCopilotVsix.ts
env:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
Expand Down
3 changes: 3 additions & 0 deletions build/azure-pipelines/win32/sdl-scan-win32.yml
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ steps:

- template: ../common/install-builtin-extensions.yml@self

- script: node build/npm/electronTypes.ts
displayName: Prepare Electron types

- template: ../common/mixin-vscode-capi.yml@self

- powershell: npm run gulp core-ci
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@ steps:

- template: ../../common/install-builtin-extensions.yml@self

- powershell: node build/npm/electronTypes.ts
displayName: Prepare Electron types

- template: ../../common/agent-sdk-produce.yml@self
parameters:
vscodePlatform: win32
Expand Down
4 changes: 4 additions & 0 deletions build/lib/i18n.resources.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@
"name": "vs/workbench/contrib/folding",
"project": "vscode-workbench"
},
{
"name": "vs/workbench/contrib/github",
"project": "vscode-workbench"
},
{
"name": "vs/workbench/contrib/html",
"project": "vscode-workbench"
Expand Down
15 changes: 15 additions & 0 deletions cli/src/commands/serve_web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ async fn handle(
};

append_secret_headers(&ctx.cm.base_path, &mut res, &client_key_half);
append_frame_ancestors(&mut res);

Ok(res)
}
Expand Down Expand Up @@ -276,6 +277,20 @@ fn append_secret_headers(
);
}

/// Prevents other origins from embedding serve-web pages. Same-origin iframes
/// used by the workbench itself are still allowed.
fn append_frame_ancestors(res: &mut Response<HyperBody>) {
let headers = res.headers_mut();
headers.append(
::http::header::CONTENT_SECURITY_POLICY,
"frame-ancestors 'self'".parse().unwrap(),
);
headers.insert(
::http::header::HeaderName::from_static("x-frame-options"),
"SAMEORIGIN".parse().unwrap(),
);
}

/// Gets the release info from the VS Code path prefix, which is in the
/// format `/<quality>-<commit>/...`
fn get_release_from_path(path: &str, platform: Platform) -> Option<(Release, String)> {
Expand Down
Loading
Loading