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 from raw.githubusercontent.com
specify extension add — downloading extension ZIP from GitHub release assets
SPECKIT_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 Found
Expected 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:
--dev from local clone — specify extension add /path/to/local/extension --dev (requires repo cloned locally)
- Localhost proxy — download catalog/zips via
gh CLI, rewrite URLs to localhost, serve with python3 -m http.server, then point SPECKIT_CATALOG_URL at localhost
--from with 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 via uv tool)
- OS: macOS
- Auth available:
gh auth token returns valid PAT with repo scope
Description
When using
specify extensioncommands with a catalog hosted in a private GitHub repository, all network operations fail with HTTP 404 because the CLI uses unauthenticatedurllib.request.urlopenfor 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 repoCurrent Behavior
Expected Behavior
When
GITHUB_TOKENorGH_TOKENis set in the environment, speckit should include anAuthorization: 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.Suggested Implementation
The change is small — in the
_fetch_single_cataloganddownload_extensionmethods ofextensions.py, add auth headers when a recognized env var is present and the URL is a GitHub domain:Workarounds
Currently the only options for private repos are:
--devfrom local clone —specify extension add /path/to/local/extension --dev(requires repo cloned locally)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.zipNone of these support the native
specify extension search→specify extension addworkflow that public catalogs enjoy.Use Case
Organizations hosting internal extension catalogs in private GitHub repositories. The
extension-catalogs.ymlmulti-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
specify-cli(installed viauv tool)gh auth tokenreturns valid PAT with repo scope