Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
8a98bba
feat: add GitHub Codespaces provider contract
coygeek Jun 14, 2026
cc82d3a
merge: integrate github codespaces contract plan
coygeek Jun 14, 2026
b55b1ab
feat(github-codespaces): implement SSH lease lifecycle
coygeek Jun 14, 2026
261c1d3
merge: integrate github codespaces lifecycle plan
coygeek Jun 14, 2026
1e4c1b1
docs(github-codespaces): add docs and live smoke
coygeek Jun 14, 2026
dc0d210
merge: integrate github codespaces docs smoke plan
coygeek Jun 14, 2026
af8e365
merge: update github codespaces branch from upstream main
coygeek Jun 14, 2026
0bbc647
fix(github-codespaces): harden lifecycle defaults
coygeek Jun 14, 2026
5462f1f
fix(github-codespaces): scope live smoke cleanup check
coygeek Jun 14, 2026
29c5932
fix(github-codespaces): propagate runtime SSH config
coygeek Jun 14, 2026
ee6aa03
Merge remote-tracking branch 'origin/main' into feat/github-codespace…
coygeek Jun 14, 2026
7080fe2
fix(github-codespaces): cap display names
coygeek Jun 14, 2026
918085e
Merge remote-tracking branch 'origin/main' into feat/github-codespace…
coygeek Jun 14, 2026
539e6a4
Merge remote-tracking branch 'origin/main' into feat/github-codespace…
coygeek Jun 14, 2026
e525fc5
fix(github-codespaces): retain dirty leases on release
coygeek Jun 14, 2026
b07db3b
fix(github-codespaces): retain fallback claims
coygeek Jun 14, 2026
3a4a98a
fix(github-codespaces): accept start no-op
coygeek Jun 14, 2026
e4a3bf5
fix(github-codespaces): honor type aliases
coygeek Jun 14, 2026
eac4e03
fix(github-codespaces): accept delete no-op
coygeek Jun 14, 2026
d2899e6
fix(github-codespaces): probe status waits
coygeek Jun 14, 2026
b520fe3
Merge remote-tracking branch 'origin/main' into feat/github-codespace…
coygeek Jun 14, 2026
9823d90
fix(github-codespaces): preserve delete release policy
coygeek Jun 14, 2026
1887b52
fix(github-codespaces): block repo config redirects
coygeek Jun 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/providers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ selection metadata. Regenerate it with `node scripts/generate-provider-matrix.mj
`scripts/check-docs.sh` fails when provider registration, metadata, docs paths, or
this generated table drift.

Current built-in surface: 55 providers (32 SSH lease, 22 delegated run, 1 service control).
Current built-in surface: 56 providers (33 SSH lease, 22 delegated run, 1 service control).

Access terms:

Expand Down Expand Up @@ -90,6 +90,7 @@ Access terms:
| [external](external.md) (`exec-provider`) | built-in; `ssh-lease` · external-provider | Crabbox-managed SSH; `crabbox-sync` · direct only; features: `ssh`, `crabbox-sync`, `cleanup`, `desktop`, `browser`, `code` | `linux`; Configured executable contract | `byo`; GPU: unknown | external executable; contract-defined | Private or organization-specific provider integration | Safety and semantics depend on the configured executable |
| [freestyle](freestyle.md) | built-in; `delegated-run` · delegated-sandbox | No SSH; `archive-sync` · direct only; features: `archive-sync` | `linux`; Freestyle VM | `provider-managed`; GPU: unknown | Freestyle; provider VM cleanup | Hosted delegated Linux VM execution | No Crabbox-managed SSH path |
| [gcp](gcp.md) (`google`, `google-cloud`) | built-in; `ssh-lease` · brokerable-cloud | Crabbox-managed SSH; `crabbox-sync` · coordinator optional; features: `ssh`, `crabbox-sync`, `cleanup`, `tailscale` | `linux`; Google Compute Engine VM | `cloud`; GPU: optional | Crabbox or coordinator; instance and firewall cleanup | Linux compute with broad machine selection | Project, IAM, quota, and firewall setup required |
| [github-codespaces](github-codespaces.md) (`codespaces`, `gh-codespaces`) | built-in; `ssh-lease` · direct-cloud | Crabbox-managed SSH; `crabbox-sync` · direct only; features: `ssh`, `crabbox-sync`, `cleanup` | `linux`; GitHub Codespace | `provider-managed`; GPU: no | GitHub Codespaces; delete or stop claim-owned Codespace | Repository-backed Linux devcontainer over SSH | Requires gh auth, Codespaces quota, and an SSH-enabled devcontainer |
| [hetzner](hetzner.md) | built-in; `ssh-lease` · brokerable-cloud | Crabbox-managed SSH; `crabbox-sync` · coordinator optional; features: `ssh`, `crabbox-sync`, `cleanup`, `desktop`, `browser`, `code`, `tailscale` | `linux`; Hetzner Cloud server | `cloud`; GPU: no | Crabbox or coordinator; server delete | Cost-effective high-CPU Linux VM | Linux-only and capacity varies by location |
| [hostinger](hostinger.md) | built-in; `ssh-lease` · direct-cloud | Crabbox-managed SSH; `crabbox-sync` · direct only; features: `ssh`, `crabbox-sync`, `cleanup` | `linux`; Hostinger VPS | `cloud`; GPU: no | Hostinger subscription; stop only | Direct Linux VPS with persistent subscription | Purchase needs opt-in and release does not cancel billing |
| [hyperv](hyperv.md) | built-in; `ssh-lease` · local-vm | Crabbox-managed SSH; `crabbox-sync` · direct only; features: `ssh`, `crabbox-sync`, `cleanup` | `windows/normal`; Microsoft Hyper-V VM | `local`; GPU: no | Crabbox; VM delete | Local native Windows VM | Windows host with Hyper-V required |
Expand Down
261 changes: 261 additions & 0 deletions docs/providers/github-codespaces.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
# GitHub Codespaces Provider

Read this when you are:

- choosing `provider: github-codespaces`;
- validating a direct GitHub Codespaces SSH lease;
- changing `internal/providers/githubcodespaces` or the guarded live smoke.

GitHub Codespaces is a Linux-only **SSH lease** provider. Crabbox creates a
Codespace from a GitHub repository, asks `gh codespace ssh --config` for the
OpenSSH connection details, stores that generated SSH config in Crabbox state,
and then uses the normal Crabbox SSH sync, `run`, `ssh`, `status`, and
`stop` paths.

The provider is **direct-only** in this release. It never routes through the
coordinator, so the local CLI must have GitHub CLI authentication and the
operator owns quota, billing, retention, and cleanup.

## When To Use It

Use GitHub Codespaces when the desired execution surface is a repository-backed
Codespace and the project already has an SSH-enabled Linux devcontainer. Prefer
AWS, Azure, GCP, Hetzner, Linode, or DigitalOcean when you need a plain VM,
coordinator-side credentials, broad OS support, or cloud-specific cost controls.

## Commands

```sh
crabbox doctor --provider github-codespaces --github-codespaces-repo example-org/my-app
crabbox warmup --provider github-codespaces --github-codespaces-repo example-org/my-app --type basicLinux32gb
crabbox run --provider github-codespaces --id my-app -- pnpm test
crabbox ssh --provider github-codespaces --id my-app
crabbox stop --provider github-codespaces my-app
crabbox cleanup --provider github-codespaces --dry-run
```

Aliases: `codespaces`, `gh-codespaces`.

`--id` accepts the canonical lease id (`cbx_...`), the friendly slug, or the
GitHub Codespace name when a matching local Crabbox claim exists. Crabbox
refuses to manage an unclaimed Codespace by name.

## Requirements

- Install the GitHub CLI as `gh`, or point Crabbox at it with
`githubCodespaces.ghPath`, `CRABBOX_GITHUB_CODESPACES_GH_PATH`, or
`--github-codespaces-gh-path`.
- Authenticate `gh` with an account that can create Codespaces for the selected
repository:

```sh
gh auth login
gh auth status
```

- Ensure `GH_TOKEN`, `GITHUB_TOKEN`, or the `gh` credential store has a token
with access to Codespaces and the selected repository.
- Configure the repository with an SSH-enabled Linux devcontainer. The image
must run an SSH server and include Git, `rsync`, and `tar`. A common
devcontainer feature is `ghcr.io/devcontainers/features/sshd:1`.
- Keep local OpenSSH and `rsync` available for Crabbox's data plane.

The provider asks GitHub for an OpenSSH config rather than shelling through
`gh codespace ssh -- <command>`. That keeps the normal Crabbox sync/run/ssh
behavior intact, including `rsync -e "ssh -F ..."`.

## Configuration

```yaml
provider: github-codespaces
target: linux
githubCodespaces:
repo: example-org/my-app
ref: main
machine: basicLinux32gb
devcontainerPath: .devcontainer/devcontainer.json
workingDirectory: /workspaces/my-app
geo: ""
idleTimeout: 30m
retentionPeriod: 168h
deleteOnRelease: true
ghPath: gh
workRoot: /workspaces/my-app
```

Config keys under `githubCodespaces:`:

| Key | Default | Notes |
| --- | --- | --- |
| `apiUrl` | `https://api.github.com` | Trusted config only; useful for GitHub Enterprise-style API routing when supported by the environment. |
| `ghPath` | `gh` | Trusted config only; local GitHub CLI executable. |
| `repo` | inferred from the GitHub remote when possible | Repository in `owner/name` form. Trusted config, environment, or CLI flag only; repo-local config cannot redirect Codespaces creation. Required when no GitHub remote can be inferred. |
| `ref` | empty | Git ref for new Codespaces. Empty uses GitHub's default behavior. |
| `machine` | `basicLinux32gb` | GitHub Codespaces machine slug. `--type` is an alias for this value. |
| `devcontainerPath` | empty | Optional devcontainer path for creation. |
| `workingDirectory` | empty | Optional Codespaces working directory setting. |
| `geo` | empty | Optional GitHub geographic location preference. |
| `idleTimeout` | `30m` | Codespaces idle timeout sent to GitHub on create. |
| `retentionPeriod` | `168h` | Codespaces retention period sent to GitHub on create. |
| `deleteOnRelease` | `true` | Delete on `stop` unless a retained lease claim says release by stopping. |
| `workRoot` | `/workspaces/<repo>` when repo is known | Remote path Crabbox syncs to and runs from. |

Provider flags:

```text
--github-codespaces-repo
--github-codespaces-ref
--github-codespaces-machine
--github-codespaces-devcontainer-path
--github-codespaces-working-directory
--github-codespaces-geo
--github-codespaces-idle-timeout
--github-codespaces-retention-period
--github-codespaces-delete-on-release
--github-codespaces-gh-path
--github-codespaces-work-root
```

Environment overrides:

```text
CRABBOX_GITHUB_CODESPACES_API_URL
CRABBOX_GITHUB_CODESPACES_GH_PATH
CRABBOX_GITHUB_CODESPACES_REPO
CRABBOX_GITHUB_CODESPACES_REF
CRABBOX_GITHUB_CODESPACES_MACHINE
CRABBOX_GITHUB_CODESPACES_DEVCONTAINER_PATH
CRABBOX_GITHUB_CODESPACES_WORKING_DIRECTORY
CRABBOX_GITHUB_CODESPACES_GEO
CRABBOX_GITHUB_CODESPACES_IDLE_TIMEOUT
CRABBOX_GITHUB_CODESPACES_RETENTION_PERIOD
CRABBOX_GITHUB_CODESPACES_DELETE_ON_RELEASE
CRABBOX_GITHUB_CODESPACES_WORK_ROOT
```

Do not put GitHub tokens in Crabbox config or on command lines. Use
`GH_TOKEN`, `GITHUB_TOKEN`, or the GitHub CLI credential store.

## Lifecycle

1. Read GitHub CLI auth state and login identity.
2. Resolve the repository from `githubCodespaces.repo`, flags, env, or the
current GitHub remote.
3. Check available Codespaces machines for the repo/ref.
4. Create a Codespace with the configured machine, ref, devcontainer path,
working directory, geo, idle timeout, retention period, and Crabbox display
name.
5. Store a local Crabbox claim that binds the lease id, slug, Codespace name,
repository, machine, and GitHub login.
6. Wait for the Codespace to become available.
7. Ask `gh codespace ssh --config -c <codespace>` for OpenSSH config, store it
under Crabbox state, select the matching target, and wait for SSH readiness.
8. Use normal Crabbox SSH and rsync behavior for `run`, `sync`, and `ssh`.
9. On `stop`, delete or stop the claim-owned Codespace according to the release
policy.

If a retained Codespace is stopped, resolving it later starts it and waits for
availability before refreshing the generated SSH config.

## Ownership And Cleanup

GitHub Codespaces does not expose custom user labels. Crabbox therefore uses a
local claim as the ownership predicate. Release and cleanup require the claim to
match the provider, Codespace name, and creating GitHub login.

Deletion is conservative:

- Crabbox refuses to release a Codespace without a local claim.
- Crabbox refuses to delete when GitHub reports uncommitted or unpushed changes.
- Cleanup mutates only expired claim-owned Codespaces.
- Account switches are rejected when the current `gh` login differs from the
claim login.

Use dry-run cleanup before mutation:

```sh
crabbox list --provider github-codespaces --json
crabbox cleanup --provider github-codespaces --dry-run
crabbox cleanup --provider github-codespaces
```

## SSHD And Devcontainer Contract

`gh codespace ssh --config` requires an SSH server inside the Codespace. A plain
devcontainer image that does not start `sshd` is not enough for Crabbox because
Crabbox needs direct OpenSSH and rsync access.

For a devcontainer-based smoke, include an SSH feature such as:

```json
{
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
"features": {
"ghcr.io/devcontainers/features/sshd:1": {}
}
}
```

The ready path also expects Git, `rsync`, `tar`, and a writable work root.

## Guarded Live Smoke

The repeatable live check is opt-in and local-only:

```sh
CRABBOX_LIVE=1 \
CRABBOX_LIVE_PROVIDERS=github-codespaces \
CRABBOX_GITHUB_CODESPACES_SMOKE_REPO=example-org/my-app \
GH_TOKEN=... \
scripts/live-github-codespaces-smoke.sh
```

The script defaults to a skipped classification and does not call Crabbox unless
`CRABBOX_LIVE=1`, the provider filter selects `github-codespaces`, a smoke repo
is supplied, and GitHub credentials are explicitly available. It runs a read-only
doctor first, creates a short-lived Codespace lease, runs a command through the
normal synced Crabbox path, prints the Crabbox SSH command, releases the lease,
runs dry-run cleanup, and verifies the claim-owned inventory is empty.

Final classifications include:

```text
classification=live_github_codespaces_smoke_passed
classification=environment_blocked
classification=credential_bound
classification=quota_blocked
classification=validation_failed
classification=cleanup_failed
```

If credentials, entitlement, quota, or local `gh` auth are unavailable, report
the classification instead of treating the live smoke as a provider failure.

## Capabilities

- **OS target**: Linux only.
- **SSH**: yes, from `gh codespace ssh --config`.
- **Crabbox sync**: yes, through normal OpenSSH/rsync.
- **Coordinator**: never; direct CLI only.
- **Desktop / browser / code**: not advertised in this release.
- **Tailscale**: not advertised; GitHub's SSH path is used.
- **Cleanup**: yes, claim-owned only.

## Gotchas

- `--class` is not supported. Use `--type <machine>` or
`--github-codespaces-machine <machine>`.
- `provider=github-codespaces` supports `target=linux` only.
- A Codespace without an SSH server fails during SSH config or readiness.
- Manual Codespaces are intentionally invisible to Crabbox unless a local
Crabbox claim exists.
- `deleteOnRelease: true` still refuses deletion when GitHub reports uncommitted
or unpushed work.

## Related Docs

- [Provider reference](README.md)
- [Provider backends](../provider-backends.md)
- [Provider feature overview](../features/providers.md)
- [providers command](../commands/providers.md)
- [ssh command](../commands/ssh.md)
14 changes: 14 additions & 0 deletions docs/providers/provider-metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,20 @@
"caveat": "Project, IAM, quota, and firewall setup required",
"docs": "gcp.md"
},
"github-codespaces": {
"status": "built-in",
"category": "direct-cloud",
"substrate": "GitHub Codespace",
"location": "provider-managed",
"ssh": "crabbox-managed",
"sync": "crabbox-sync",
"gpu": "no",
"lifecycle": "GitHub Codespaces",
"cleanup": "delete or stop claim-owned Codespace",
"bestFit": "Repository-backed Linux devcontainer over SSH",
"caveat": "Requires gh auth, Codespaces quota, and an SSH-enabled devcontainer",
"docs": "github-codespaces.md"
},
"hetzner": {
"status": "built-in",
"category": "brokerable-cloud",
Expand Down
6 changes: 4 additions & 2 deletions docs/source-map.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ SSH-lease providers:
- Google Cloud (Compute Engine): `internal/providers/gcp`, with CLI helpers in `internal/cli/gcp.go`
- Hetzner Cloud: `internal/providers/hetzner`, with CLI helpers in `internal/cli/hcloud.go`
- DigitalOcean Droplets: `internal/providers/digitalocean`, with config glue in `internal/cli/config.go`
- GitHub Codespaces: `internal/providers/githubcodespaces`, with config glue
and env overrides in `internal/cli/config.go`
- OVHcloud Public Cloud: `internal/providers/ovh`, with config glue in `internal/cli/config.go`
- Parallels (macOS VM host): `internal/providers/parallels`, with CLI helpers in `internal/cli/parallels.go`
- Proxmox VE: `internal/providers/proxmox`, with CLI helpers in `internal/cli/proxmox.go`
Expand Down Expand Up @@ -135,7 +137,7 @@ Actions hydration or repo scripts.
Provider docs:

- Per-provider feature notes: `docs/features/aws.md`, `docs/features/azure.md`, `docs/features/hetzner.md`, `docs/features/blacksmith-testbox.md`, `docs/features/namespace-devbox.md`, `docs/features/namespace-devbox-setup.md`, `docs/features/semaphore.md`, `docs/features/sprites.md`, `docs/features/daytona.md`, `docs/features/islo.md`, `docs/features/e2b.md`
- Per-provider reference: `docs/providers/README.md` plus one file per provider under `docs/providers/`, including `docs/providers/apple-vz.md` for the local Apple Silicon `Virtualization.framework` path, `docs/providers/digitalocean.md` for the direct Droplet provider, `docs/providers/ovh.md` for the direct OVHcloud provider, `docs/providers/incus.md` for the separate local live validation contract, and `docs/providers/superserve.md` for delegated Superserve execution and live proof
- Per-provider reference: `docs/providers/README.md` plus one file per provider under `docs/providers/`, including `docs/providers/apple-vz.md` for the local Apple Silicon `Virtualization.framework` path, `docs/providers/digitalocean.md` for the direct Droplet provider, `docs/providers/github-codespaces.md` for the direct Codespaces SSH lease, `docs/providers/ovh.md` for the direct OVHcloud provider, `docs/providers/incus.md` for the separate local live validation contract, and `docs/providers/superserve.md` for delegated Superserve execution and live proof
- Provider/backend authoring guide: `docs/provider-backends.md`, `docs/features/provider-authoring.md`
- Tailscale contract: `docs/features/tailscale.md`

Expand Down Expand Up @@ -222,5 +224,5 @@ Provider docs:
- Release workflow and Homebrew tap fallback: `.github/workflows/release.yml`
- GoReleaser archives and Homebrew formula config: `.goreleaser.yaml`
- Docs command-surface check, link check, site builder, and Pages deploy: `scripts/check-command-docs.mjs`, `scripts/check-docs-links.mjs`, `scripts/build-docs-site.mjs`, `.github/workflows/pages.yml`
- Live provider smoke coverage: `scripts/live-smoke.sh`, plus provider-specific guarded smokes such as `scripts/live-digitalocean-smoke.sh` and `scripts/live-superserve-smoke.sh`
- Live provider smoke coverage: `scripts/live-smoke.sh`, plus provider-specific guarded smokes such as `scripts/live-digitalocean-smoke.sh`, `scripts/live-github-codespaces-smoke.sh`, and `scripts/live-superserve-smoke.sh`
- Live coordinator auth smoke coverage: `scripts/live-auth-smoke.sh`
2 changes: 1 addition & 1 deletion internal/cli/claim.go
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,7 @@ func applyLeaseClaimEndpoint(claim *leaseClaim, server Server, target SSHTarget)

func claimEndpointInactiveState(state string) bool {
state = strings.TrimSpace(state)
return statusTerminalState(state) || strings.EqualFold(state, "paused") || strings.EqualFold(state, "deleting")
return statusTerminalState(state) || strings.EqualFold(state, "stopped") || strings.EqualFold(state, "paused") || strings.EqualFold(state, "deleting")
}

// updateLeaseClaimTailscale records a tailnet endpoint on an existing claim.
Expand Down
Loading
Loading