GitHub Codespaces lifecycle management, SSH transport, and credential relay for Copilot CLI.
A copilot-extensions plugin that provides:
- SSH transport -- multiplexed SSH connections to CodeSpaces via
ssh-manager, wrapping
gh codespace ssh --config - Lifecycle management -- create, delete, list, and status for CodeSpaces
- Credential relay -- forward git credentials, GitHub tokens, and Azure tokens to CodeSpaces over SSH tunnels (pluggable sources: git-credential, gh-auth, az-login)
- Agent-bridge provider -- register CodeSpaces as dynamic agents in agent-bridge for inter-agent communication
All configuration lives in the adopting repo in a codespaces.yaml file.
The service reads config live from adopted repos -- no generated intermediate
config.
# codespaces.yaml
defaults:
machine_type: largePremiumLinux
location: EastUs
credentials:
sources:
git-credential:
enabled: true
allowed_hosts:
- "*.visualstudio.com"
- "dev.azure.com"
gh-auth:
enabled: true
allowed_hosts:
- "github.com"
# az-login: # Azure token relay (disabled by default)
# enabled: false
# allowed_resources:
# - "https://management.azure.com/"
repos:
org/my-repo:
machine_type: largePremiumLinux256gb
location: EastUsagent-codespaces ssh <name> # SSH into a CodeSpace
agent-codespaces ssh --stdio <name> # Structured SSH for agent-bridge
agent-codespaces list # List active CodeSpaces
agent-codespaces create <owner/repo> # Create a CodeSpace + run provisioning
agent-codespaces delete <name> # Delete a CodeSpace (--force to skip prompt)
agent-codespaces config adopt # Register repo for config
agent-codespaces config init # Scaffold codespaces.yaml from your CodeSpaces
agent-codespaces config show # Show resolved config
agent-codespaces bridge register # Register CodeSpaces as bridge agents
agent-codespaces cleanup # Remove stale local state (SSH configs, sockets)
agent-codespaces status # Service + relay + tunnel state
agent-codespaces version # Show versionagent-codespaces create <owner/repo> \
--branch <branch> \ # branch to create on (default: repo default)
--display-name <name> \ # CodeSpace display name
--timeout 300 \ # seconds to wait for Available (default 300)
--no-wait # don't wait / skip provisioningMachine type and location come from codespaces.yaml (per-repo overrides
apply). After the CodeSpace is Available, on_create provisioning hooks from
codespaces.yaml run automatically.
Usually unnecessary. Once agent-codespaces is installed, agent-bridge auto-registers the live
codespace:namespace resolver, so CodeSpaces are addressable ascodespace:<name>(raw or friendly) with no registration.bridge registeronly POSTs a staticcs-<name>snapshot (with a TTL) for HTTP consumers that prefer a pre-registered provider list; it is optional and superseded by the resolver.
agent-codespaces bridge register [--ttl 300] [--bridge-url <url>]
agent-codespaces bridge refresh [--ttl 300] [--bridge-url <url>]
agent-codespaces bridge status [--bridge-url <url>]
agent-codespaces bridge unregister [--bridge-url <url>]Linux/WSL: the bridge defaults to port 9281, but these commands default
--bridge-urltohttp://127.0.0.1:9280. On Linux/WSL pass--bridge-url http://127.0.0.1:9281explicitly.
The relay forwards git-credential requests from a CodeSpace back to the host
over the SSH tunnel, resolving them through the host's Git Credential Manager
(GCM) — which serves both GitHub (github.com) and Azure DevOps
(*.visualstudio.com, dev.azure.com) credentials.
To avoid the failure mode where a missing/expired credential causes a CodeSpace
git fetch to hang indefinitely on git credential fill:
- Host GCM runs non-interactively (
GIT_TERMINAL_PROMPT=0,GCM_INTERACTIVE=never), so it errors fast instead of blocking on a prompt. - The relay replies
quit=1when a gitget/fillrequest can't be resolved, which makes git in the CodeSpace abort immediately (fatal: credential helper ... told us to quit) rather than dropping to an interactive prompt. CodeSpace SSH sessions also exportGIT_TERMINAL_PROMPT=0. - On connect, remote-domain auth is verified up front: the workspace's
git remote -vdomains are probed against the host credential store, and any domain lacking local auth is reported as a[WARN]so it can be fixed (az login/ GCM sign-in) before work begins, rather than discovered mid-fetch.
This is a public repo, so internal org/account/repo names and personal
aliases must never land in it. The generated codespaces.yaml scaffold is
checked for such leaks by tests/test_config_init.py, and the whole working
tree by tools/check-no-internal-identifiers.py
(wire it up as a git pre-push hook).
A denylist that named those identifiers would itself leak them, so it is never stored in the repo. Both guards read it privately from:
- env
COPILOT_EXTENSIONS_FORBIDDEN_IDS(comma-separated), and ~/.agent-codespaces/forbidden-identifiers.txt(one per line; blank lines and#comments ignored).
With neither configured (a fresh clone / CI) the identifier check is a no-op, so the guards are safe to ship. Populate one of the sources on your own machine — e.g.:
# ~/.agent-codespaces/forbidden-identifiers.txt
my-internal-org
my-internal-repo
my-alias
Matching is case-insensitive (substring). The host file lives in $HOME, outside
any repo, so it is never committed.
cd plugins/agent-codespaces
pip install -e ".[dev]" -e "../../libs/ssh-manager[dev]"
pytest tests/