← FinOpsAid blog

What are we paying for dev environments nobody is in? The Codespaces cost nobody owns
Codespaces bills compute while it runs and storage while it exists, so stopping isn't deleting. Where idle and oversized environments hide, and how to stop it.
Codespaces is the GitHub surface that most reliably surprises people, and the reason is a mismatch between how it feels and how it bills. It feels like a browser tab: you close it and it's gone. It bills like infrastructure: the machine exists until something stops it, and the disk exists until someone deletes it.
So the environments costing you money are, almost by definition, the ones nobody is thinking about. Nobody audits a dev environment they've forgotten they created, and the people best placed to notice — the developers who own them — never see a bill.
Two meters, and they don't stop together
Codespaces charges for compute by the core-hour while an instance is running, and for storage by the gigabyte-month while it exists at all. Those are separate meters with separate stop conditions, which is the single most useful thing to internalise about the product.
Stopping a Codespace halts the compute meter. It does not halt the storage meter. A stopped environment is cheap, not free, and an organization with three hundred stopped-but-retained Codespaces is paying a standing monthly charge for disks attached to work that finished months ago. Deleting is what stops the second meter, and deletion is the action nobody takes, because it feels destructive and nothing prompts it.
That asymmetry explains most Codespaces bills. The compute side is usually fine — idle timeouts do their job. The storage side accumulates silently, because the natural end of a task is closing a tab, and closing a tab deletes nothing.
The teal bar is the one nobody looks at. Idle timeouts handle the amber meter automatically; nothing handles the teal meter unless you set a retention period, because the natural end of a task produces no deletion.
How idle happens even with a timeout configured
The default idle timeout stops an inactive Codespace after a period, and most teams reasonably assume that solves the runaway-compute problem. It mostly does, with a few exceptions worth knowing.
A timeout measures inactivity, and plenty of things count as activity that aren't a human working. A file watcher, a dev server rebuilding on a poll, a long-running test process, a language server indexing a large repo, an agent or automation connected to the workspace — any of these can keep an environment technically busy while nobody has looked at it since Thursday. The environment isn't idle by the timeout's definition, so it never stops, and the core-hours accrue at full rate.
The tell is a Codespace with high running hours and no recent commits or pushes from its owner. Activity in the machine, no activity in the repository. That pairing is what distinguishes a genuinely busy environment from one that's merely awake.
Oversized is the quieter half
Idle gets the attention because it's dramatic. Oversizing costs more in aggregate and never announces itself.
Codespaces machine types scale by core count and price scales with them, so choosing a larger type multiplies every hour that environment ever runs. The problem is that the choice is made once, usually at the moment of most uncertainty — someone hits a slow build, bumps to a bigger machine, and the setting persists for the life of the environment and every environment cloned from that configuration afterwards.
Sizing deserves the same treatment as any other capacity decision: look at what the workload actually used rather than what it was provisioned for. An environment consistently running at a fraction of its allocated cores is paying a multiple for headroom it never touches. The saving is real, it's arithmetic rather than a judgement call, and it compounds because it applies to every future hour.
Attribution is what makes it fixable
Codespaces cost has an unusual property among GitHub surfaces: it attributes cleanly. Every instance belongs to a named user and a specific repository. There's no allocation rule to argue about, which puts it in the certain bucket of savings you can actually defend rather than the modeled one.
That makes it a good place to start a cost programme. The finding is unambiguous, the owner is known, and the fix takes seconds. It's also the surface where a nudge works best — a developer shown a list of their own forgotten environments will clean them up without anyone escalating, which is not true of most cost conversations.
The two settings that stop it recurring
Reclaiming today's waste is an afternoon. Stopping it coming back is two org-level policies, and they're the actual deliverable.
Retention. Set a maximum retention period — an organization-level policy rather than a per-developer habit — so unused Codespaces are deleted rather than stopped-and-kept indefinitely. This is the setting that addresses the storage meter, and it's the one most orgs have never touched. Pick a period generous enough not to destroy work in progress and short enough that abandonment self-corrects.
Machine-type constraints. Restrict the machine types available per repository, so the largest sizes require a deliberate choice rather than being the convenient default. Combined with a devcontainer.json that specifies a sensible size, this stops oversizing propagating through cloned configurations.
Both are prevention rather than cleanup, which is what separates a cost programme from a cost cleanup.
A Codespaces review worth repeating
- List by running hours, then check for repo activity. Busy machine plus quiet repository is the idle signature.
- Count stopped-but-retained environments. This is the standing storage charge nobody has looked at.
- Compare provisioned cores against used cores. Oversizing is arithmetic, not opinion.
- Send findings to owners, not to a channel. Every Codespace has a name attached; use it.
- Set retention and machine-type policy once. Cleanup without these is a task you will repeat every quarter.
- Re-check after onboarding waves. New joiners clone templates, and templates carry whatever size was set in them.
FinOpsAid surfaces both halves: Codespaces inventories every instance with idle and oversized detection and a modeled monthly cost per environment — flagged as modeled, because the rate card is applied to observed usage rather than read off a per-instance invoice line — with the owning user and repository attached to each finding.
Frequently asked questions
Does stopping a Codespace stop the charges?
It stops the compute meter but not the storage one. Codespaces bills compute per core-hour only while running, and storage per gigabyte-month for as long as the environment exists. Only deleting it stops both, and closing the browser tab does neither.
Why do Codespaces stay idle even with an idle timeout configured?
Because a timeout measures inactivity, and plenty of things count as activity that are not a human working — a file watcher, a polling dev server, a long-running test process, a language server indexing, or a connected agent. The environment never goes idle by the timeout's definition, so the core-hours keep accruing.
What stops Codespaces waste coming back?
Two organization-level policies rather than another cleanup: a maximum retention period, so unused environments are deleted rather than stopped and kept indefinitely, and machine-type constraints per repository, so the largest sizes require a deliberate choice. Without those, the same audit is due again next quarter.
Want to see which dev environments are billing you for nothing? Connect your GitHub org — read-only and free during beta — or explore the demo dashboard first.