Skip to content

Latest commit

 

History

6 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

model-budget-action

CI License: MIT

Fail CI when a model weight, bundle, or packaged dataset grows past a size you declared on purpose, before it reaches production.

Why this exists

In July 2026 a deploy on my own site quietly broke for twelve days. A 3.6 MB token library had slipped into the server bundle, pushing the Cloudflare Worker past its 3 MiB gzip limit on the free plan. Nothing crashed loudly: the build kept succeeding, the deploy command kept exiting zero, and production just kept serving the last version that fit under the limit. I only found out because I went looking for something unrelated. The full account is in the post-mortem.

The bug was never really about tokenizers or Cloudflare specifically. It was that nothing in CI knew a size limit existed, so nothing could tell me I had crossed it. This action is that missing check, generalized: declare a budget for any file or glob pattern, model weights, JS bundles, packaged datasets, serverless handlers, and CI fails with a clear table the moment something exceeds it, instead of two weeks later.

Quickstart

- uses: actions/checkout@v4
- uses: jmweb-org/model-budget-action@v1
  with:
    config: .github/model-budgets.json
[
  { "pattern": "models/*.onnx", "max-size": "50MB", "gzip": false },
  { "pattern": ".open-next/**/handler.mjs", "max-size": "2.5MB", "gzip": true }
]

Inputs

Input Default Meaning
config .github/model-budgets.json Path to the budgets file (JSON or YAML), relative to the repository root.
fail-on-missing false Fail the run if a declared pattern matches zero files, instead of skipping it.
warn-at 85 Percentage of a budget at which a file that still passes gets a ::warning:: annotation.

Outputs

Output Meaning
report JSON: every checked file with its size, budget, margin, and status, plus a summary count. Use it in a later step to post a PR comment, push a metric, or whatever else you need.

Budget file

A list of entries. Each one needs pattern and max-size; gzip is optional and defaults to false.

Field Meaning
pattern Glob relative to the repository root. Supports * (within one path segment), ** (across segments, zero or more), and ? (one character). No brace expansion, no character classes.
max-size A number of bytes, or a number followed by KB, MB, or GB. Sizes are binary: 1 KB = 1024 B, the same convention ls -lh and the platform limits this action targets both use.
gzip If true, the file is gzip-compressed in memory before measuring, and the budget applies to that compressed size. Use this for anything a server or CDN ships gzipped, since that is the size that actually counts against a platform limit.

JSON works out of the box. YAML is supported through a small hand-written parser that covers exactly the shape above, a flat list of scalar key/value maps, not a general YAML parser. Reach for JSON if you need anything more nested.

What actually happens

For every file a pattern matches, the action compares its size (raw or gzipped, per the gzip flag) against max-size:

  • over budget: the run fails, an ::error:: is attached to the file, and it shows up in the job summary table.
  • at or above warn-at% of the budget, still under it: the run passes, but a ::warning:: is attached so it shows up as an annotation on the pull request.
  • comfortably under: nothing extra happens.

Every run writes a markdown table to the job summary (GITHUB_STEP_SUMMARY) with one row per checked file: name, size, budget, margin, and status, so you don't have to open the raw logs to see what happened.

A pattern that matches no files is reported but does not fail the run by default; set fail-on-missing: true if a missing artifact is itself a problem worth catching (for example, a build step that should always produce a file this pattern expects).

Real example

The budget that would have caught the incident above:

[
  { "pattern": ".open-next/server-functions/default/handler.mjs", "max-size": "2.5MB", "gzip": true }
]

2.5 MB gzip leaves headroom under Cloudflare's 3 MiB free-plan ceiling for the rest of what the Worker bundle ships. A CI run against the commit that broke the deploy would have failed on this line instead of shipping it.

Local development

$ node --test

No install step, the action is plain Node built-ins plus bash, nothing to npm install before it runs, so it starts fast as a CI step.

License

MIT. See LICENSE.

About

Fail CI when a model weight, bundle, or packaged dataset grows past a declared size budget.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages