Releases run only by hand: Actions → Release → Run workflow on main. .github/workflows/release.yml then:
- build — reads
versionfrompackages/client/package.jsonand stops ifv<version>is already tagged. Runs lint, typecheck, unit tests, the production build and the VS Code 1.85 e2e suite, then packages the VSIX. - draft — creates a draft GitHub release
v<version>targeting the commit being released and attaches the VSIX. A draft doesn't create its tag yet. - publish — runs in the protected
releaseenvironment. Signs in to Azure as a managed identity (no stored Marketplace token) and publishes to the Visual Studio Marketplace, then to Open VSX. Both use--skip-duplicate, so re-running a half-finished release is safe. - release — publishes the draft, which creates the
v<version>tag.
Sign in at https://marketplace.visualstudio.com/manage with your Microsoft account and create a publisher (<publisher> below). It must match publisher in the manifest, and the ID can't be changed later.
Only global Azure DevOps Personal Access Tokens can publish to the Marketplace, and Azure DevOps retires them on December 1, 2026. CI signs in as an Azure managed identity instead.
-
Create a free Azure account at https://azure.microsoft.com/free if you don't have a subscription. The identity is an Azure resource and needs one; it costs nothing.
-
In the Azure portal at <portal.azure.com>, open Managed Identities → Create. Pick the subscription, a resource group (e.g.
css-modules-devtools), any region and a name (e.g.css-modules-devtools-publisher). Use a managed identity, as Microsoft's publishing guide does; there are reports of App Registrations signing in but being refused at publish withoperation not allowed. -
Open the identity itself, not its resource group (Resource group → Overview → the Managed Identity resource), then Settings → Federated credentials → Add credential:
- Scenario: GitHub Actions deploying Azure resources
- Organization:
<owner>(the repo owner's GitHub login, also for personal accounts) - Organization ID and Repository ID: the numeric IDs, from
gh api users/<owner> --jq .idandgh api repos/<owner>/<repo> --jq .id - Repository:
<repo> - Entity type: Environment, Environment name:
release - Name: e.g.
github-release
The subject identifier should read
repo:<owner>@<owner-id>/<repo>@<repo-id>:environment:release. Repositories created or renamed after July 15, 2026 send this ID-based subject; check yours withgh api repos/<owner>/<repo>/actions/oidc/customization/sub(use_immutable_subject: true). This trusts any run of a job in thereleaseenvironment, whatever triggered it. -
On the identity's Overview, copy the Client ID and the Tenant ID (the tenant is under Properties if it isn't shown).
- Sign in at https://open-vsx.org with GitHub and link an Eclipse account under Settings.
- Sign the Eclipse Foundation Open VSX Publisher Agreement from the same page.
- Create an access token under Settings → Access Tokens.
- Create the namespace once:
bunx ovsx create-namespace <publisher> -p <token>.
-
Settings → Environments → New environment named
release. Add yourself as a required reviewer. Under Deployment branches and tags, choose Selected branches and tags and addmain. -
In that environment, add the secrets
AZURE_CLIENT_IDandAZURE_TENANT_ID(from step 2.4) andOVSX_PAT(from step 3.3). Or from a terminal:gh secret set AZURE_CLIENT_ID --env release gh secret set AZURE_TENANT_ID --env release gh secret set OVSX_PAT --env release
The Marketplace knows the identity by its Azure DevOps profile ID, not its Client ID. Only the identity itself can read that ID, so run a throwaway workflow once:
-
Commit this as
.github/workflows/whoami.ymland push it tomain:name: Marketplace identity on: workflow_dispatch permissions: id-token: write jobs: whoami: runs-on: ubuntu-latest environment: release steps: - uses: azure/login@053f7285eb20bf5469ef084617b0b24d3cec53fc # v3.1.0 with: client-id: ${{ secrets.AZURE_CLIENT_ID }} tenant-id: ${{ secrets.AZURE_TENANT_ID }} allow-no-subscriptions: true - run: az rest -u https://app.vssps.visualstudio.com/_apis/profile/profiles/me --resource 499b84ac-1321-427f-aa17-267ca6975798 --query id -o tsv
-
Run it from Actions → Marketplace identity → Run workflow, approve the
releaseenvironment, and copy the ID the last step prints. -
At https://marketplace.visualstudio.com/manage, open your publisher → Members → Add, paste the ID and give it the Contributor role. If the search finds nothing, you pasted the Client ID instead.
-
Delete
whoami.yml.
-
Bump
versioninpackages/client/package.json. The rootpackage.jsonversion is not used. -
Add a
## X.Y.Zsection at the top ofCHANGELOG.md. The build copies it into the VSIX, where the Marketplace shows it on the Changelog tab. -
Run the checks locally:
bun run check && bun run typecheck && bun test packages && bun run test:e2e && bun run package
-
Commit and push to
main. README images on the listing load frommain, so push before releasing. -
Actions → Release → Run workflow on
main, then approve thepublishjob when it waits on thereleaseenvironment.
If a job fails, use Re-run failed jobs. A new run for the same version reuses the draft and points it at the new commit.
- Marketplace: nothing expires. If publishing fails with
operation not allowed, check the identity is still a Contributor on the publisher. Ifazure/loginfails, check the federated credential still names thereleaseenvironment. - Open VSX: rotate
OVSX_PATif Open VSX rejects it.