Release gate
Block a deploy until every string in the commit is translated, with one check from CI, the CLI, MCP or REST.
The release gate answers one question before a deploy: is everything this commit ships translated, and are the translations in the build? It is one read-only op, check_sync, so the CLI, CI, MCP and REST all get the same verdict. npx @meterapp/glossa check exits with status 1 when the answer is no, which is all a CI system needs to stop a deploy.
npx @meterapp/glossa checknpx @meterapp/glossa check --wait 10m --max-missing 0How it works
The check is about the commit being built, not about whatever Glossa has received most recently. The CLI sends the commit's source files (the files in .glossa.json, as push would). Glossa parses them, matches each string to its translation unit the same way push does, and counts, for exactly those strings, which target locales lack a translation. Then it renders every locale file pull would write and returns its SHA-256. The CLI hashes the copies in your tree and compares. Only 64 hex characters per file travel, and one digest covers the whole set. A project with hundreds of files and locales checks in seconds and never downloads a translation.
Hashes are SHA-256 of the file's UTF-8 text with CRLF line endings read as LF, so a Windows checkout matches a Linux one. Compare with missing and approved_only set the way you pull. Otherwise every file differs, by design.
Moving goalposts
On a busy repository, someone is always pushing new strings. A gate that asked whether everything in Glossa is translated would never pass for long, and a deploy would wait on strings it doesn't even contain. Three rules keep the gate fixed to the commit:
- Only this commit's strings count. Strings pushed later, from main or another branch, are not in its source files, so they never block it. Deploying an older commit after main has moved on passes as long as its own strings are translated.
- Strings never pushed fail loudly. If the commit adds strings Glossa hasn't seen, the check reports
unpushed_stringsinstead of quietly passing. Push from the same job, as in the recipes below. - Files are only compared when they can match. If the commit's source differs from the one Glossa has (main pushed a newer one), pull's output has other keys, so locale files are reported
not_comparable, not stale. Coverage of the commit's own strings still gates the release.
What can still move: a translator edits a string after you pulled (the file is stale until you pull again), and the clock while translations are being written. --wait 10m re-checks every 15 seconds (--interval) until the gate passes or time runs out. It doesn't wait on what time can't fix: stale files and unreadable formats fail at once. In AI mode a commit's new strings are usually translated in a minute or two. With human or agent translation, pick thresholds that match how fast your translators work.
Gates
| Failure | Set by | Means |
|---|---|---|
| unpushed_strings | always | A string in this commit's source files was never pushed, so nobody can have translated it. Run push; in AI mode it is translated in a minute or two. |
| missing_translations | max_missing (default 0) | More strings × locales lack a translation than you allow. Count only reviewed or approved ones with require_reviewed. |
| low_coverage | min_coverage (off) | A locale has a lower share of this commit's strings translated than you allow, e.g. 95. |
| stale_files | stale: fail (default), warn, ignore | A locale file in the tree differs from what pull writes now: Glossa has translations the build would not ship. |
| pending_jobs | allow_pending (off) | Translate jobs for the project are still queued or running. --wait sits them out. |
| files_not_ready | always | Glossa can't read one of the project's files yet (a learn_format task is open). |
Your release policy, in the repo
Put the thresholds in .glossa.json under check, so every pipeline and every developer runs the same gate. Flags (--max-missing, --min-coverage, --require-reviewed, --stale, --allow-pending, --locale) override it for one run. --max-missing none turns the count off.
{ "project": "web", "files": [ "locales/en.json" ], "locales": [ "es", "fr", "de" ], "check": { "max_missing": 0, "min_coverage": 100, "require_reviewed": false, "stale": "fail" }}Recipes
| How you ship | Gate |
|---|---|
| Continuous deployment, AI translation | push, pull and check --wait 10m in the deploy job. New strings are translated while the check waits; the deploy goes out fully translated or not at all. |
| Continuous deployment, translators or an agent | Keep max_missing at 0 for the locales you promise and gate the rest with min_coverage, or check them with --locale so one slow language doesn't hold every release. |
| Release trains (weekly, per sprint) | Run check on the release branch only. Freeze the branch, push it, let the queue drain, and the check tells you when the train can leave. |
| Mobile apps with translations in git | Commit pulled files from a bot PR and run check --stale fail in CI, so a store build never ships older translations than Glossa has. |
| Ship now, translate after | max_missing: null and stale: warn turn the gate into a report: the job passes, the summary lists what is missing. |
Translations pulled at build time (nothing to keep in sync in git):
# .github/workflows/deploy.yml (translations pulled at build time)jobs: deploy: runs-on: ubuntu-latest env: GLOSSA_API_KEY: ${{ secrets.GLOSSA_API_KEY }} steps: - uses: actions/checkout@v4 - run: npx @meterapp/glossa push # this commit's strings, if new - run: npx @meterapp/glossa pull # every locale, freshly rendered - run: npx @meterapp/glossa check --wait 10m - run: npm run build && npm run deployTranslations committed to the repo:
# .github/workflows/ci.yml (translations committed to the repo)jobs: translations: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: npx @meterapp/glossa check --stale fail env: GLOSSA_API_KEY: ${{ secrets.GLOSSA_API_KEY }}In GitHub Actions the check also writes a job summary: a table of every locale and the reason a release was blocked, on the run page.
From an agent or any HTTP client
Agents call check_sync over MCP to ask whether the branch they are working on is ready. Without sources it checks the source Glossa last received. Without the CLI, send the source files and, optionally, a hashes map of path to SHA-256 for a server-side stale verdict; ok is the answer:
curl -X POST https://getglossa.dev/api/v1/check_sync \ -H "Authorization: Bearer gls_…" -H "Content-Type: application/json" \ -d "$(jq -n --rawfile en locales/en.json \ '{project: "web", sources: [{path: "locales/en.json", content: $en}], compare_files: false}')" \ | jq -e .okLarge projects can pass compare_files: false to skip rendering and gate on coverage only. The full input and output are in the API reference.