VS Code拡張をCIから公開しようとしたら、認証まわりが2026年時点で思ったより深かった話
はじめに
個人で作っている VS Code 拡張機能を、GitHub Actions で「リリースを publish したら自動で公開される」状態にしたい——そう思って作業を始めたら、公開先の選定・認証・トークンの廃止スケジュールまで、想像以上に論点が多いことに気づきました。
この記事は、実際に拡張を公開できるところまで持っていく過程で出てきた疑問を、同じところで詰まりそうな読者に向けて再構成した探求メモです。具体的には次の4点を扱います。
- 拡張の公開先は Marketplace / Open VSX / npm のどれ?「拡張って結局 npm パッケージだから npm にも出すべき?」問題
- VS Code Marketplace の認証(PAT)がしんどい。しかも 2026年末で PAT が廃止される
- その代替の Entra ID / OIDC は、GitHub Actions + 個人アカウントだと現実的に厳しい
- 一方で npm は Trusted Publishing(OIDC)でトークンレス公開ができるようになっていた
前段として、CI をまともに動かすために TypeScript / Node のバージョン整備でも少しはまったので、そこから書きます。
前提・環境
- パッケージマネージャ: pnpm
- CI: GitHub Actions
- 言語: TypeScript
- 執筆時点: 2026年7月
まずCIを直す: Dependabot失敗・TypeScript 6・Node LTS
公開の話に入る前に、Dependabot の PR がことごとく失敗していたのを直しました。原因は2つに分かれていました。
1. auto-merge ワークフローが存在しないアクションを参照していた
##[error]Unable to resolve action `dependabot/fetch-metadata@v5`, unable to find version `v5`
dependabot/fetch-metadata の最新は v3 系で、v5 は存在しませんでした。すべての Dependabot PR がこのステップで即死していたので、@v3 に修正するだけで解消です。
2. TypeScript のメジャー更新で型チェックが壊れた
typescript を 6 系に上げる PR だけ、型チェックがこう落ちました。
src/util/debounce.ts(2,32): error TS2304: Cannot find name 'setTimeout'.
tsconfig.json の lib は ES2020(DOM を含めない)だったので、setTimeout / clearTimeout は @types/node から供給される想定です。ところが types を明示していなかったため、すべての @types/* が暗黙で取り込まれる構成になっていて、メジャー更新の組み合わせでこのグローバルが解決できなくなっていました。
ベストプラクティスは「環境を暗黙頼みにせず明示する」こと。VS Code 拡張なら次のように書きます。
{
"compilerOptions": {
"lib": ["ES2020"],
"types": ["node", "vscode"]
}
}
types を指定すると、その配列に書いたパッケージだけがグローバルスコープに入ります。余計な @types/* が混ざらず、Node のグローバルも確実に載ります。VS Code 拡張は vscode モジュールも使うので "vscode" を含めるのを忘れないようにします(外すと import * as vscode from 'vscode' が解決できなくなります)。
@types/node は「最新」ではなく「Node の LTS」に合わせる
Dependabot は @types/node を 26 系に上げようとしてきましたが、2026年7月時点での状況を整理すると次の通りです。
| バージョン | 位置づけ | 補足 |
|---|---|---|
| Node 24 | Active LTS | 本番推奨。EOL は2028年4月 |
| Node 26 | Current | 2026年4月リリース。LTS 昇格は2026年10月 |
| Node 22 | Maintenance LTS | EOL は2027年4月 |
VS Code 拡張のランタイムは VS Code に同梱された Node なので、@types/node は「最新」より「実行環境に近い LTS」に合わせるのが理にかなっています。ここでは 26(Current)ではなく 24(LTS) を採用し、Dependabot 側では @types/node のメジャー更新を無視する設定にして LTS ラインに固定しました。
ignore:
- dependency-name: "@types/node"
update-types:
- "version-update:semver-major"
拡張の公開先: Marketplace / Open VSX / npm
ここからが本題です。「VS Code 拡張って package.json を持つ npm 形式のパッケージだし、npm にも publish すべき?」という疑問が最初に出ました。
結論から言うと、公開先ごとに役割が違います。
| 公開先 | 何のため | 認証 | 備考 |
|---|---|---|---|
| VS Code Marketplace | 公式 VS Code ユーザー向け | Azure DevOps PAT(vsce) |
2026-12にPAT廃止予定 |
| Open VSX | VSCodium / Cursor / Windsurf / Gitpod 向け | GitHub ログイン(ovsx) |
無料・Azure不要 |
| npm | ライブラリとして配布する場合 | トークン or OIDC | 拡張本体の配布先ではない |
正直なところ、拡張本体を npm に公開する必然性は基本的にありません。理由は、拡張は .vsix として Marketplace / Open VSX から入るものであり、npm install で入れて使う人はいないからです。しかも拡張の main は vscode モジュールに依存していて、VS Code の拡張ホスト外では動きません。npm に出しても「動かないパッケージ」が並ぶだけ、というのが実態です。
npm publish が意味を持つのは、たとえばレンダリングロジックなど「拡張から切り出した再利用可能なライブラリ」を別パッケージとして出すケースです。それは拡張本体とは別物になります。

Marketplace と Open VSX は同じ .vsix を両方に出すのが素直です。まず1回だけパッケージして、同一成果物を両レジストリに publish します。
- name: Package extension
run: pnpm dlx @vscode/vsce package --no-dependencies -o extension.vsix
- name: Publish to VS Code Marketplace
run: pnpm dlx @vscode/vsce publish --no-dependencies --packagePath extension.vsix
env:
VSCE_PAT: ${{ secrets.VSCE_PAT }}
- name: Publish to Open VSX
run: |
pnpm dlx ovsx create-namespace YOUR_PUBLISHER -p "$OVSX_PAT" || true
pnpm dlx ovsx publish extension.vsix -p "$OVSX_PAT"
env:
OVSX_PAT: ${{ secrets.OVSX_PAT }}
Open VSX 側の注意点として、Eclipse Foundation の Publisher Agreement への署名が必須です(未署名だと publish が失敗します)。名前空間(publisher フィールドに対応)は初回 publish で自動作成されるので、create-namespace は保険であり、|| true で冪等にしています。名前空間の「検証済みバッジ」は任意で、後から GitHub Issue で申請できます。
VS Code Marketplaceの認証がしんどい
Marketplace への publish には Azure DevOps の PAT が要ります。この取得が一番の難所でした。ハマりどころを時系列で残しておきます。
PAT は Azure ポータルではなく Azure DevOps にある
marketplace.visualstudio.com/manage(publisher 管理)でも、Azure ポータルの Entra ID でもなく、dev.azure.com の「User settings → Personal access tokens」で作ります。スコープは Marketplace → Manage、Organization は All accessible organizations にするのがポイントです(単一 org に絞ると publish で失敗します)。
Azure DevOps の Organization 作成に Azure サブスクリプションが要る
これは最近のポリシー変更で、新規 Organization 作成には Azure サブスクリプションのリンクが必要になっていました。しかも 無料試用(Free Trial)サブスクリプションは弾かれます。従量課金(Pay-As-You-Go)なら「保持自体は無料(本人確認でカードは必要、使わなければ $0)」なので、実質そこを通す必要があります。
PAT廃止(2026-12)とEntra/OIDCの現実
苦労して PAT を用意する一方で、見過ごせない事実があります。Azure DevOps のグローバル PAT は2026年12月1日で廃止予定で、Microsoft は Entra ID ベースの認証への移行を推奨しています。vsce 側のフラグは --azure-credential です。
「じゃあ最初から Entra/OIDC で組めばいいのでは?」と考えて調べたのですが、GitHub Actions + 個人アカウント publisher の組み合わせでは、現時点で現実的に厳しいという結論になりました。理由は次の通りです。
- Microsoft の公式手順は ユーザー割り当てマネージド ID + Azure Pipelines 前提で、GitHub Actions 向けの手順が用意されていない
--azure-credentialを フェデレーテッド サービスプリンシパルで使うと、verify-patは通るのに publish で「corporate credentials が必要」というエラーになる報告があり、Issue は "not planned" で閉じられている(vscode-vsce#1023)- そのエラーは、個人アカウント所有の publisher に対して組織テナントの Entra トークンを提示する、という身元のミスマッチが根にある
まとめると、PAT と Entra/OIDC の立ち位置はこうです。
| PAT | Entra ID / OIDC | |
|---|---|---|
| 現状の GitHub Actions 対応 | 実績あり | 公式手順は Azure Pipelines 中心 |
| 個人アカウント publisher | 動く | 詰まりやすい(#1023) |
| 廃止スケジュール | 2026-12-01 | 今後の推奨 |
したがって現実解は「当面は PAT で公開し、廃止期限までに移行を再評価する」でした。期限まで約17か月あり、その頃には GitHub Actions 向けの手順が整うか、publisher を組織テナントへ移す判断ができるはずです。App 登録とフェデレーテッド資格情報だけ先に用意しておけば、切り替え自体は差分数行で済みます。
npm Trusted Publishing: トークンレスOIDC公開
Marketplace 側が「PAT がしんどい・OIDC はまだ厳しい」だったのに対し、npm 側は逆に OIDC が快適でした。npm の Trusted Publishing は2025年7月に GA していて、GitHub Actions からトークンなしで publish できます。
要件はシンプルです。
- ワークフローに
permissions: id-token: write - ランナーの npm CLI が 11.5.1 以上
- npm 側で「信頼する GitHub リポジトリ / ワークフローファイル名(+任意で environment)」を設定
そして NPM_TOKEN は不要になり、さらに provenance(来歴)が自動で付与されます。実際のログでも、トークンを一切渡していないのに provenance が Sigstore の透明性ログに記録されていました。
permissions:
contents: read
id-token: write # npm Trusted Publishing (OIDC)
# ...
- name: Publish to npm
run: |
npm install -g npm@latest # >= 11.5.1 を保証
npm publish --access public
注意点として、npm の Trusted Publisher 設定で environment を指定した場合は、ジョブ側にも environment: を合わせる必要があります。指定していなければ不要です。
補足しておくと、前述の通り拡張本体を npm に出す必然性は薄いので、これは「もし npm にも出すなら、その認証はトークンレスにできる」という話として読んでください。
完成したワークフロー
最終的に、リリースを publish したら3つの公開先へ流れるワークフローになりました。npm ステップは最後に置き、ここでコケても本命の Marketplace / Open VSX 公開を止めないようにしています。
name: Publish Extension
on:
release:
types: [published]
permissions:
contents: read
id-token: write # npm Trusted Publishing (OIDC)
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v6
with:
node-version: '24.x'
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm run typecheck
- run: pnpm run lint
- run: pnpm run test
- name: Package extension
run: pnpm dlx @vscode/vsce package --no-dependencies -o extension.vsix
- name: Publish to VS Code Marketplace
run: pnpm dlx @vscode/vsce publish --no-dependencies --packagePath extension.vsix
env:
VSCE_PAT: ${{ secrets.VSCE_PAT }}
- name: Publish to Open VSX
run: |
pnpm dlx ovsx create-namespace YOUR_PUBLISHER -p "$OVSX_PAT" || true
pnpm dlx ovsx publish extension.vsix -p "$OVSX_PAT"
env:
OVSX_PAT: ${{ secrets.OVSX_PAT }}
- name: Publish to npm
run: |
npm install -g npm@latest
npm publish --access public
リリースを publish したところ、3つとも成功しました。ログ上は Marketplace が Published YOUR_PUBLISHER.YOUR_EXTENSION vX.Y.Z、Open VSX が 🚀 Published ...、npm が provenance 付きで + YOUR_EXTENSION@X.Y.Z を返しています。
まとめ
VS Code 拡張の自動公開は、ワークフロー YAML 自体よりも「どこに出すか」「どう認証するか」の判断のほうが重い、というのが正直な感想でした。実務目線での判断基準を整理しておきます。
- 公開先: 拡張本体は Marketplace + Open VSX。npm は原則不要(ライブラリを切り出すときだけ)
- Marketplace 認証: 当面は PAT。ただし 2026-12-01 の PAT 廃止を見据える。Entra/OIDC は GitHub Actions + 個人アカウントだと現状ハマりやすいので、期限前に再評価
- Open VSX: 無料で導入コストも低い。Eclipse Publisher Agreement の署名だけ忘れずに
- npm(出すなら): Trusted Publishing でトークンレス+provenance が今どきの最適解
- 足回り:
@types/nodeは Node の LTS に合わせる、tsconfig のtypesは明示する
参考
- Publishing Extensions | Visual Studio Code Extension API
- npm trusted publishing with OIDC is generally available | GitHub Changelog
- Trusted publishing for npm packages | npm Docs
- Node.js Releases | endoflife.date
- Open VSX Namespace Access | eclipse-openvsx wiki
- vscode-vsce#1023 - Corporate credentials required with federated service principal






