Summary
The Netclaw GitHub App (Iv23lipIurKdMkbqy6nH, registered as netclaw-dev) returns HTTP 403 "Resource not accessible by integration" from https://api.github.com/copilot_internal/v2/token on every exchange attempt, regardless of which permissions the App declares. As of this issue's fix we work around it by impersonating the Neovim Copilot plugin's OAuth App (Iv1.b507a08c87ecfe98) — the same posture every other community Copilot client takes (avante.nvim, copilot.lua, CodeAlta). This issue captures the diagnosis so we can revisit the integration model later.
Diagnosis
Stage 0 of PR #1075 used the Neovim plugin's OAuth App ID as a placeholder. Commit b3d64d3f swapped it out for a Netclaw-owned GitHub App registered with "Account email read-only" permission. That swap broke any newly-minted OAuth token because:
/copilot_internal/v2/token is the editor-integration surface (VS Code, Neovim, JetBrains, gh CLI). It rejects GitHub App user-to-server tokens regardless of permission claims.
- Adding
Copilot Chat: Read-only and Copilot Editor Context: Read-only to the App did not change the behavior — still 403.
- A clean Docker container, fresh device flow, fresh exchange request: still 403.
Empirically verified across:
- The Netclaw-owned App at multiple permission settings
- A fresh container (
netclaw-copilot-test) with no prior state
- A CodeAlta-style reference implementation in
CodeAlta/CodeAlta — comment in their source: "GitHub Copilot extension OAuth app id. Ideally we should use our own, but for now we do what others do."
What was actually shipped (workaround)
- Reverted
ClientId in src/Netclaw.Providers/GitHubCopilot/GitHubCopilotDescriptor.cs to Iv1.b507a08c87ecfe98 (Neovim Copilot plugin OAuth App, allowlisted).
- Added the editor-integration header contract on the exchange request (
Editor-Version, Editor-Plugin-Version, Copilot-Integration-Id: vscode-chat, X-GitHub-Api-Version) — without Copilot-Integration-Id GitHub's gateway routes the token through the generic App permission model and the 403 stands even with the right client ID.
- Switched the exchange
Authorization scheme from token to Bearer to match what working clients send.
Real options to revisit
- Apply to GitHub for Netclaw's own OAuth App (not GitHub App) to be added to the Copilot integration allowlist. Clean long-term answer. Wall-clock measured in weeks; requires a partner-integration review on GitHub's side.
- Migrate to the documented Copilot SDK pathway (
docs.github.com/en/copilot/how-tos/copilot-sdk/...). The SDK uses the OAuth token directly, both OAuth Apps and GitHub Apps are first-class, and we'd be off the _internal endpoint permanently. A real engineering project — would be a v2 of the Copilot provider.
- Stay on impersonation indefinitely. Grey but proven posture. Works today.
Related: #1148 — GitHub Enterprise support
Almost certainly the same root cause. Two things bind us to non-enterprise github.com today:
- The hardcoded
https://api.github.com/copilot_internal/v2/token endpoint in src/Netclaw.Providers/GitHubCopilot/CopilotTokenExchanger.cs:32-33. Enterprise tenants use per-org subdomains under *.ghe.com; the exchange endpoint shape differs.
- The hardcoded
https://api.githubcopilot.com base for chat in src/Netclaw.Providers/GitHubCopilot/GitHubCopilotDescriptor.cs:24. CodeAlta's implementation resolves this dynamically from the exchange response's endpoints.api field AND from a proxy-ep= marker embedded in the Copilot token string itself, defaulting to api.individual.githubcopilot.com for personal accounts. See CodeAlta CopilotDirectAuth.cs#L407-L465.
Both of these need to land together for #1148 to be tractable; pursuing the SDK migration (option 2 above) would close both this issue and that one.
References
Summary
The Netclaw GitHub App (
Iv23lipIurKdMkbqy6nH, registered asnetclaw-dev) returns HTTP 403"Resource not accessible by integration"fromhttps://api.github.com/copilot_internal/v2/tokenon every exchange attempt, regardless of which permissions the App declares. As of this issue's fix we work around it by impersonating the Neovim Copilot plugin's OAuth App (Iv1.b507a08c87ecfe98) — the same posture every other community Copilot client takes (avante.nvim, copilot.lua, CodeAlta). This issue captures the diagnosis so we can revisit the integration model later.Diagnosis
Stage 0 of PR #1075 used the Neovim plugin's OAuth App ID as a placeholder. Commit
b3d64d3fswapped it out for a Netclaw-owned GitHub App registered with "Account email read-only" permission. That swap broke any newly-minted OAuth token because:/copilot_internal/v2/tokenis the editor-integration surface (VS Code, Neovim, JetBrains, gh CLI). It rejects GitHub App user-to-server tokens regardless of permission claims.Copilot Chat: Read-onlyandCopilot Editor Context: Read-onlyto the App did not change the behavior — still 403.Empirically verified across:
netclaw-copilot-test) with no prior stateCodeAlta/CodeAlta— comment in their source: "GitHub Copilot extension OAuth app id. Ideally we should use our own, but for now we do what others do."What was actually shipped (workaround)
ClientIdinsrc/Netclaw.Providers/GitHubCopilot/GitHubCopilotDescriptor.cstoIv1.b507a08c87ecfe98(Neovim Copilot plugin OAuth App, allowlisted).Editor-Version,Editor-Plugin-Version,Copilot-Integration-Id: vscode-chat,X-GitHub-Api-Version) — withoutCopilot-Integration-IdGitHub's gateway routes the token through the generic App permission model and the 403 stands even with the right client ID.Authorizationscheme fromtokentoBearerto match what working clients send.Real options to revisit
docs.github.com/en/copilot/how-tos/copilot-sdk/...). The SDK uses the OAuth token directly, both OAuth Apps and GitHub Apps are first-class, and we'd be off the_internalendpoint permanently. A real engineering project — would be a v2 of the Copilot provider.Related: #1148 — GitHub Enterprise support
Almost certainly the same root cause. Two things bind us to non-enterprise github.com today:
https://api.github.com/copilot_internal/v2/tokenendpoint insrc/Netclaw.Providers/GitHubCopilot/CopilotTokenExchanger.cs:32-33. Enterprise tenants use per-org subdomains under*.ghe.com; the exchange endpoint shape differs.https://api.githubcopilot.combase for chat insrc/Netclaw.Providers/GitHubCopilot/GitHubCopilotDescriptor.cs:24. CodeAlta's implementation resolves this dynamically from the exchange response'sendpoints.apifield AND from aproxy-ep=marker embedded in the Copilot token string itself, defaulting toapi.individual.githubcopilot.comfor personal accounts. SeeCodeAlta CopilotDirectAuth.cs#L407-L465.Both of these need to land together for #1148 to be tractable; pursuing the SDK migration (option 2 above) would close both this issue and that one.
References
b3d64d3f("chore(providers): use Netclaw-owned GitHub App client_id for Copilot"), part of PR initial pass at gh copilot as a provider #1075CodeAlta/CodeAlta—CopilotDirectAuth.cs