Skip to content

ci: gate repository root against unbounded file growth - #32529

Open
devin-ai-integration[bot] wants to merge 2 commits into
litellm_internal_stagingfrom
litellm_ci_root_file_count_gate
Open

ci: gate repository root against unbounded file growth#32529
devin-ai-integration[bot] wants to merge 2 commits into
litellm_internal_stagingfrom
litellm_ci_root_file_count_gate

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Relevant issues

Follows the #pr-review discussion about random files landing in the repo root (the qa_sticky_session.sh example from #31688 is removed here)

Linear ticket

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • My PR passes all CI/CD checks (e.g., lint, format, unit tests)
  • 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

Captured at commit cd1fe15f93. After removing qa_sticky_session.sh the tree has 43 tracked root files, so with the limit at 43 the gate passes; dropping the limit by one simulates a PR adding a 44th root file and it fails with an actionable message plus the full listing

$ python scripts/check_root_file_count.py 43
Root file count OK: 43 tracked file(s) <= limit 43
$ echo $?
0

$ python scripts/check_root_file_count.py 42
::error::Too many files in the repository root: 43 tracked file(s), limit is 42
  .dockerignore
  .env.example
  ...
  uv.lock
Move new files into an appropriate subdirectory instead of the repo root. If a new root file is genuinely required, raise MAX_ROOT_FILES in .github/workflows/check-root-file-count.yml in the same PR so the bump is reviewed
$ echo $?
1

Type

🚄 Infrastructure

Changes

I looked at how other projects keep their root tidy before building this. There is no widely adopted off-the-shelf GitHub Action for "cap files in the repo root"; projects that care either maintain a hand-rolled allowlist script or, more commonly, enforce a simple count in CI (the kubernetes/kubernetes verify-* shell hacks are the canonical example of the latter). Per Ishaan's steer in the thread I went with the count approach since it is the least fussy and directly answers Yuneng's "how do we explicitly allow a new file" question

scripts/check_root_file_count.py lists the tracked files with git ls-files, keeps only those with no / in their path (repo root), and exits non-zero when the count exceeds the limit passed on argv. The new .github/workflows/check-root-file-count.yml runs it on PRs into the litellm_* branches (and the merge queue) with MAX_ROOT_FILES set to the current count

This PR also removes qa_sticky_session.sh, the stray root-level QA script from #31688 that kicked off the thread; it is a standalone manual curl script that nothing imports, so deleting it drops the count to 43 and lets MAX_ROOT_FILES start at 43 rather than 44

Pinning the limit at exactly today's count is deliberate: the tree passes as-is, no further cleanup is forced, and any PR that adds a net-new root file goes red. To land a genuinely needed root file you bump MAX_ROOT_FILES in the same PR, which turns "allow a new root file" into a one-line, reviewable diff rather than an invisible drift. A reviewer seeing that bump can push back or ask for the file to move into a subdirectory instead

A count-only gate does not pin which files are allowed, so swapping one root file for another keeps the tree green; that is an accepted trade for the simplicity Ishaan asked for. If we later want per-file control the same script can grow an allowlist without changing the workflow contract

tests/test_litellm/test_check_root_file_count.py pins the behavior that matters: that only top-level paths are counted (nested paths are ignored), that count-equal-to-limit passes while over-limit fails, and that the failure message is actionable and lists offenders in sorted order

Link to Devin session: https://app.devin.ai/sessions/ca9e2a7219ce49faa2c7bdcbff0b36db
Requested by: @ishaan-berri

Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>
@devin-ai-integration
devin-ai-integration Bot requested a review from a team July 8, 2026 18:54
@ishaan-berri ishaan-berri self-assigned this Jul 8, 2026
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • 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.
You have signed the CLA already but the status is still pending? Let us recheck it.

@greptile-apps

greptile-apps Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces a lightweight CI gate that prevents unbounded growth of files in the repository root by counting tracked root-level files with git ls-files and failing the build when the count exceeds MAX_ROOT_FILES (currently set to 44, matching the existing tree).

  • scripts/check_root_file_count.py lists tracked files, filters to those with no / in their path, and exits non-zero with a GitHub-annotated error and a full listing when the limit is exceeded.
  • .github/workflows/check-root-file-count.yml runs the script on PRs targeting the litellm_* family of branches and on merge queue events, with pinned action SHAs and minimal contents: read permissions.
  • tests/test_litellm/test_check_root_file_count.py covers the three key behaviors: root-only filtering, equal-to-limit passing, and over-limit failure with a sorted, actionable listing.

Confidence Score: 4/5

Safe to merge — changes are confined to a new CI workflow, a standalone utility script, and its tests; no production code is touched.

The script and workflow are clean and well-thought-out. Two small robustness gaps exist: main() will crash with a bare IndexError/ValueError traceback if invoked without a valid integer argument, and the test file’s top-level module loading will emit a confusing TypeError at pytest collection time if spec_from_file_location ever returns None. Neither affects the gate’s correctness in CI as currently wired.

Both findings are in scripts/check_root_file_count.py and tests/test_litellm/test_check_root_file_count.py and are confined to argument validation and test import robustness.

Important Files Changed

Filename Overview
.github/workflows/check-root-file-count.yml New workflow gating PRs on repo-root file count; correctly pins action SHAs, sets minimal permissions, and uses a sensible concurrency group.
scripts/check_root_file_count.py Clean, well-structured script; minor robustness gap in main() — missing argument-count and type validation before int(argv[1]).
tests/test_litellm/test_check_root_file_count.py Good coverage of the three key behaviors; module loading at file scope can produce a confusing import error if the path changes.

Reviews (1): Last reviewed commit: "ci: gate repository root against unbound..." | Re-trigger Greptile

Comment on lines +49 to +50
def main(argv: tuple[str, ...]) -> int:
max_root_files = int(argv[1])

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.

P2 The main() function indexes argv[1] without first verifying that a second element exists, and passes the raw string directly to int() without catching ValueError. Invoking the script with no argument or a non-integer value produces a bare Python traceback rather than an actionable message — something a developer debugging the workflow locally would find confusing.

Suggested change
def main(argv: tuple[str, ...]) -> int:
max_root_files = int(argv[1])
def main(argv: tuple[str, ...]) -> int:
if len(argv) < 2:
print("Usage: check_root_file_count.py <max_root_files>", file=sys.stderr)
return 2
try:
max_root_files = int(argv[1])
except ValueError:
print(f"Error: expected an integer argument, got {argv[1]!r}", file=sys.stderr)
return 2

Comment on lines +4 to +7
_MODULE_PATH = Path(__file__).resolve().parents[2] / "scripts" / "check_root_file_count.py"
_spec = importlib.util.spec_from_file_location("check_root_file_count", _MODULE_PATH)
checker = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(checker)

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.

P2 The module is loaded at module scope, so a None return from spec_from_file_location (e.g. if the script path is wrong after a rename) causes module_from_spec(None) to raise TypeError at collection time, making the entire test file fail to import with a confusing traceback. Adding a guard before proceeding keeps the failure message clear.

Suggested change
_MODULE_PATH = Path(__file__).resolve().parents[2] / "scripts" / "check_root_file_count.py"
_spec = importlib.util.spec_from_file_location("check_root_file_count", _MODULE_PATH)
checker = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(checker)
_MODULE_PATH = Path(__file__).resolve().parents[2] / "scripts" / "check_root_file_count.py"
_spec = importlib.util.spec_from_file_location("check_root_file_count", _MODULE_PATH)
if _spec is None or _spec.loader is None:
raise ImportError(f"Could not load module from {_MODULE_PATH}")
checker = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(checker)

@codecov

codecov Bot commented Jul 8, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

…imit to 43

Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>
@codspeed-hq

codspeed-hq Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

⚡ 1 improved benchmark
❌ 2 regressed benchmarks
✅ 27 untouched benchmarks

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
test_completion_simple_message 3.2 ms 4.4 ms -25.75%
test_completion_with_tools 3.2 ms 4.2 ms -24%
test_completion_multi_turn 4.2 ms 3.1 ms +33.72%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing litellm_ci_root_file_count_gate (cd1fe15) with litellm_internal_staging (86a9871)

Open in CodSpeed

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