feat(ci): add the cost map guard check - #39835
Conversation
Replace test-model-map.yml with a pull_request_target guard that validates the cost map, its backup, and its generated schema on every PR, and additionally enforces the sync bot contract on litellm_cost_map_sync_* branches: only the three cost map files may change, no model or field is removed, and the special root keys stay untouched.
Greptile SummaryThis PR replaces the existing model-map validation workflow with a base-branch cost-map guard
Confidence Score: 5/5The PR appears safe to merge, with no concrete correctness, security, or repository-rule violations identified The workflow executes trusted base-branch code with read-only permissions, treats pull request revisions as data, and the guard and tests cover the stated synchronization and bot-contract requirements
|
| Filename | Overview |
|---|---|
| .github/workflows/cost-map-guard.yml | Adds the read-only base-branch workflow that fetches pull request revisions and runs the guard |
| ci_cd/cost_map_guard.py | Implements synchronization checks and the stricter sync-bot change contract |
| tests/test_litellm/test_cost_map_guard.py | Covers file drift, schema validation, bot restrictions, and revision-based command execution |
| .github/workflows/test-model-map.yml | Removes the narrower validation workflow superseded by the new guard |
Reviews (1): Last reviewed commit: "feat(ci): add the cost map guard check" | Re-trigger Greptile
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 61bed79. Configure here.
e0c5bb9
into
litellm_internal_staging
TLDR
Problem this solves:
How it solves it:
cost-map-guardcheck runs from the default branch on every PRlitellm_cost_map_sync_*branches it also enforces the bot contractsample_specandfallback_generalizationsstay untouchedtest-model-map.yml, which the new check fully coversUser Flow
Before: a cost map PR that drops a model from the root file but not the backup passes every check and merges, so different proxies price the same model differently
openrouter/openai/gpt-3.5-turbo-instructfrommodel_prices_and_context_window.jsonand leaveslitellm/model_prices_and_context_window_backup.jsonas it wasvalidate-model-prices-jsoncheck is green: the file still parses and the schema is still in syncPOST https://litellm-domain/v1/chat/completionsfor that model with nox-litellm-response-costheader, while a proxy running on the bundled backup still prices it from the old entryAfter: the same PR is blocked with the exact drift named, and only merges once both copies agree
openrouter/openai/gpt-3.5-turbo-instructfrommodel_prices_and_context_window.jsonand leaveslitellm/model_prices_and_context_window_backup.jsonas it wascost-map-guardcheck is red withlitellm/model_prices_and_context_window_backup.json differs from model_prices_and_context_window.json; copy the root file over itDesign
This is the first of two PRs for the cost map sync bot. The bot (next PR) opens PRs on
litellm_cost_map_sync_*branches from the live OpenRouter and Vercel AI Gateway catalogs and merges them once this check is green, so the check has to come from a branch the bot cannot rewrite. That is why it usespull_request_target: GitHub runs the workflow and checks out the code from the default branch, and the PR's files are only ever read as data byci_cd/cost_map_guard.py(base and head are fetched by SHA and read withgit show, nothing from the PR is executed). The job keepscontents: readand does not persist credentialsHuman PRs get the file checks only: the root map parses to an object, the backup is byte-identical to it, the checked-in schema equals the one
ci_cd/generate_model_prices_schema.pywould generate, and every entry validates against that schema. Bot branches additionally get the contract above, so a catalog glitch or a bad transform can add or reprice models but can never delete one, drop a field a customer relies on, touch code, or edit the special root keysRelevant issues
Linear ticket
Part of LIT-6995 (the sync bot PR that follows resolves it)
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
uv run pytest tests/test_litellm/<your_test_file>.py -v. Leave the suites (make test-unit-*,make test-unit) to CI: it finishes in ~15 minutes where a laptop takes an hour or more@greptileaito re-request a review after pushing changes)Delays in PR merge?
If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).
Screenshots / Proof of Fix
Three throwaway branches off this PR's tip play the PRs the check has to judge. Each was pushed and opened as a PR into this branch (#39830, #39831, #39832, closed again):
pull_request_targetonly runs the workflow from the default branch, so the live check cannot fire on those and the runs below are the exact command the job runs, executed locally against the pushed revisions. The live run gets captured on the same branches once this lands on the default branch, before the bot PR mergeslitellm_cost_map_demo_human(acc5eaf): removesopenrouter/openai/gpt-3.5-turbo-instructfrom the root map onlylitellm_cost_map_sync_demo_ok(e5f43ba): a bot branch that addsopenrouter/inception/mercury-2.5-previewand repricesopenrouter/deepseek/deepseek-v4-pro-0813, with backup and schema in synclitellm_cost_map_sync_demo_bad(9bed767): a bot branch that removes a model, dropssupports_reasoningfrom an entry, adds a key tosample_spec, and editsREADME.mdBefore (b3c867c)
Human PR that de-syncs the backup
git worktree add /tmp/demo litellm_cost_map_demo_human && cd /tmp/demo && jq empty model_prices_and_context_window.json; echo "jq exit $?"jq exit 0python ci_cd/generate_model_prices_schema.py --check; echo "schema check exit $?"model_prices_and_context_window.schema.json is in sync and model_prices_and_context_window.json validates against itthenschema check exit 0: both steps oftest-model-map.ymlpass and the drift mergesBot PR within the contract
litellm_cost_map_sync_demo_okBot PR that breaks the contract
litellm_cost_map_sync_demo_badsample_specedit, and theREADME.mdedit all go unnoticedAfter (61bed79)
Human PR that de-syncs the backup
python ci_cd/cost_map_guard.py --base litellm_cost_map_guard --head litellm_cost_map_demo_human --head-ref litellm_cost_map_demo_human; echo "exit $?"Bot PR within the contract
python ci_cd/cost_map_guard.py --base litellm_cost_map_guard --head litellm_cost_map_sync_demo_ok --head-ref litellm_cost_map_sync_demo_ok; echo "exit $?"Bot PR that breaks the contract
python ci_cd/cost_map_guard.py --base litellm_cost_map_guard --head litellm_cost_map_sync_demo_bad --head-ref litellm_cost_map_sync_demo_bad; echo "exit $?"Type
🚄 Infrastructure
✅ Test
Caveats (if any)
Medium
pull_request_targetreads the workflow from the default branchcost-map-guardruncost-map-guardreplacesvalidate-model-prices-jsonwherever a ruleset or reviewer expects the old nameLow
litellm_cost_map_demo*branches are throwaway, deleted after the live demodeclared_reasoning_effortsdocstring inlitellm/router_utils/reasoning_effort_capability.pystill namesvalidate-model-prices-jsoncost-map-guardproxy-infra / Run tests (Python 3.10)is red at 61bed79, inherited from stagingtest_http_parsing_utils.py::TestNumericFormFields::test_qualifiers_and_optionality_are_unwrappedfails the same way on every branch cut before fix(proxy): strip every TypedDict qualifier before numeric form-field detection #39780 (merged to staging 2026-09-05 01:36Z; staging's Unit Tests run 33956401452 is green since); the required context is the aggregateproxy-infra / Run tests, which is green, and the merge result carries the fixFinal Attestation
Note
Medium Risk
Uses
pull_request_target(documented as base-branch-only execution with read-only PR data), which is security-sensitive CI surface area; changes gate merges on pricing map consistency and bot behavior.Overview
Replaces the
validate-model-prices-jsonworkflow (test-model-map.yml,jq+ schema--check) with acost-map-guardjob that runs onpull_request_targetso the check always executes from the default branch while PR map files are read viagit showat merge base and head.The new
ci_cd/cost_map_guard.pyenforces, on every PR: valid rootmodel_prices_and_context_window.json, byte-identicallitellm/model_prices_and_context_window_backup.json, checked-in schema matchinggenerate_model_prices_schema.py, and schema validation of entries. Branches namedlitellm_cost_map_sync_*additionally get a bot contract: only the three map files may change, no model/field removals, andsample_spec/fallback_generalizationsmust be unchanged.Adds
tests/test_litellm/test_cost_map_guard.pycovering file drift, validation errors, bot vs human rules, and CLI/git integration.Reviewed by Cursor Bugbot for commit 61bed79. Bugbot is set up for automated code reviews on this repo. Configure here.