A codespace starts, but npm ci fails with npm ERR! 401 Unauthorized - GET https://npm.pkg.github.com/@acme%2fui because NPM_TOKEN is empty; meanwhile another team's repository somehow has access to the production analytics key, and the monthly bill shows 32-core machines idling over the weekend. Secrets and machine sizing are the two settings that most often go wrong when a team moves from a pilot to real use of Codespaces. This page sets both up deliberately, as part of cloud development environments for onboarding.

Codespaces secrets are separate from Actions and Dependabot secrets, even when they share a name. That separation is the root of most "I set the secret but it's not there" reports.

Diagnostic

Inside the failing codespace, check which expected variables are present, then list what is configured at each level from a terminal with admin rights:

#!/usr/bin/env bash
set -euo pipefail
for v in NPM_TOKEN SENTRY_DSN STRIPE_TEST_KEY; do
  val="${!v:-}"
  if [ -n "$val" ]; then echo "$v: set (${#val} chars)"; else echo "$v: MISSING"; fi
done
gh secret list --app codespaces --repo acme/shop
gh secret list --app codespaces --org acme
gh secret list --app actions --repo acme/shop

Expected bad output:

NPM_TOKEN: MISSING
SENTRY_DSN: set (95 chars)
STRIPE_TEST_KEY: MISSING
NAME          UPDATED
SENTRY_DSN    2026-07-02
NAME             UPDATED        VISIBILITY
ANALYTICS_KEY    2026-05-11     all
NAME          UPDATED
NPM_TOKEN     2026-08-20

NPM_TOKEN exists only as an Actions secret, so codespaces never receive it. And an organisation-level Codespaces secret with visibility all is injected into every repository's codespaces, including ones that have no business holding it.

Where Codespaces Secrets Come From Layers of secret sources that can inject variables into a codespace, from user to organisation. Where Codespaces Secrets Come From User secrets per developer, chosen repos Repository secrets Codespaces app, one repo Organisation secrets Codespaces app, scoped repos Actions secrets CI only, not injected
Actions secrets are not in this list; they never reach a codespace.

Root cause

GitHub keeps secrets per "app": Actions, Codespaces and Dependabot each have their own store. A secret created for Actions is available to workflow runs only; the Codespaces store is what gets injected as environment variables when a codespace starts. Within Codespaces there are three levels — user, repository and organisation — and organisation secrets have a visibility setting (all, private, or selected repositories). Defaults tend towards convenience: organisation admins pick all to make a pilot work, and every repository then receives the secret. Separately, secrets are injected at creation and on restart, so adding a secret while a codespace is running has no effect until it restarts.

Machine sizing has a similar shape. Without a policy, any developer can pick any machine type the organisation allows, and without an idle timeout policy the default of 30 minutes can be raised per user. Prebuilds, which run on their own machines, add a separate line to the bill that many teams do not notice until the first invoice.

Resolution

  1. Create secrets in the Codespaces store, scoped as narrowly as possible:
#!/usr/bin/env bash
set -euo pipefail
gh secret set NPM_TOKEN --app codespaces --repo acme/shop --body "$(op read 'op://dev/github-packages/read-token')"
gh secret set STRIPE_TEST_KEY --app codespaces --repo acme/shop --body "$(op read 'op://dev/stripe/test-secret-key')"
gh secret set ANALYTICS_KEY --app codespaces --org acme --visibility selected --repos acme/dashboard
gh secret list --app codespaces --repo acme/shop

Use read-only, development-scoped credentials: a packages token that can only read, Stripe test-mode keys, sandbox API keys. Nothing that can touch production data belongs in a CDE secret.

  1. Declare which secrets the repository expects in devcontainer.json, so Codespaces prompts developers for user-level values on creation and documents them in one place:
{
  "secrets": {
    "NPM_TOKEN": { "description": "Read-only token for GitHub Packages", "documentationUrl": "https://docs.acme.dev/onboarding/npm" },
    "STRIPE_TEST_KEY": { "description": "Stripe test-mode secret key" }
  }
}
  1. Restart running codespaces after adding secrets: gh codespace stop -c <name> and start again. Rebuilding is not required.

  2. Restrict machine types and idle time with an organisation policy. In Organisation settings → Codespaces → Policies, create a policy for the repository that limits machine types to those in hostRequirements and caps idle timeout and retention:

#!/usr/bin/env bash
set -euo pipefail
gh api /repos/acme/shop/codespaces/machines --jq '.machines[] | "\(.name)\t\(.cpus) cpu\t\(.memory_in_bytes/1073741824|floor) GB"'
gh api -X GET /orgs/acme/codespaces --jq '.codespaces[] | "\(.machine.name)\t\(.state)\t\(.last_used_at)\t\(.owner.login)"' | sort | head -20

Set the repository's hostRequirements to the smallest machine that runs the stack comfortably — often 4 cores and 16 GB — and let the policy prevent larger ones except for repositories that genuinely need them.

Secret Scope Options Table comparing user, repository and organisation Codespaces secrets on who sets them and where they apply. Secret Scope Options Scope Set by Applies to User each developer repos they choose Repository repo admins that repo only Org, selected org admins listed repos Org, all org admins every repo, avoid
Prefer repository scope for shared dev credentials and user scope for anything personal.

Expected output

NPM_TOKEN: set (40 chars)
SENTRY_DSN: set (95 chars)
STRIPE_TEST_KEY: set (107 chars)
$ npm ci
added 1412 packages in 38s
$ gh api -X GET /orgs/acme/codespaces --jq '[.codespaces[].machine.name] | group_by(.) | map("\(.[0]): \(length)")[]'
standardLinux32gb: 14

Every expected secret is present, the private package installs, and all codespaces for the repository run on the allowed machine type. The diagnostic prints only the length of each value, never the value itself, so its output is safe to paste into a support channel when something is still missing.

The organisation-level ANALYTICS_KEY now appears only in the dashboard repository's codespaces. Verify that by starting a codespace in an unrelated repository and checking the variable is absent — scoping mistakes are invisible from the repository that is configured correctly.

Prevention

  1. Audit organisation secret visibility quarterly. List organisation Codespaces secrets with visibility all and justify each one; most should be selected.

  2. Keep the expected-secrets list in devcontainer.json and check it in make doctor, which prints exactly which variable is missing and where to get it.

  3. Alert on cost anomalies. A weekly job that lists running codespaces older than a day, or machines larger than policy, catches forgotten workspaces before they dominate the bill.

Monthly Cost Before and After Policies Bar chart of one organisation's Codespaces cost before and after machine and idle policies. Monthly Cost Before and After Policies no policies 4,200 USD machine type limits 2,600 USD plus 30 min idle cap 1,500 USD
Illustrative figures; idle time and oversized machines were most of the reduction.

Platform caveats

All clients: secrets are environment variables inside the codespace, visible to any process the developer runs, including extensions. Treat them as development credentials that a compromised extension could read, and scope them accordingly.

Prebuilds: secrets are available to prebuilds only if configured for them; do not rely on user secrets during onCreateCommand. Keep private-package access in repository secrets if prebuilds need it.

Apple Silicon (ARM64) developers: machine types are x86_64 regardless of the client; size by the stack's needs, not by the laptop's specs.

Rollback

Delete or narrow secrets and remove the policy to return to the previous state; running codespaces keep injected values until restarted:

#!/usr/bin/env bash
set -euo pipefail
gh secret delete STRIPE_TEST_KEY --app codespaces --repo acme/shop
gh secret set ANALYTICS_KEY --app codespaces --org acme --visibility private --body "$(op read 'op://dev/analytics/key')"

Frequently Asked Questions

Why is my Actions secret not available in Codespaces?

Actions and Codespaces have separate secret stores. Create the secret again with gh secret set NAME --app codespaces at the repository, organisation or user level, then restart the codespace.

Do I need to rebuild a codespace after adding a secret?

No. Secrets are injected when the codespace starts, so stopping and starting it is enough. A rebuild also works but takes longer.

Should developers use their own credentials or shared ones?

Use personal (user-level) secrets for anything tied to an identity — cloud CLI access, personal API keys. Use repository secrets for shared, low-privilege development credentials such as a read-only package token or test-mode keys.

How do we stop people choosing large machines?

Create an organisation Codespaces policy that restricts machine types for the repository, and set hostRequirements in devcontainer.json so the default choice is already the right size.