Skip to content

ci: gate undocumented POST routes in the proxy OpenAPI document - #45843

Merged
kerrylu-berri merged 8 commits into
mainfrom
litellm_openapi_docs_gate
Oct 10, 2026
Merged

kerrylu-berri merged 8 commits into
mainfrom
litellm_openapi_docs_gate

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

How it solves it:

  • scripts/check_openapi_docs.py walks app.openapi() and reports every POST operation missing one of the three (OA001 body, OA002 description, OA003 200 schema)
  • scripts/openapi_docs_gate.py counts those per rule at HEAD and at the merge-base, same ratchet as the other four gates, so a new undocumented route fails and existing ones may only shrink
  • Wired into make lint (so make check), the test-linting.yml python job and the base-counts publisher

Targets main; #45838 (the documented decision routes the gate was written against) is merged

Also drops the unused TypeIs import in litellm/proxy/_types.py that was failing ruff on main and blocking this PR's lint job

User Flow

Before: an engineer adds a raw-body POST route and nobody finds out it is undocumented until a user opens Swagger

  1. They add @router.post("/v1/new_thing") with async def new_thing(request: Request) and open a PR
  2. make check and the lint CI job pass
  3. A user opens https://litellm-domain/ and sees POST /v1/new_thing with no description, no request body and 200 Successful Response with no schema

After: the same PR fails lint and names the route and what it is missing

  1. They add the same route and open a PR
  2. make check and the lint CI job fail with OA001 ... POST /v1/new_thing, OA002 ..., OA003 ... and a one-paragraph hint on how to document it
  3. They add a request model, a docstring and response_model (or responses={200: ...}), the gate passes, and Swagger shows the full operation

Pre-Submission checklist

  • I have added meaningful tests
  • The handful of test files covering my change pass locally, e.g. uv run pytest tests/unit/<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
  • My PR passes all required CI/CD checks (e.g., lint, schema.d.ts sync check, etc.)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to re-request a review after pushing changes)

Screenshots / Proof of Fix

Probe route appended to litellm/proxy/proxy_server.py for both runs, then reverted:

@app.post("/v1/gate_probe", dependencies=[Depends(user_api_key_auth)])
async def gate_probe(request: Request):
    return await request.body()

Before (cbd81cc)

Undocumented route added

  1. ls scripts/openapi_docs_gate.py
  2. ls: cannot access 'scripts/openapi_docs_gate.py': No such file or directory, nothing in make lint looks at the OpenAPI document, so the probe route passes lint

After (d27236e)

Clean tree

  1. uv run --no-sync python scripts/openapi_docs_gate.py --base litellm_decisions_swagger_docs
  2. OK: no OA rule grew past its merge-base count (base litellm_decisions_swagger_docs) (35 s wall clock for the HEAD and merge-base measurements together)

Undocumented route added

  1. uv run --no-sync python scripts/openapi_docs_gate.py --base litellm_decisions_swagger_docs
  2. Output, exit 1:
FAIL: OA-rule totals grew past their merge-base count (base litellm_decisions_swagger_docs):
  OA001: total 183 over ceiling 182 (this change added 1)
    POST /v1/gate_probe
  OA002: total 46 over ceiling 45 (this change added 1)
    POST /v1/gate_probe
  OA003: total 235 over ceiling 234 (this change added 1)
    POST /v1/gate_probe
Give every new POST route a request body schema (a typed body parameter, or `openapi_extra={'requestBody': inline_request_body(Model, example)}` when the handler reads `request.body()`), a docstring, and a 200 schema (`response_model=` or `responses={200: {'model': ...}}`), or document an equal number of existing routes; the ceiling is the merge-base count. Run `python scripts/check_openapi_docs.py` to see every finding.

Full finding list

  1. uv run --no-sync python scripts/check_openapi_docs.py | cut -d' ' -f3 | sort | uniq -c
  2. 182 OA001, 45 OA002, 234 OA003 across 352 POST operations, the baseline the ratchet starts from

Type

🚄 Infrastructure

Caveats (if any)

Medium

  • 267 of 352 POST operations are already undocumented, the gate only stops that number growing

Low

  • The gate imports the proxy app, about 15 s per measurement, cached per merge-base like the other gates
  • The lint job installs the proxy extra for this step only, the other gates keep the smaller install
  • Only POST is gated, GET and DELETE operations have no request body to miss
  • The failure message names a route only when its path literal appears in lines the diff added

Final Attestation

  • The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR

Link to Devin session: https://app.devin.ai/sessions/6fb807b73569493f85e18ebd83dfbf6f
Open in Devin Desktop: https://app.devin.ai/desktop/session/6fb807b73569493f85e18ebd83dfbf6f?variant=devin
Requested by: @kerry-berri

kerry and others added 3 commits October 10, 2026 19:02
Add summaries, descriptions, inlined request body schemas with examples and typed 200 responses to the
decision routes and their aliases, so the OpenAPI document and the lazy snapshot show both request formats

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.


kerry seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

@greptile-apps

greptile-apps Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 2/5

[High impact] Adds a new lint gate that runs during CI and blocks merges.

Fix the missing proxy install and the checks that reject valid documented routes before merging.

Findings

  1. P1 Clean runners cannot run gate ▶
  2. P1 Documented uploads fail lint ▶
  3. P1 Documented creation routes fail lint ▶

Summary

Adds a count-based gate for POST request schemas, descriptions, and response schemas.

  • Lint now catches growing gaps in POST route docs.

devin-ai-integration[bot] explicitly notes that route names are only reported when their path literals appear on added lines. That limitation was not reported again.

Reviews (1) · Last reviewed commit: "ci: gate undocumented POST routes in the..." · Reviewed by Greptile

- name: Install the proxy
if: matrix.checker == 'openapi-docs'
run: |
uv sync --frozen --group proxy-dev --group e2e-dev

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Clean runners cannot run gate

The new gate imports the proxy, but this install does not select --extra proxy. proxy_server requires apscheduler, which uv.lock includes only through that extra. On a clean runner, the checker fails during import before comparing any counts.

Add the proxy extra to this install and the Python job's install in test-linting.yml. Ensure the local gate target installs it too.


def _json_schema(container: object) -> object:
content: Final = _as_mapping(_as_mapping(container).get("content"))
return _as_mapping(content.get("application/json")).get("schema")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Documented uploads fail lint

_json_schema only accepts application/json, so OA001 rejects a fully documented multipart or form request. The proxy already uses this pattern in create_file, with UploadFile = File(...) and Form(...) parameters. Adding another documented upload route increases the count and fails lint even though Swagger shows its request schema.

Check schemas under the supported content types rather than requiring JSON.

Comment on lines +44 to +45
if not _json_schema(_as_mapping(operation.get("responses")).get("200")):
yield "OA003"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Documented creation routes fail lint

OA003 requires a 200 schema even when a POST documents a different success status. For example, create_user has a typed request, a description, and response_model=SCIMUser with status_code=201. A new route following that pattern still increases OA003 and fails lint.

Accept the route's documented success response instead of requiring a response it never returns.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@codecov

codecov Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Base automatically changed from litellm_decisions_swagger_docs to main October 10, 2026 21:05
@devin-ai-integration
devin-ai-integration Bot requested a review from a team October 10, 2026 21:05
kerry and others added 2 commits October 10, 2026 21:06
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@codspeed

codspeed Bot commented Oct 10, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

⚠️ 1 benchmark spent significant time in system calls

System calls cannot be consistently instrumented, so they are not included in the measure, which understates the real cost. Please switch to the Walltime instrument to accurately measure system calls.

Measurement and system calls

✅ 31 untouched benchmarks


Comparing litellm_openapi_docs_gate (f4aaa32) with main (a1e5c90)1

Open in CodSpeed

Footnotes

  1. No successful run was found on main (988cc7d) during the generation of this report, so a1e5c90 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report. ↩

@kerrylu-berri
kerrylu-berri enabled auto-merge (squash) October 10, 2026 21:47
@kerrylu-berri
kerrylu-berri merged commit bea0d5c into main Oct 10, 2026
88 of 89 checks passed
@kerrylu-berri
kerrylu-berri deleted the litellm_openapi_docs_gate branch October 10, 2026 21:51

This branch is waiting to be deployed

1 waiting deployment
e2e-changed — f4aaa320 Waiting Oct 10, 2026 by devin-ai-integration[bot] via oauth #4469
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants