How the extension is published to the VS Code Marketplace and Open VSX Registry, how to cut a release, and how to recover or recreate the pipeline if anything in the identity setup breaks.
- Bump
versionin package.json (e.g.0.26.0→0.26.1). This is whatvsceactually publishes — it reads the version from package.json, not from the git tag. - Commit and merge to
master. - Create a new GitHub Release at https://github.com/probe-rs/vscode/releases/new:
- Tag:
vX.Y.Zmatching the package.json version, prefixed withv(e.g.v0.26.1). Create the tag fresh from this page — don't push a tag separately. - Target:
master. The tag will be created onmasterHEAD. - Release notes: written or auto-generated.
- Click Publish release (not "Save draft" — drafts do not trigger the workflow).
- Tag:
- The publish workflow (.github/workflows/publish.yml) starts and pauses for approval in the
marketplaceenvironment. Approve in the Actions tab. - Two parallel jobs run:
publish-vsce(VS Code Marketplace) andpublish-ovsx(Open VSX). Each succeeds or fails independently — a transient failure in one does not block the other. - Verify both registries show the new version.
| Source of truth | What it determines |
|---|---|
version in package.json |
The version vsce and ovsx publish to the marketplaces. |
Git tag vX.Y.Z on the release |
The commit the workflow checks out and runs against. Also the rule the marketplace environment matches against to authorize the run. |
The two must match — there's no automation enforcing this today, so it's a manual discipline. The release tag is what the workflow runs from; the package.json version is what gets published. Mismatched values produce confusing failures (e.g. workflow runs from v0.26.1 but publishes 0.26.0).
The workflow fires only on release: published. It does not run on push, PR, or schedule. When that event fires, the workflow's ref is the release's tag (e.g. refs/tags/v0.26.1), not a branch — this matters for the environment policy below.
Both jobs bind to the GitHub marketplace environment, which enforces:
- Required reviewer — a repo admin must manually approve each run.
- Tag policy — runs are only permitted from refs matching
v*, which every release tag should match.
A publish never happens silently and never from an unauthorized ref.
We use Microsoft Entra federated credentials instead of a long-lived Personal Access Token. The chain:
- Microsoft Entra tenant
probers.onmicrosoft.comhosts an app registrationgithub-actions-vscode-publish. - The app has a federated credential trusting GitHub Actions OIDC tokens whose subject equals
repo:probe-rs/vscode:environment:marketplace. - The corresponding service principal is a Contributor member of the marketplace publisher
probe-rs. - During the publish job,
azure/login@v3exchanges the GitHub OIDC token for an Azure access token.vsce publish --azure-credentialthen uses that token to call the marketplace API.
The workflow references only two non-secret identifiers — AZURE_CLIENT_ID and AZURE_TENANT_ID — stored as environment variables (not secrets) on the marketplace environment. No marketplace credential is at rest in this repository.
Open VSX has no OIDC equivalent. We use a token issued to the publisher account on https://open-vsx.org, stored as the environment secret OPEN_VSX_MARKETPLACE_TOKEN. Rotate it every ~12 months (see Maintenance).
Day-to-day publishing should always go through the workflow. If you need to publish from a local machine in an emergency, use npm run publish after vsce login probe-rs with a personal PAT — the --azure-credential flag in CI is workflow-specific.
Use this if the Entra tenant dies, ownership changes, or you fork into a new org.
- Owner on the marketplace publisher (https://marketplace.visualstudio.com/manage/publishers/probe-rs).
- Admin on the GitHub repo.
- A Microsoft Entra tenant where you can create app registrations. If you don't have one, sign up for any Microsoft 365 Business plan trial — the cheapest works, cancel before the trial ends. The Entra tenant survives forever; only the paid Office bits go away. Tenant creation through the Entra portal alone is gated for individuals — the Business signup flow is currently the only reliable path that doesn't require a paid Visual Studio subscription.
In https://entra.microsoft.com (signed in to the new tenant):
- Identity → Applications → App registrations → + New registration.
- Name:
github-actions-vscode-publish - Supported account types: single tenant
- Redirect URI: leave blank
- Name:
- From the resulting Overview page, save the Application (client) ID and Directory (tenant) ID GUIDs.
On the new app: Manage → Certificates & secrets → Federated credentials → + Add credential.
- Scenario: GitHub Actions deploying Azure resources
- Organization:
probe-rs, Repository:vscode - Entity type: Environment
- GitHub environment name:
marketplace(must match exactly — case-sensitive). This value drives the Subject identifier; do not put a credential nickname here. - Name (further down the form, required, just a human label): something like
github-actions-vscode-publish-marketplace. This field is cosmetic and does not affect matching. - Audience:
api://AzureADTokenExchange(default) - Before saving, verify the Subject identifier preview reads exactly
repo:probe-rs/vscode:environment:marketplace. If it reads anything else (e.g. ends in your credential nickname), the GitHub environment name field is wrong — fix it before saving.
The publisher Members page does not accept the SP's Application ID or Object ID — it requires the Team Foundation Identity ID from the Azure DevOps profile system. Bootstrap by calling the Profile API as the SP itself:
# In Entra → app → Certificates & secrets → Client secrets, create a temporary
# secret with the shortest expiry (7 days). Copy the value immediately.
az login --service-principal \
-u <APPLICATION_CLIENT_ID> \
-t <TENANT_ID> \
-p '<TEMP_SECRET_VALUE>' \
--allow-no-subscriptions
az rest -u https://app.vssps.visualstudio.com/_apis/profile/profiles/me \
--resource 499b84ac-1321-427f-aa17-267ca6975798Copy the id field from the JSON response. Then immediately delete the temp client secret in Entra and run az logout.
At https://marketplace.visualstudio.com/manage/publishers/probe-rs → Members → + Add: paste the id into the User Id field, role: Contributor (least privilege — Contributor can publish updates; Owner can also manage members and is unnecessary for automation).
In https://github.com/probe-rs/vscode/settings/environments:
- Create environment named exactly
marketplace. - Required reviewers: add at least one repo admin.
- Deployment branches and tags: select "Selected branches and tags" and add a tag rule for
v*. Do not add a branch rule likemaster— the workflow's ref will always be the release's tag, so a branch rule never matches and only adds confusion. - Environment variables (not secrets — these are non-sensitive identifiers):
AZURE_CLIENT_ID— Application (client) ID from step 1AZURE_TENANT_ID— Directory (tenant) ID from step 1
- Environment secret:
OPEN_VSX_MARKETPLACE_TOKEN— token issued at https://open-vsx.org → Settings → Access Tokens.
Cut a tagged pre-release on a throwaway version number, approve the workflow run, watch both jobs succeed, then unpublish the test version from both marketplaces.
- OVSX token: rotate every ~12 months. Generate new token, update env secret, revoke old token.
- Entra tenant heartbeat: 200-day inactivity timer. Each release resets it; an idle period of 6+ months warrants a manual sign-in to https://entra.microsoft.com.
- No PAT to rotate for VS Marketplace — this is one of the main reasons for the OIDC setup. The federated credential lives indefinitely; nothing to expire.