Repository navigation
fix(lint): give the file-size grandfather list a ceiling that only shrinks (#14236) - #14463
Merged
Merged
Conversation
…rinks (#14236) KNOWN_LARGE exempted three files by name with a comment promising they were "under active decomposition". Nothing checked the promise: orchestrator.py grew from the 779 lines noted beside it to 1114, and manager.py and tool_handler.py, which never had a recorded size at all, reached 4068 and 4063 — 6.8x the 600-line limit — with the hook reporting all three clean. Each entry now carries the line count it was last measured at, and the ratchet turns one way only: over the ceiling fails (a grandfathered file may not grow), under it fails asking for the ceiling to be lowered, and reaching the limit fails asking for the entry to be deleted — an entry naming a compliant file exempts nothing while looking authoritative. --audit-ceilings applies the three rules to every entry regardless of what is staged, and reports how many entries it reached, so a matcher that has stopped matching cannot pass by scanning nothing. Wired into code-quality.yml beside the extension-import baseline audit. Decomposing the three files stays out of scope (#5060's campaign); this only stops them growing while it happens.
Contributor
✅ SSOT Configuration Compliance: Passing🎉 No hardcoded values detected that have SSOT config equivalents! |
…4236) The two prints added by the previous commit tripped the #1082 guard on lines this PR touched. Both now go through stdlib logging. stdlib `logging`, not autobot_shared.logging_manager: this is a pre-commit hook running as a bare script on every commit, and the platform logger would drag config loading into that path — the same trade taken in autobot_shared/user_management/password_epoch.py. Line 170's pre-existing print in main() moved too, deliberately rather than silently: leaving it would have left one 176-line file with two output paths for the same findings, and it is the last print in the file. A silent conversion to logging is worse than the print it replaces, so the output path is asserted, not assumed: findings are logged at ERROR so they clear logging's lastResort threshold and stay visible when nothing configured logging at all; configure_logging() attaches a stderr handler at INFO so the clean-run line is not discarded; and five tests run the checker against a violating fixture and capture the emitted record. Mutating the level to debug, dropping the emit, or neutering configure_logging all go red. The no-stdout test parses the AST for a call to the banned name assembled by implicit concatenation, so the fixture cannot trip the lint it enforces.
This was referenced Aug 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Thinking Path
A grandfather list with no ceiling is a ratchet mounted backwards. Entries go
in, nothing takes them out, and the guard quietly stops guarding while
continuing to report green. The defect is the missing ceiling, not the entry
count — so I measured before deciding how hard to push.
Present state.
MAX_LINES = 600. Three entries, measured onDev_new_gui:autobot-backend/orchestrator.py# 779 lines (#5060 — target <800)autobot-backend/chat_workflow/manager.pyautobot-backend/chat_workflow/tool_handler.pyHas the list grown? No.
git log --followover the hook shows the samethree names in all five commits since it landed on 2026-05-23; the only change
was #14235 tightening
endswithto==. So the list never gained an entry —the files inside it grew instead, which is the failure mode a bare-name
exemption cannot see.
orchestrator.pyis 43% past the size it was exemptedat and past its own stated target of <800. The two with no recorded number had
nothing to regress against at all, and grew 6.8x under a passing hook. The
issue quoted 3977 and 4058 yesterday; they are 4068 and 4063 today, so the
drift is live, not historic.
How aggressive. Growth is unbounded and two of three entries were never
measured, so the ceiling is enforced in all three directions rather than only
the obvious one, and the audit is wired into a blocking CI step rather than
left to the staged-files hook.
Do I also shrink the list now? No — explicitly. All three entries stay, at
their current sizes. Splitting a 4000-line module is #5060's campaign, not a
lint fix, and this PR does not reduce any file by a line. The problem this PR
solves is that the exemption had no bound; the files are still 1.9x–6.8x over
the limit after it merges.
What Changed
scripts/check_python_file_size.pyKNOWN_LARGEis nowdict[str, int]— path to the line count it was lastmeasured at, per the issue's proposed shape.
mutation has something to break:
line_count > ceiling-> fail. A grandfathered file may not grow; theexemption freezes the size it was granted for.
line_count < ceiling-> fail, naming the exact number to write back. Anunlowered ceiling re-licenses the lines just removed, which is how a
ratchet stops turning.
line_count <= MAX_LINES-> fail, asking for the entry to be deleted.A list still naming a compliant file exempts nothing while looking
authoritative.
--audit-ceilingsmode applies those rules to every entry regardless ofwhat is staged, so a stale entry cannot sit there after its file shrank,
moved, or was deleted. It reports how many entries it reached and fails when
that is not the expected count.
repo_root()derives from__file__, not the cwd.loggingrather than
autobot_shared.logging_manager: this is a pre-commit hook runas a bare script on every commit, and the platform logger would pull config
loading into that path — the trade taken in
autobot_shared/user_management/password_epoch.py. Findings log at ERRORso they clear
logging.lastResort's WARNING threshold and stay visible evenwhen nothing configured logging;
configure_logging()attaches a stderrhandler at INFO so the clean-run line is not discarded either.
The pre-existing stdout call in
main()moved too, rather than being leftunder the changed-lines carve-out. Said here rather than done silently: it is
the one line in this PR the failing check did not require, and the reason is
that keeping it would leave a 176-line file with two output paths for the same
findings.
.github/workflows/code-quality.yml— new blocking step running--audit-ceilings, beside the existing extension-import baseline audit. Thepre-commit hook only sees staged files; this is the sink that makes the
repo-wide direction real.
repo_tests/python_file_size_ratchet_test.py— 20 tests, new file.Verification
The ceiling asserts both directions.
test_no_entry_may_be_addedset(KNOWN_LARGE)is a subset of a frozen baseline literaltest_no_ceiling_may_be_raised<=its frozen baseline valuetest_growing_past_the_ceiling_failsverdict(rel, ceiling + 1)is a violation naming the ceilingtest_an_entry_whose_file_is_now_compliant_failsverdict(rel, 600)demands the entry be deletedtest_a_shrunk_file_must_lower_its_ceilingverdict(rel, ceiling - 1)names the new numbertest_the_audit_reports_a_vanished_entrytest_the_audit_surfaces_a_ceiling_violationtest_a_file_at_its_ceiling_passesThe frozen baseline is a deliberate second copy of the mapping: a ratchet needs
a fixed reference, and a list only ever compared against itself can drift
anywhere. Lowering a ceiling needs no edit to it; raising one or adding a fourth
file must fail, and the comment says so.
Reach self-check.
test_matcher_reaches_every_entry_over_a_tracked_enumerationdrives the hook's own matcher across 4958 paths enumerated by
git ls-files,not by anything in the hook, and asserts the matcher classifies exactly the 3
entries as grandfathered.
test_enumeration_reaches_the_repoguards theenumeration itself with a floor of 3000, so an empty listing cannot agree with
anything. The counter deliberately does not share the matcher's blind spot:
counting the dict's own keys would fail exactly when the matcher does — a
key-form rewrite silences both, and both then agree that all is well. Driving the
matcher from an outside enumeration cannot agree that way, and
M6below is thedemonstration. The audit's own counter is a third mechanism again: it counts
files it actually read off disk, so
run_auditfails on "reached 0 of 3"(
test_run_audit_fails_when_the_scan_reached_nothing).The findings still reach the developer. A silent conversion to logging would
be worse than the stdout call it replaced — a guard whose output vanishes passes
while reporting nothing. Five tests run the checker against a violating fixture
and capture the emitted record:
test_a_violation_is_reported_to_the_developer(the finding text and line count are present),
test_findings_are_emitted_above_the_lastresort_threshold(level>= WARNING,so a bare invocation that never configured logging still shows them),
test_audit_problems_are_reported_to_the_developer,test_configure_logging_makes_the_clean_run_visible(a handler exists and INFOis enabled, or the clean-run line would be discarded) and
test_the_hook_has_no_stdout_calls_left.Observed from a bare run of a standalone copy:
Fixture hygiene.
test_the_hook_has_no_stdout_calls_leftwalks the AST for acall to the banned name, assembled by implicit string concatenation, so this
fixture cannot trip the very lint it enforces — no exemption was added, and it
self-guards on the assembled needle. The prose comment above it describes the
banned call without quoting it. Fixtures needing an over-limit file build one in
tmp_pathrather than shipping one.Mutation table — standalone harness (
assert mutated != originalbefore eachrun, baseline green first). Mutations applied to a copy in scratch; nothing
was pushed to the branch.
test_growing_past_the_ceiling_fails(+2)test_an_entry_whose_file_is_now_compliant_failstest_a_shrunk_file_must_lower_its_ceilingtest_no_ceiling_may_be_raised,test_recorded_ceilings_match_the_files_todaytest_no_entry_may_be_addedtest_matcher_reaches_every_entry_over_a_tracked_enumeration(+6)test_the_audit_reports_a_vanished_entry(+2)test_the_audit_reports_a_vanished_entrytest_the_audit_surfaces_a_ceiling_violationtest_run_audit_fails_when_the_scan_reached_nothing(+3)run_auditremovedtest_run_audit_fails_when_the_scan_reached_nothingdebug(invisible)test_findings_are_emitted_above_the_lastresort_threshold(+1)test_a_violation_is_reported_to_the_developer(+1)debugtest_audit_problems_are_reported_to_the_developerconfigure_loggingattaches nothingtest_configure_logging_makes_the_clean_run_visibletest_the_hook_has_no_stdout_calls_left16/16 mutations caught. M9 and M11 exist because the first pass scored 8/9:
test_recorded_ceilings_match_the_files_todaypasses on today's tree whether ornot the audit reports anything, since there is nothing to report — a green test
proving nothing about the line it names. Two tests were added to stage a real
breach and a reach-zero scan. M12–M16 arrived with the logging conversion, for
the same reason: the conversion had to be provably not silent.
Syntax:
python3 -m py_compileon both Python files,yaml.safe_loadon theworkflow. Per repo policy nothing was installed and no application code was run;
the harness is a standalone scratch copy.
Model Used
Claude Opus 5 (1M context)
Discovered, filed not fixed
#14462 — the same guard is unenforced for everything outside the list.
500 tracked
.pyfiles exceed 600 lines; 3 are grandfathered, so 497 are overthe limit with no exemption and no enforcement: the hook is staged-files-only,
and the one repo-wide run (
enforce-precommit.yml) routes findings into a::warning::and exits 0. Largest is 31019 lines. Out of scope here — this PRwas asked to bound the three named files, and extending ceilings to 497 files is
a different change with a different risk profile. Cross-linked both ways.
Closes #14236