|
| 1 | +# Releasing the VS Code extension |
| 2 | + |
| 3 | +This package (`copilotkit-vscode-extension`) ships to two registries: |
| 4 | + |
| 5 | +- [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=copilotkit.copilotkit-vscode-extension) — primary, used by VS Code, Cursor, etc. |
| 6 | +- [Open VSX](https://open-vsx.org/extension/copilotkit/copilotkit-vscode-extension) — used by VSCodium, Gitpod, OpenVSCode Server, Theia-based IDEs. |
| 7 | + |
| 8 | +The flow mirrors the [`CopilotKit/aimock`](https://github.com/CopilotKit/aimock) release model for publish: a `chore: release vX.Y.Z` commit on `main` triggers CI, which self-gates on whether the version is already live and publishes if not. Unlike aimock, contributors don't hand-edit `package.json` — the **VS Code Extension — Metadata Sync** workflow (`.github/workflows/vscode-extension-changelog-sync.yml`) auto-syncs `package.json.version` (and optionally `package.json.description`) from the CHANGELOG entry (and optionally the README.md first paragraph) when the PR is opened, and commits `chore: release vX.Y.Z` on the PR branch for you. **No tag is created by hand. No `vsce publish` is run by hand.** |
| 9 | + |
| 10 | +## Cutting a release |
| 11 | + |
| 12 | +Cutting a release is one file edit, one commit, one PR. |
| 13 | + |
| 14 | +1. Prepend an entry to `packages/vscode-extension/CHANGELOG.md`: |
| 15 | + |
| 16 | + ```md |
| 17 | + ## 0.1.1 — 2026-04-22 |
| 18 | + |
| 19 | + ### Fixed |
| 20 | + |
| 21 | + - Hook Explorer crash on empty workspaces |
| 22 | + |
| 23 | + ### Added |
| 24 | + |
| 25 | + - New "Copy AG-UI run URL" command |
| 26 | + ``` |
| 27 | + |
| 28 | + Use Keep-a-Changelog subsections: `Added`, `Changed`, `Fixed`, `Removed`, `Deprecated`, `Security`. The date is whatever date you commit. |
| 29 | + |
| 30 | + If you also want to change how the extension is described on the Marketplace / Open VSX listing page, edit the first paragraph under the `# CopilotKit for VS Code` title in `packages/vscode-extension/README.md` in the same PR. The metadata-sync workflow extracts that paragraph, strips inline markdown, and writes it to `package.json.description` (the field both registries render as the one-liner). First paragraph must stay ≤200 chars — the workflow fails fast otherwise so you don't silently ship a truncated listing. |
| 31 | + |
| 32 | +2. Commit with any message (e.g. `docs: changelog for 0.1.1`) and push. |
| 33 | +3. Open a PR. |
| 34 | +4. **Automation**: the **VS Code Extension — Metadata Sync** workflow runs on your PR, reads the top version from CHANGELOG and the first paragraph of README.md, and bumps `packages/vscode-extension/package.json` (`.version` and/or `.description`) to match. If a version bump is needed it commits `chore: release vX.Y.Z`; otherwise, if only the description drifted it commits `chore(vscode-extension): sync description from README`. No action needed from you — just pull the updated branch if you keep working locally. |
| 35 | +5. Review, merge with a merge commit (NOT squash — the `chore: release` commit needs to land on `main` so the publish workflow's path filter fires). |
| 36 | + |
| 37 | +On merge, CI auto-publishes to the VS Code Marketplace and Open VSX, tags `vscode-extension-vX.Y.Z`, cuts a GitHub Release, and posts to `#oss-alerts`. |
| 38 | + |
| 39 | +### Manual escape hatch |
| 40 | + |
| 41 | +If the sync workflow is down or you can't wait, bump `package.json` yourself in the same PR and use `chore: release vX.Y.Z` as your own commit subject. Everything downstream works the same. |
| 42 | + |
| 43 | +## CI publish flow |
| 44 | + |
| 45 | +On every push to `main` under `packages/vscode-extension/**`, `.github/workflows/publish-vscode-extension.yml` runs: |
| 46 | + |
| 47 | +1. Reads `version` from `packages/vscode-extension/package.json`. |
| 48 | +2. Calls `vsce show <publisher>.<name>` and checks whether that exact version is already listed on the Marketplace. |
| 49 | +3. If already published → no-op (green, zero side effects). This makes every non-release push to main safe. |
| 50 | +4. Otherwise: install, build, `vsce package` → single `extension.vsix`, upload artifact. |
| 51 | +5. Marketplace auth is GitHub OIDC → Entra federated Service Principal → `vsce publish --azure-credential`. No user PAT. The SP is `copilotkit-vscode-publish` in the copilotkit.ai tenant with Contributor role on the Marketplace `copilotkit` publisher. `azure/login@v2` sources `AZURE_CLIENT_ID` / `AZURE_TENANT_ID` / `AZURE_SUBSCRIPTION_ID` from the `production` environment **variables** (not secrets) and populates the `AzureCliCredential` chain that `vsce` reads by default. A `verify-pat --azure-credential` pre-flight step catches auth issues before we publish. |
| 52 | +6. Publish the same `.vsix` to Open VSX with `$OVSX_PAT` (bot-owned PAT, quarterly rotation — OIDC is not yet supported on Open VSX). |
| 53 | +7. Create tag `vscode-extension-v<version>` and push it. |
| 54 | +8. Cut a GitHub Release using the CHANGELOG section for that version (or auto-generated notes if absent). |
| 55 | +9. Post a Slack message to `SLACK_WEBHOOK` if configured. |
| 56 | + |
| 57 | +The federated credential on the Entra SP is pinned to subject `repo:CopilotKit/CopilotKit:environment:production`, which means the workflow's `environment: production` must match **exactly** (case-sensitive) for the OIDC token exchange to succeed. |
| 58 | + |
| 59 | +Both publishes use `continue-on-error` so a partial failure is visible. The final "Report publish results" step fails the job if either registry ultimately failed — you never land in "Marketplace succeeded, Open VSX silently skipped." |
| 60 | + |
| 61 | +## Transient registry failures |
| 62 | + |
| 63 | +Both registries occasionally return transient `5xx` errors during publish. In particular, **Open VSX's `/publish` endpoint returns intermittent `502 Bad Gateway` errors** — a known Eclipse Foundation infra issue. VS Code Marketplace is more reliable but can also flake on `502`/`503`/`504`. |
| 64 | + |
| 65 | +The CI workflow handles this automatically: |
| 66 | + |
| 67 | +- Each publish step retries up to **5 times** with backoff (10s, 20s, 40s, 60s, 90s). |
| 68 | +- Only transient conditions are retried: `5xx`, timeouts, connection resets, DNS (`EAI_AGAIN`). Auth (`401`/`403`, `TF400813`, "Invalid access token") and validation (`400`/`422`, manifest errors) fail fast with no retry. |
| 69 | +- A `"version already exists"` response is treated as **idempotent success** — this handles the case where attempt N-1 actually landed on the registry but its response never reached us (e.g. `502` after commit), and attempt N sees the version already there. |
| 70 | +- Each attempt is wrapped in `::group::` / `::endgroup::` so the per-attempt logs are collapsible in the GH Actions UI. |
| 71 | + |
| 72 | +**If the workflow fails after all retries:** |
| 73 | + |
| 74 | +1. Rerun the job from the GH Actions UI (Actions → failed run → _Re-run failed jobs_). |
| 75 | +2. **Do NOT bump the version.** The same tag + version is safe to re-publish because both registries treat "version already exists" as idempotent in our retry logic — whichever one previously succeeded will short-circuit, and the one that failed will get a fresh attempt. |
| 76 | + |
| 77 | +**For manual publishes** (emergencies only — prefer CI): |
| 78 | + |
| 79 | +- If `npx ovsx publish ...` fails with a `502`, just retry the same command 2–3 times. |
| 80 | +- If `vsce publish` fails with a `5xx`, same — retry. Auth errors (`TF400813`) mean the PAT is bad; don't retry, fix the token. |
| 81 | + |
| 82 | +## Verifying |
| 83 | + |
| 84 | +After CI goes green: |
| 85 | + |
| 86 | +- Marketplace: <https://marketplace.visualstudio.com/items?itemName=copilotkit.copilotkit-vscode-extension> |
| 87 | +- Open VSX: <https://open-vsx.org/extension/copilotkit/copilotkit-vscode-extension> |
| 88 | +- Smoke test install: |
| 89 | + |
| 90 | + ```bash |
| 91 | + code --install-extension copilotkit.copilotkit-vscode-extension --force |
| 92 | + ``` |
| 93 | + |
| 94 | + (for VSCodium / Cursor, use their respective CLIs with the same extension ID.) |
| 95 | + |
| 96 | +## Rollback |
| 97 | + |
| 98 | +**Both registries treat published versions as immutable.** You cannot unpublish or replace a bad version — you can only publish a patch that supersedes it. |
| 99 | + |
| 100 | +If a bad version ships: |
| 101 | + |
| 102 | +1. Fix the regression on a branch, open a PR, land it on `main`. |
| 103 | +2. Prepend a new `## X.Y.Z — <date>` entry with a `### Fixed` subsection to `packages/vscode-extension/CHANGELOG.md` (patch-bumping the version), open a PR, and merge. The metadata-sync workflow auto-commits the corresponding `package.json` bump as `chore: release vX.Y.Z` on the PR branch — same flow as a normal release. |
| 104 | +3. If the regression is severe enough to warrant pulling the listing, the Marketplace / Open VSX admin UIs each offer a per-version unlist (different from unpublish) — use that as a last resort; prefer shipping forward. |
| 105 | + |
| 106 | +## Rollback guidance |
| 107 | + |
| 108 | +### OIDC auth failures |
| 109 | + |
| 110 | +If the `Login to Azure` or `Verify Marketplace credential` step fails on the first OIDC publish, check these in order: |
| 111 | + |
| 112 | +1. **SP not added as Contributor on the Marketplace publisher.** `copilotkit-vscode-publish` must be a member of the `copilotkit` publisher at <https://marketplace.visualstudio.com/manage/publishers/copilotkit> with the Contributor (or higher) role. |
| 113 | +2. **Federated credential subject mismatch.** The credential on the Entra app must have subject **exactly** `repo:CopilotKit/CopilotKit:environment:production` (case-sensitive, no trailing slash). The workflow's `environment: production` must match that subject byte-for-byte. |
| 114 | +3. **Tenant conditional access policy blocking workload identities.** The copilotkit.ai tenant may have CA policies that block service principals from non-corporate IPs. Check Entra → Security → Conditional Access and exclude the `copilotkit-vscode-publish` SP from any policy that filters on location. |
| 115 | +4. **Missing `permissions: id-token: write` on the `publish` job.** Without this, GitHub will not mint an OIDC token and `azure/login@v2` will fail with "Unable to get ACTIONS_ID_TOKEN_REQUEST_URL env variable". |
| 116 | + |
| 117 | +### Temporary rollback to PAT |
| 118 | + |
| 119 | +`VSCE_PAT` is retained in the `production` environment secrets specifically as a rollback lever for this cutover window. To revert: |
| 120 | + |
| 121 | +1. Check out `.github/workflows/publish-vscode-extension.yml`, replace `--azure-credential` with `--pat "$VSCE_PAT"`, and re-add the `env: { VSCE_PAT: ${{ secrets.VSCE_PAT }} }` block on the `Publish to VS Code Marketplace` step. |
| 122 | +2. Push. Ship the release. Debug OIDC out of band. |
| 123 | + |
| 124 | +### Retiring the PAT |
| 125 | + |
| 126 | +Once OIDC publishing has been confirmed green in CI at least once, **delete `VSCE_PAT` from the `production` environment**. That commit — the one that removes the last Marketplace PAT from the pipeline — is the actual completion of this migration. Until then the PAT is still a live credential and still needs to be treated as one for rotation / incident response purposes. |
| 127 | + |
| 128 | +## Notes and future work |
| 129 | + |
| 130 | +- **ADO PAT retirement (2026-12-01):** Azure DevOps is sunsetting long-lived Marketplace PATs. This workflow has been migrated to OIDC / federated credentials ahead of that date; `VSCE_PAT` is retained only as a short-lived rollback lever (see above). |
| 131 | +- **If the npm monorepo release pipeline ever needs to coordinate with VSIX releases** (e.g. pin the extension to a known-good `@copilotkit/*` version), extend `release.config.json` with a `vscode-extension` scope and gate it on the same version-on-main trigger — do not try to bolt VSIX publishing onto `scripts/release/publish-release.ts` which is npm-only. |
| 132 | + |
| 133 | +--- |
| 134 | + |
| 135 | +## One-time setup (historical reference) |
| 136 | + |
| 137 | +The following setup was completed during pipeline bootstrap (2026-04-21) and should not need to be redone. Documented here for disaster recovery / re-bootstrapping. |
| 138 | + |
| 139 | +### Prerequisites (one-time, maintainer) |
| 140 | + |
| 141 | +Configuration lives in the **`production`** GitHub environment (repo → Settings → Environments → production), split between non-secret **variables** and **secrets**: |
| 142 | + |
| 143 | +Environment **variables** (non-secret, consumed by `azure/login@v2`): |
| 144 | + |
| 145 | +- `AZURE_CLIENT_ID` — client ID of the `copilotkit-vscode-publish` Entra Service Principal. |
| 146 | +- `AZURE_TENANT_ID` — tenant ID for copilotkit.ai. |
| 147 | +- `AZURE_SUBSCRIPTION_ID` — subscription the SP is scoped into. |
| 148 | + |
| 149 | +Environment **secrets**: |
| 150 | + |
| 151 | +- `OVSX_PAT` — Open VSX Personal Access Token (bot-owned, quarterly rotation). OIDC is not yet supported on Open VSX, so a PAT is still required here. |
| 152 | +- `VSCE_PAT` — **retained as explicit rollback only.** No longer referenced by the workflow; Marketplace auth is OIDC. Delete this secret once OIDC publishing has been confirmed green at least once in production (see [Rollback guidance](#rollback-guidance)). |
| 153 | +- `SLACK_WEBHOOK` (optional) — webhook for the #oss-alerts release ping. Publish succeeds without it. |
| 154 | + |
| 155 | +### One-time Open VSX setup (first release only) |
| 156 | + |
| 157 | +Open VSX requires claiming the namespace before the first publish: |
| 158 | + |
| 159 | +1. Maintainer signs up at <https://open-vsx.org> with their GitHub identity. |
| 160 | +2. Creates a personal access token at <https://open-vsx.org/user-settings/tokens>. |
| 161 | +3. Claims the publisher namespace (one-time, from any machine): |
| 162 | + |
| 163 | + ```bash |
| 164 | + npx --yes ovsx create-namespace copilotkit -p <token> |
| 165 | + ``` |
| 166 | + |
| 167 | +4. Stores the token as `OVSX_PAT` in the `production` GH environment. |
| 168 | + |
| 169 | +Once the namespace is claimed, subsequent CI publishes run unattended. |
0 commit comments