-
Notifications
You must be signed in to change notification settings - Fork 7.2k
[Feature]: Support GITHUB_TOKEN authentication for private catalog and extension download URLs #2037
Description
Description
When using specify extension commands with a catalog hosted in a private GitHub repository, all network operations fail with HTTP 404 because the CLI uses unauthenticated urllib.request.urlopen for both catalog fetches and extension ZIP downloads.
This affects:
specify extension search— fetching catalog JSON fromraw.githubusercontent.comspecify extension add— downloading extension ZIP from GitHub release assetsSPECKIT_CATALOG_URL— same issue when pointing to a private repo
Current Behavior
# Catalog hosted in a private repo
$ specify extension catalog add \
"https://raw.githubusercontent.com/my-org/my-repo/main/speckit-extensions/catalog.json" \
--name internal --install-allowed
$ specify extension search jira
# Warning: Could not fetch catalog 'internal': Failed to fetch catalog from
# https://raw.githubusercontent.com/my-org/my-repo/main/speckit-extensions/catalog.json:
# HTTP Error 404: Not Found
# Same with SPECKIT_CATALOG_URL
$ SPECKIT_CATALOG_URL="https://raw.githubusercontent.com/my-org/my-repo/main/speckit-extensions/catalog.json" \
specify extension search jira
# HTTP Error 404: Not Found
# Setting GITHUB_TOKEN or GH_TOKEN has no effect
$ GITHUB_TOKEN=$(gh auth token) specify extension search jira
# HTTP Error 404: Not FoundExpected Behavior
When GITHUB_TOKEN or GH_TOKEN is set in the environment, speckit should include an Authorization: token <value> header on requests to GitHub-hosted URLs (raw.githubusercontent.com, github.com, api.github.com). This would enable organizations to host private extension catalogs without workarounds.
# Should work with token in environment
$ GITHUB_TOKEN=$(gh auth token) specify extension search jira
# Found 1 extension(s): ...
$ GITHUB_TOKEN=$(gh auth token) specify extension add jira-sync
# ✓ Extension installed successfully!Suggested Implementation
The change is small — in the _fetch_single_catalog and download_extension methods of extensions.py, add auth headers when a recognized env var is present and the URL is a GitHub domain:
import urllib.request
headers = {}
token = os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")
if token and any(
host in url
for host in ("raw.githubusercontent.com", "github.com", "api.github.com")
):
headers["Authorization"] = f"token {token}"
req = urllib.request.Request(url, headers=headers)
with urllib.request.urlopen(req, timeout=10) as response:
...Workarounds
Currently the only options for private repos are:
--devfrom local clone —specify extension add /path/to/local/extension --dev(requires repo cloned locally)- Localhost proxy — download catalog/zips via
ghCLI, rewrite URLs to localhost, serve withpython3 -m http.server, then pointSPECKIT_CATALOG_URLat localhost --fromwith local serve —gh release download+ localhost HTTP server +specify extension add ext --from http://localhost:PORT/ext.zip
None of these support the native specify extension search → specify extension add workflow that public catalogs enjoy.
Use Case
Organizations hosting internal extension catalogs in private GitHub repositories. The extension-catalogs.yml multi-catalog feature (added in #1707) already supports adding custom catalog URLs — this feature request completes the story by making those catalogs work when the source repo is private.
Environment
- speckit CLI:
specify-cli(installed viauv tool) - OS: macOS
- Auth available:
gh auth tokenreturns valid PAT with repo scope