Repository navigation
tech-debt(guards): function-length-check is whole-file, so a legacy long function blocks every unrelated change to its file #16191
Description
Activity
A third instance, filed as #16250:
autobot-backend/initialization/lifespan.py.cleanup_serviceshas 267 body lines and_init_graph_rag_servicehas 68, and both blocked a two-line change to that file (#16231). They belong in this issue's offender list, per the criterion that the debt be enumerable.One constraint for whichever option is taken. If the fix scopes the hook to changed lines, it must also work under
enforce-precommit.yml'spre-commit run --from-ref HEAD^1 --to-ref HEAD^2. That run stages nothing, so a staged-diff scope sees an empty change and would pass everything. pre-commit exports the range asPRE_COMMIT_FROM_REF, and #16241 uses it forno-local-schemas. A file passed but not staged (--all-files) has no change to scope to, so it should be judged whole rather than passed. The shared helpersadded_lines()andstaged_paths()intools/lint/_scan_helpers.py(from #16241) are reusable here: a function counts as touched when an added line falls inside its span.- added a commit that references this issue
on Sep 11, 2026 The current offender list, for AC5. It is recorded here so the debt can be enumerated, rather than discovered one blocked PR at a time.
128 functions exceed the 65-body-line ERROR threshold, in 101 files. The measurement scanned 2,751 git-tracked
.pyfiles on the tree atd5b45663f(#16241's head, 0 commits behindDev_new_guiat the time).Top-level directory Offenders autobot-backend91 autobot-infrastructure24 autobot-slm-backend6 scripts2 docs2 autobot-npu-worker2 tools1 How it was measured. A text-only AST pass that imports and runs nothing from the repo. It replicates two things exactly:
function_length_checker.py's counting: body lines, excluding thedefline, the docstring, blank lines and comment-only lines.- The wrapper's path filters: the same unanchored regexes for
__pycache__|node_modules|.venv|venv|archive|temp,test_|_test.py|tests/,__init__.py$andmigrations/.
As a cross-check, #16250's two offenders come out exactly as #16250 measured them:
cleanup_services(267) and_init_graph_rag_service(68).Re-deriving it. After #16241 merges, the checker has a
--whole-filemode that judges every file whole, so this list can be regenerated rather than trusted from this comment. Pipe it the tracked.pyfiles, filtered the same way. The hook itself only ever judges the functions a change touches, so none of these block an unrelated change any more.All 128, longest first
Body lines Function Location 339 create_workflow_progress_widgetautobot-infrastructure/shared/scripts/utilities/enhance_workflow_ui.py:381294 create_workflow_notification_componentautobot-infrastructure/shared/scripts/utilities/enhance_workflow_ui.py:36267 cleanup_servicesautobot-backend/initialization/lifespan.py:2251179 _populate_default_providersautobot-backend/llm_shared/provider_registry.py:544173 generate_reportautobot-backend/benchmarks/benchmark_belief_state.py:479171 _pulse_check_workerautobot-backend/services/npu_worker_manager.py:433166 build_scenariosautobot-backend/benchmarks/benchmark_belief_state.py:161142 execute_toolsautobot-backend/chat_workflow/graph.py:954142 chatautobot-backend/services/llm_service.py:181132 example_database_exhaustionautobot-backend/context_aware_decision/examples_counterfactual.py:157132 streamautobot-backend/services/llm_service.py:398129 _deterministic_embedautobot-backend/knowledge/rag_benchmarks.py:394129 _generate_recovery_actionsautobot-backend/orchestration/causal_error_recovery.py:246129 _run_provisioning_taskautobot-slm-backend/api/setup_wizard.py:958123 register_root_endpointsautobot-backend/initialization/endpoints.py:112122 verifyautobot-backend/services/claim_verifier.py:590120 lifespanautobot-slm-backend/main.py:176114 _detect_lsp_violationsautobot-backend/code_analysis/src/anti_pattern_detector.py:1379111 mainscripts/audit_api_wiring.py:766110 chat_completionsautobot-backend/api/openai_compat.py:299109 get_available_actionsautobot-backend/integrations/microsoft365_integration.py:163109 fetch_contentautobot-backend/knowledge/connectors/gdrive.py:157106 execute_with_fallbackautobot-backend/llm_shared/model_fallback_coordinator.py:60104 chat_optimizedautobot-backend/services/llm_service.py:725101 generate_browser_fix_scriptautobot-infrastructure/shared/scripts/utilities/fix_settings_loading.py:17399 _try_mcp_dispatchautobot-backend/chat_workflow/tool_handler.py:99499 plan_workflowautobot-infrastructure/shared/scripts/utilities/orchestrator_enhancement.py:8298 example_network_timeoutautobot-backend/context_aware_decision/examples_counterfactual.py:2998 _get_ideal_network_scan_workflowautobot-infrastructure/shared/scripts/utilities/agent_workflow_analysis.py:7498 analyze_codebasedocs/examples/mcp_agent_workflows/code_analysis.py:2997 _default_rulesautobot-backend/knowledge/query_sanitizer.py:14995 init_uiautobot-npu-worker/resources/windows-npu-worker/gui/widgets/benchmark_widget.py:13894 _execute_iteration_phasesautobot-backend/agent_loop/loop.py:60194 _dispatch_tool_callautobot-backend/chat_workflow/tool_handler.py:324194 _github_requestautobot-backend/integrations/github_integration.py:26894 searchautobot-backend/knowledge/search.py:19594 generate_main_envautobot-infrastructure/shared/scripts/generate-env-files.py:4993 hire_agentautobot-backend/llc/api/agent_hires.py:34993 execute_coordinated_workflowautobot-backend/orchestration/workflow_executor.py:60390 generate_responseautobot-backend/chat_workflow/graph.py:64790 request_approvalautobot-backend/integrations/slack_integration.py:24790 generate_native_vm_envautobot-infrastructure/shared/scripts/generate-env-files.py:22589 _chat_completion_implautobot-backend/llm_shared/providers/anthropic.py:39589 _generate_markdown_reportautobot-infrastructure/shared/scripts/microservice_architecture_evaluator.py:134588 _invokeautobot-backend/llc/adapters/copilot_subscription_adapter.py:5988 _analyze_page_contentautobot-infrastructure/shared/scripts/debug/frontend_analysis_lib.py:30187 syncautobot-backend/knowledge/connectors/base.py:39187 _generate_markdown_reportautobot-infrastructure/shared/scripts/quick_microservice_analysis.py:52486 syncautobot-backend/knowledge/connectors/external_adapter.py:24286 trigger_comment_wakeautobot-backend/llc/services/comment_wake_service.py:3885 fix_long_linesautobot-infrastructure/shared/scripts/fix_long_lines.py:1085 generate_docker_production_envautobot-infrastructure/shared/scripts/generate-env-files.py:46885 quick_microservice_analysisautobot-infrastructure/shared/scripts/quick_microservice_analysis.py:26884 messagesautobot-backend/api/anthropic_compat.py:24384 _prepare_llm_request_paramsautobot-backend/chat_workflow/llm_handler.py:84984 fetch_contentautobot-backend/knowledge/connectors/onedrive.py:20884 _chat_completion_implautobot-backend/llm_shared/providers/ollama_provider.py:8183 _build_fatautobot-backend/llc/kb/context_builder.py:11283 _run_single_iterationautobot-backend/services/autoresearch/auto_research_agent.py:68982 set_user_bundleautobot-backend/api/voice_bundle_user.py:18182 _handle_delegate_toolautobot-backend/chat_workflow/tool_handler.py:228082 handle_github_webhookautobot-backend/llc/api/github_webhooks.py:13682 _run_adapterautobot-backend/llc/scheduler/heartbeat_scheduler.py:46682 handle_errorautobot-backend/orchestration/error_handler.py:36281 _async_cleanup_expired_chatsautobot-backend/tasks/chat_retention.py:8581 mainautobot-slm-backend/scripts/remove_orphaned_node.py:11980 execute_playbookautobot-slm-backend/services/playbook_executor.py:119879 _ws_connect_and_listenautobot-backend/services/slm_client.py:59579 generate_network_envautobot-infrastructure/shared/scripts/generate-env-files.py:38478 generate_reportautobot-infrastructure/shared/scripts/analyze_duplicates.py:16178 create_migration_strategyautobot-infrastructure/shared/scripts/microservice_architecture_evaluator.py:95378 verify_embedding_model_consistencyautobot-infrastructure/shared/scripts/verify_knowledge_consistency.py:14778 init_uiautobot-npu-worker/resources/windows-npu-worker/gui/widgets/connection_info.py:7377 _stream_generatorautobot-backend/api/openai_compat.py:18577 _invokeautobot-backend/llc/adapters/claude_code_adapter.py:20976 _async_cleanup_expired_kb_entriesautobot-backend/tasks/knowledge_retention.py:4875 updateautobot-backend/agent_loop/belief_state.py:16275 process_chat_messageautobot-backend/api/chat.py:78675 run_rag_benchmarkautobot-backend/api/knowledge_rag.py:45375 _get_rs256_keypairautobot-backend/auth_middleware.py:13174 create_connectorautobot-backend/api/knowledge_connectors.py:44574 perform_knowledge_searchautobot-backend/chat_workflow/graph.py:119174 _detect_unwired_trackersautobot-backend/code_analysis/src/anti_pattern_detector.py:186574 call_toolautobot-backend/services/realtime_mcp_bridge.py:29074 checktools/lint/canonical/rules/py_banned_route_path.py:13873 device_pollautobot-backend/api/provider_auth.py:47673 _invokeautobot-backend/llc/adapters/claude_code_subscription_adapter.py:5973 _synthesize_clusterautobot-backend/services/knowledge/kb_synthesizer.py:32773 generate_recommendationautobot-infrastructure/shared/scripts/analysis/redis_vector_analysis.py:48473 _build_argument_parserautobot-infrastructure/shared/scripts/logging/log_forwarder.py:120372 _detect_duplicate_class_shapesautobot-backend/code_analysis/src/anti_pattern_detector.py:172272 discover_sourcesautobot-backend/knowledge/connectors/external_adapter.py:14872 _invokeautobot-backend/llc/adapters/copilot_local_adapter.py:8172 _handle_due_agentautobot-backend/llc/scheduler/heartbeat_scheduler.py:26372 process_recordingautobot-backend/transcriber/orchestrator.py:3872 generate_localhost_envautobot-infrastructure/shared/scripts/generate-env-files.py:14872 mainautobot-infrastructure/shared/scripts/utilities/optimize_agents.py:54471 ai_propose_regionsautobot-backend/api/playwright.py:82171 validate_workflowautobot-infrastructure/shared/scripts/utilities/validate_ci_pipeline.py:1571 ldap_loginautobot-slm-backend/api/sso_auth.py:32170 stream_task_eventsautobot-backend/api/a2a.py:31270 check_approval_responseautobot-backend/integrations/slack_integration.py:45470 _get_tool_handlerautobot-backend/tools/tool_registry.py:68970 mainautobot-infrastructure/shared/scripts/detect_missing_error_handling.py:34470 oauth_callbackautobot-slm-backend/api/sso_auth.py:22769 _fetch_catalog_documentautobot-backend/api/marketplace_sources.py:19569 _make_requestautobot-backend/services/ai_stack_client.py:25269 resolveautobot-backend/services/conflict_resolver.py:16569 monitor_browser_activitydocs/examples/mcp_agent_workflows/vnc_monitoring.py:2868 _request_approvalautobot-backend/agent_loop/loop.py:175968 process_requestautobot-backend/agents/standardized_agent.py:19868 _predict_heuristicautobot-backend/context_aware_decision/counterfactual_reasoner.py:22368 _init_graph_rag_serviceautobot-backend/initialization/lifespan.py:117168 send_template_messageautobot-backend/integrations/whatsapp_integration.py:45168 _execute_nodeautobot-backend/orchestration/graph_runner.py:64367 post_task_completionautobot-backend/integrations/slack_integration.py:16467 ingestautobot-backend/llc/kb/artifact_ingestor.py:8467 _llm_summarize_and_indexautobot-backend/llc/kb/sprint_summarizer.py:27167 _handle_rate_limitedautobot-backend/llc/scheduler/heartbeat_scheduler.py:56967 find_similar_trajectoriesautobot-backend/memory/trajectory_store.py:38766 upload_file_to_knowledgeautobot-backend/api/knowledge.py:127966 get_org_chartautobot-backend/llc/api/companies.py:114166 _claude_chat_completionautobot-backend/llm_shared/providers/vertexai.py:31166 _register_default_configsautobot-backend/middleware/hook_invoker.py:11866 advanced_searchautobot-backend/services/rag_service.py:70066 manage_channel_mappingautobot-backend/services/slack_approval_integration.py:17366 identify_service_boundariesautobot-infrastructure/shared/scripts/microservice_architecture_evaluator.py:78666 _migratescripts/migrate_connector_credentials.py:67- added a commit that references this issue
on Sep 12, 2026 Closing — fully delivered; never auto-closed because Dev_new_gui wasn't GitHub's configured default branch at merge time.
Verified against
origin/main:autobot-infrastructure/shared/scripts/hooks/function_length_checker.pyscopes viaadded_lines()/resolve_base()/staged_paths()(:47) instead of whole-file scanning. PR body's evidence table proves all 3 named scenarios: an edit beside an 80-line legacy function is green with the skip reported (AC1); an edit inside that function is red (AC2); a new long function added to the same file is red (AC3). AC4 does not apply — the diff-scoping approach (option 1) was chosen over a baseline (option 2), so there is no baseline to ratchet, which is consistent rather than a gap. An offender census was posted on the issue itself: 128 functions exceed the 65-body-line ERROR threshold across 101 files, cross-checked against #16250's two known offenders.Delivered by merged PR #16241.
- added a commit that references this issue
on Sep 14, 2026
The guard is whole-file, so one oversized legacy function holds every future change to that file hostage
Function Length Check (#620)fails a commit when any function in a staged fileexceeds 65 body lines — including functions the diff never reads. The practical effect
is that a one-line fix to such a file cannot land on its own. The author must either
abandon the fix or bundle it with a large refactor of unrelated code.
Two instances today, both blocking work unrelated to the oversized function
autobot-backend/api/analytics_code_review.pyanalyze_diffget_git_diff, which does not touchanalyze_diff(#16186)autobot-backend/a2a/task_executor.py_execute_claimedIn both cases the blocked change made the file smaller or left it the same size.
Why this is a defect and not the guard working as intended
Forcing a refactor into an unrelated changeset raises the risk of that changeset by
an order of magnitude, and this is not theoretical — it nearly shipped a bug today.
Splitting
_execute_claimedto unblock the isort fix, the author's line-arithmeticdropped an
else:from the eval gate._fail_on_evalwould then have rununconditionally: every task that PASSED self-eval would have been marked FAILED.
That version passed
flake8,isort,blackand the repo-wide F821 audit. It wassyntactically perfect and semantically inverted. It was caught only because the author
re-read the extracted blocks against the original by hand.
So the guard's current shape trades a cosmetic property (no long functions) for a real
one (small, reviewable, low-risk diffs), and the trade is invisible because the risk
lands in a different PR than the one the guard was protecting.
docs/developer/CLAUDE_REVIEW.mdand the repo's batching rule both say independent ordifferent-risk changes belong in separate PRs. This guard makes that impossible for any
file containing a legacy long function.
What is NOT being proposed
Not raising the 65 threshold, not adding a blanket opt-out, and not weakening the guard.
Long functions are worth fixing and both of the above are being fixed properly (#16186,
and the
task_executor.pysplit landed as43e5de997).Options
scoped to files the diff touches. A pre-existing long function stays failing for
anyone who edits it, and stops failing for someone who edits its neighbour.
python_file_size_ratchet_baseline.py: record today's offenders with their currentlength, fail on any increase or any new offender, and let unrelated edits pass. This
keeps the debt visible and enforces the direction of travel.
Option 2 matches machinery this repo already has and already trusts. It also produces a
list — which is the thing the current guard cannot give you, because it only ever reports
the one file someone happened to touch.
Acceptance criteria
without modifying that function. Evidence: a commit doing exactly that, with the
guard green.
such a change.
guard red on such a change.
property
RATCHET_BASELINES.mdrequires of the file-size ratchet.discovered one blocked PR at a time.
Provenance
Found by hitting it twice in one session on unrelated work (#16186, #16175). Filed as the
pattern; #16186 remains the instance and is being fixed on its own terms.