Skip to main content

Setting up GitHub Copilot budget policies without GitHub Enterprise Cloud

If you search for "GitHub Copilot budget policy", most posts point you to cost centers: assign a budget to a cost center, map the cost center to a team or business unit, done. Clean story, except for one detail nobody mentions upfront — cost centers require a GitHub Enterprise Cloud account.

That's not the same thing as being on the Copilot Enterprise plan; user-level budgets already work fine on Copilot Business. It's about the account type; a standalone Organization account doesn't have cost centers, and neither does GitHub Enterprise Server if you're self-hosted. If you're not on Enterprise Cloud, cost centers are off the table.

We ran into exactly this. No Enterprise Cloud account, no cost centers, but still a real need to keep Copilot spend under control across a growing user base.

Remark: if you do have GitHub Enterprise Cloud, cost centers are probably still the better fit. This post is for the rest of us.

The starting point

We have two distinct groups of Copilot users:

  • A large group of daily users: they use Copilot as part of their normal workflow, fairly predictable usage.
  • A smaller group of power users: heavier usage, more experimentation, agent-style workloads that burn through budget faster.

A single flat budget per user doesn't work here: set it low enough to control the power users and the daily users are needlessly constrained; set it high enough for the daily users and the power users blow through any real cost control.

The approach: layered per-user budgets

Without cost centers, the building block you're left with is the per-user budget. So instead of trying to simulate teams or business units, we built the policy around the two groups we actually have:

  • A universal budget that applies to every Copilot user in the organization.
  • An additional per-user budget assigned specifically to power users, on top of the universal one.

The budget itself stays pooled at the enterprise level. We're not carving out separate pots of money per team. What changes is the ceiling: no individual user, regardless of group, can exceed their assigned budget. A daily user is capped at the universal amount. A power user is capped at the universal amount plus their extra allocation.

Remark: the pooling matters. It means unused budget from daily users isn't wasted. It stays available at the enterprise level while still giving you a hard per-user ceiling to prevent runaway individual spend.

Configuring it step by step

This is done under "Budgets and alerts" on your organization or enterprise billing page. Concretely:

  • Go to Budgets and alerts. Navigate to your organization or enterprise account, open Billing & Licensing, and click Budgets and alerts.


  • Create the universal budget. Click New budget → set "Budget Type" to AI credits budget → under "Budget scope" choose UsersUniversal. This creates a per-user budget that applies to every Copilot-licensed user by default.

 


  • Set the universal amount above the per-license value. GitHub explicitly recommends setting it above $19 (Copilot Business) or $39 (Copilot Enterprise) per seat, so pooling still works and lighter users' unused budget is available to heavier ones instead of everyone being hard-capped at exactly their own license value.

  • Identify your power users. Use the AI usage dashboard to see who's consistently bumping against the universal budget — agent-heavy workflows and large-codebase work are usually the pattern.

  • Create an individual override per power user. Click New budget → set "Budget Type" to AI credits budget → under "Budget scope" choose UsersIndividual user. Select the specific person, you want to give a different budget. An individual user-level budget overrides the universal one for that user only — nobody else is affected.

  • Set an enterprise spending limit as a backstop. This caps total metered charges once the shared pool is exhausted; it doesn't affect how individual users draw from the pool, it's the ceiling on the pool itself.
  • Enable "Stop usage when budget limit is reached" on the spending limit. User-level budgets always hard-stop automatically, but spending limits don't unless you turn this on — without it, you just get an email while charges keep accruing.


  • Revisit monthly. Watch for users getting blocked early (universal budget too tight, or a new power user you haven't flagged yet) versus the pool lasting the whole cycle with no blocks (well-tuned).

Tip: if you don't have usage data yet, don't overthink the starting numbers — set something reasonable for the universal budget and tune it after your first billing cycle using the dashboard.

Why this works for us

  • It's simple to reason about: two tiers, two numbers.
  • It doesn't require an Enterprise Cloud account to get some form of cost governance.
  • It scales the right way: as we identify more power users, we just assign them the extra per-user budget instead of restructuring the whole policy.

It's not as granular as cost-center-based reporting per team. If you need to answer "how much did Team X spend this quarter", this approach won't get you there directly. For us, controlling total spend and preventing individual overuse mattered more than per-team attribution, so the trade-off was worth it.

That's it. 2 budget tiers, pooled at the top, capped per user.

More information

Popular posts from this blog

Podman– Command execution failed with exit code 125

After updating WSL on one of the developer machines, Podman failed to work. When we took a look through Podman Desktop, we noticed that Podman had stopped running and returned the following error message: Error: Command execution failed with exit code 125 Here are the steps we tried to fix the issue: We started by running podman info to get some extra details on what could be wrong: >podman info OS: windows/amd64 provider: wsl version: 5.3.1 Cannot connect to Podman. Please verify your connection to the Linux system using `podman system connection list`, or try `podman machine init` and `podman machine start` to manage a new Linux VM Error: unable to connect to Podman socket: failed to connect: dial tcp 127.0.0.1:2655: connectex: No connection could be made because the target machine actively refused it. That makes sense as the podman VM was not running. Let’s check the VM: >podman machine list NAME         ...

Cache stampede: when our cache turned against us

While investigating some performance issues, we ran into an ASP.NET Core API that cached a fairly expensive aggregation query for 60 seconds. Under normal load, that was fine: one request rebuilds the cache, everyone else reads from it. Under peak load, dozens of requests would arrive in that same expiry window, all see a cache miss, and all fire the same expensive query in parallel. The database didn't like that. That was the moment when our caching layer stopped helping and started hurting. A burst of requests comes in at the same time, all miss the cache, and all go hammer the database or the downstream API at once. That's a cache stampede . The cache was supposed to protect our backend, and for a few hundred milliseconds it did the opposite. Why this happens IMemoryCache.GetOrCreate (and its async sibling) looks like it protects you, but it doesn't add any locking on its own. Look at the naive version: public async Task<Report> GetReportAsync(string key) ...

The role of ActivitySource in OpenTelemetry for .NET

While doing some pair programming to integrate OpenTelemetry tracing to a .NET application, we had a discussion on how to use the ActivitySource . It looks simple. You new one up, give it a name, start an activity, done. The discussion started when we added a second ActivitySource with the exact same name in a different class. This made us wonder: "Are we duplicating traces now? Is this a memory leak? Do we need a singleton?" So we decided to dig deeper. This post is what we learned… What ActivitySource actually is ActivitySource is part of System.Diagnostics , not part of the OpenTelemetry NuGet packages. Microsoft built tracing primitives directly into the BCL, and OpenTelemetry's .NET SDK simply listens to them. This is why you can add distributed tracing to a library without taking a dependency on OpenTelemetry at all. An ActivitySource is a factory for Activity objects, and an Activity is .NET's name for what OpenTelemetry calls a span.(don’t ask m...