Run gh codespace from Create to Cleanup Safely
A forgotten Codespace keeps costing you allowance long after you closed the tab, and gh codespace is how you stop that. This walkthrough creates one, finds its generated name, connects, copies a file and stops or deletes it. It takes about fifteen minutes, plus however long provisioning takes.
The route
Jump straight to the step you need, or tick off Done means at the end.
The examples use GitHub CLI 2.87.3, installed on this machine, and the gh codespace command group. You need:
- GitHub CLI installed.
- A GitHub account with Codespaces access.
- A repository you are allowed to use.
Authenticate once with gh auth login if this machine is not already signed in. That changes your local GitHub CLI authentication state, so follow its prompts carefully and never paste a token into shell history or a support request. None of the examples needs sudo.
1. Check the command and your login
Confirm the installed version and ask the command group what it can do:
$ gh --version
gh version 2.87.3 (2026-02-23)
$ gh codespace --help
The group also has the short alias gh cs. Use the longer form in scripts and notes, because it is easier for someone else to recognise. Check authentication before creating anything:
$ gh auth status
$ gh codespace list
Checkpoint
You should see either an empty list or the Codespaces you already own. The list command shows at most 30 entries by default. If there is more than one result, record the exact name in the first column.
Tip
Most later commands let you omit --codespace, but that starts an interactive selection and is an easy place to act on the wrong resource.
2. Create one Codespace deliberately
Warning
Creating a Codespace provisions a remote development environment and may use up an allowance or incur charges. Check the repository and branch before you run it, and replace both placeholders with real values.
$ gh codespace create --repo OWNER/REPOSITORY --branch BRANCH
The installed command also accepts --machine, --location, --idle-timeout, --retention-period and --devcontainer-path. Leave those defaults alone until you have a concrete reason to change them.
- Retention period. Controls how long a stopped Codespace remains before automatic deletion, with a maximum of 30 days in this version.
--web. Creates it from a browser, and cannot be combined with display name, idle timeout or retention period.
Capture the name from the creation output, then verify it without changing state:
$ gh codespace list --repo OWNER/REPOSITORY
$ gh codespace view --codespace CODESPACE_NAME
Tip
Use the exact value shown for CODESPACE_NAME, not the display name. If you lose it, list the repository again rather than guessing.
3. Open a shell in the Codespace
SSH suits a repeatable terminal workflow, but the Codespace must have an SSH server. GitHub's default image includes one. A custom image may need the SSHD dev container feature or an equivalent setup in its own configuration.
$ gh codespace ssh --codespace CODESPACE_NAME
On the first connection, gh can create a key pair under ~/.ssh if it cannot find a suitable existing key. That is a local file change, so check your SSH directory and key policy first. To run one remote command and return, put the command after --:
$ gh codespace ssh --codespace CODESPACE_NAME -- pwd
/workspaces/REPOSITORY
The path is an example: your repository name and working directory can differ. A non-zero status means the connection or remote command failed. It does not mean the Codespace was deleted.
4. Copy files without surprise expansion
gh codespace cp treats a path beginning with remote: as a path on the Codespace, relative to the remote user's home directory. Copy a local file to the remote workspace like this:
$ gh codespace cp ./README.md remote:/workspaces/REPOSITORY/
Copy in the other direction by putting the remote path first:
$ gh codespace cp remote:/workspaces/REPOSITORY/README.md ./README.remote.md
Directories require --recursive:
$ gh codespace cp --recursive ./local-config remote:/workspaces/REPOSITORY/
Warning
Remote names are literal by default. The --expand option evaluates a remote path like an scp shell expression, including globs, variables and backticks. Never use it with paths supplied by another person or an untrusted script. Quoting protects your local shell, but it does not make an expanded remote expression safe.
5. Stop work, then delete only what you mean
Stopping releases the running compute but keeps the Codespace for later:
$ gh codespace stop --codespace CODESPACE_NAME
$ gh codespace view --codespace CODESPACE_NAME
Stopping is the normal end-of-session action, and it is reversible: start or open the Codespace again through the GitHub interface or an applicable gh codespace command.
Destructive action
Deletion removes the Codespace. Check its name and any uncommitted or unpushed work first.
$ gh codespace view --codespace CODESPACE_NAME
$ gh codespace delete --codespace CODESPACE_NAME
The delete command asks for confirmation when unsaved changes are present. Do not add --force just to make a script non-interactive. There is no local undo for a deleted Codespace, so preserve work in Git or copy required files out first. Avoid --all and --days unless you have reviewed exactly which Codespaces match.
6. Diagnose the common traps
- Unexpected prompt. You usually omitted
--codespace. Cancel withCtrl-Cif you cannot identify the selected resource. - No SSH server. That is a Codespace image or configuration problem. Changing the local SSH key will not fix it.
- Directory copy failed. You probably left out
--recursive. - Odd file list. It can come from
--expand. Remove that option and quote literal paths. - Stopped is not deleted. List it and inspect its state before creating a replacement.
Done means
- Authenticated.
gh auth statussucceeds for the intended GitHub account. - Named. You recorded the exact Codespace name and checked its repository and state.
- Explicit. You connected or copied files using explicit paths.
- Tidy. You stopped the resource when finished, or verified its data was preserved before deletion.
- Careful. No command used elevated privileges,
--all,--forceor untrusted remote expansion casually.