Skip to content

tech-debt(guards): function-length-check is whole-file, so a legacy long function blocks every unrelated change to its file #16191

Description

@mrveiss

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 file
exceeds 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

File Oversized function Body lines What it blocked
autobot-backend/api/analytics_code_review.py analyze_diff 117 a 2-line security fix — the last of seven unscrubbed git call sites (#16179), in get_git_diff, which does not touch analyze_diff (#16186)
autobot-backend/a2a/task_executor.py _execute_claimed 159 a 1-line isort fix — moving one import between groups, to unblock #16175

In 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_claimed to unblock the isort fix, the author's line-arithmetic
dropped an else: from the eval gate. _fail_on_eval would then have run
unconditionally: every task that PASSED self-eval would have been marked FAILED.

That version passed flake8, isort, black and the repo-wide F821 audit. It was
syntactically 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.md and the repo's batching rule both say independent or
different-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.py split landed as 43e5de997).

Options

  1. Scope the check to functions the diff touches, the way the file-size ratchet is
    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.
  2. Grandfather with an explicit, shrink-only baseline, exactly like
    python_file_size_ratchet_baseline.py: record today's offenders with their current
    length, 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

  • A one-line change to a file containing a >65-line function it does not touch lands
    without modifying that function. Evidence: a commit doing exactly that, with the
    guard green.
  • Editing a >65-line function itself still fails the guard. Evidence: the guard red on
    such a change.
  • Adding a NEW function over the threshold still fails, in any file. Evidence: the
    guard red on such a change.
  • If option 2 is taken, the baseline may only shrink, enforced by a test — the same
    property RATCHET_BASELINES.md requires of the file-size ratchet.
  • The current offender list is recorded, so the debt is enumerable rather than
    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.

Activity

  1. mrveiss commented on Sep 11, 2026

    @mrveiss
    OwnerAuthor

    A third instance, filed as #16250: autobot-backend/initialization/lifespan.py. cleanup_services has 267 body lines and _init_graph_rag_service has 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's pre-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 as PRE_COMMIT_FROM_REF, and #16241 uses it for no-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 helpers added_lines() and staged_paths() in tools/lint/_scan_helpers.py (from #16241) are reusable here: a function counts as touched when an added line falls inside its span.

  2. mrveiss commented on Sep 11, 2026

    @mrveiss
    OwnerAuthor

    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 .py files on the tree at d5b45663f (#16241's head, 0 commits behind Dev_new_gui at the time).

    Top-level directory Offenders
    autobot-backend 91
    autobot-infrastructure 24
    autobot-slm-backend 6
    scripts 2
    docs 2
    autobot-npu-worker 2
    tools 1

    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 the def line, 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$ and migrations/.

    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-file mode that judges every file whole, so this list can be regenerated rather than trusted from this comment. Pipe it the tracked .py files, 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_widget autobot-infrastructure/shared/scripts/utilities/enhance_workflow_ui.py:381
    294 create_workflow_notification_component autobot-infrastructure/shared/scripts/utilities/enhance_workflow_ui.py:36
    267 cleanup_services autobot-backend/initialization/lifespan.py:2251
    179 _populate_default_providers autobot-backend/llm_shared/provider_registry.py:544
    173 generate_report autobot-backend/benchmarks/benchmark_belief_state.py:479
    171 _pulse_check_worker autobot-backend/services/npu_worker_manager.py:433
    166 build_scenarios autobot-backend/benchmarks/benchmark_belief_state.py:161
    142 execute_tools autobot-backend/chat_workflow/graph.py:954
    142 chat autobot-backend/services/llm_service.py:181
    132 example_database_exhaustion autobot-backend/context_aware_decision/examples_counterfactual.py:157
    132 stream autobot-backend/services/llm_service.py:398
    129 _deterministic_embed autobot-backend/knowledge/rag_benchmarks.py:394
    129 _generate_recovery_actions autobot-backend/orchestration/causal_error_recovery.py:246
    129 _run_provisioning_task autobot-slm-backend/api/setup_wizard.py:958
    123 register_root_endpoints autobot-backend/initialization/endpoints.py:112
    122 verify autobot-backend/services/claim_verifier.py:590
    120 lifespan autobot-slm-backend/main.py:176
    114 _detect_lsp_violations autobot-backend/code_analysis/src/anti_pattern_detector.py:1379
    111 main scripts/audit_api_wiring.py:766
    110 chat_completions autobot-backend/api/openai_compat.py:299
    109 get_available_actions autobot-backend/integrations/microsoft365_integration.py:163
    109 fetch_content autobot-backend/knowledge/connectors/gdrive.py:157
    106 execute_with_fallback autobot-backend/llm_shared/model_fallback_coordinator.py:60
    104 chat_optimized autobot-backend/services/llm_service.py:725
    101 generate_browser_fix_script autobot-infrastructure/shared/scripts/utilities/fix_settings_loading.py:173
    99 _try_mcp_dispatch autobot-backend/chat_workflow/tool_handler.py:994
    99 plan_workflow autobot-infrastructure/shared/scripts/utilities/orchestrator_enhancement.py:82
    98 example_network_timeout autobot-backend/context_aware_decision/examples_counterfactual.py:29
    98 _get_ideal_network_scan_workflow autobot-infrastructure/shared/scripts/utilities/agent_workflow_analysis.py:74
    98 analyze_codebase docs/examples/mcp_agent_workflows/code_analysis.py:29
    97 _default_rules autobot-backend/knowledge/query_sanitizer.py:149
    95 init_ui autobot-npu-worker/resources/windows-npu-worker/gui/widgets/benchmark_widget.py:138
    94 _execute_iteration_phases autobot-backend/agent_loop/loop.py:601
    94 _dispatch_tool_call autobot-backend/chat_workflow/tool_handler.py:3241
    94 _github_request autobot-backend/integrations/github_integration.py:268
    94 search autobot-backend/knowledge/search.py:195
    94 generate_main_env autobot-infrastructure/shared/scripts/generate-env-files.py:49
    93 hire_agent autobot-backend/llc/api/agent_hires.py:349
    93 execute_coordinated_workflow autobot-backend/orchestration/workflow_executor.py:603
    90 generate_response autobot-backend/chat_workflow/graph.py:647
    90 request_approval autobot-backend/integrations/slack_integration.py:247
    90 generate_native_vm_env autobot-infrastructure/shared/scripts/generate-env-files.py:225
    89 _chat_completion_impl autobot-backend/llm_shared/providers/anthropic.py:395
    89 _generate_markdown_report autobot-infrastructure/shared/scripts/microservice_architecture_evaluator.py:1345
    88 _invoke autobot-backend/llc/adapters/copilot_subscription_adapter.py:59
    88 _analyze_page_content autobot-infrastructure/shared/scripts/debug/frontend_analysis_lib.py:301
    87 sync autobot-backend/knowledge/connectors/base.py:391
    87 _generate_markdown_report autobot-infrastructure/shared/scripts/quick_microservice_analysis.py:524
    86 sync autobot-backend/knowledge/connectors/external_adapter.py:242
    86 trigger_comment_wake autobot-backend/llc/services/comment_wake_service.py:38
    85 fix_long_lines autobot-infrastructure/shared/scripts/fix_long_lines.py:10
    85 generate_docker_production_env autobot-infrastructure/shared/scripts/generate-env-files.py:468
    85 quick_microservice_analysis autobot-infrastructure/shared/scripts/quick_microservice_analysis.py:268
    84 messages autobot-backend/api/anthropic_compat.py:243
    84 _prepare_llm_request_params autobot-backend/chat_workflow/llm_handler.py:849
    84 fetch_content autobot-backend/knowledge/connectors/onedrive.py:208
    84 _chat_completion_impl autobot-backend/llm_shared/providers/ollama_provider.py:81
    83 _build_fat autobot-backend/llc/kb/context_builder.py:112
    83 _run_single_iteration autobot-backend/services/autoresearch/auto_research_agent.py:689
    82 set_user_bundle autobot-backend/api/voice_bundle_user.py:181
    82 _handle_delegate_tool autobot-backend/chat_workflow/tool_handler.py:2280
    82 handle_github_webhook autobot-backend/llc/api/github_webhooks.py:136
    82 _run_adapter autobot-backend/llc/scheduler/heartbeat_scheduler.py:466
    82 handle_error autobot-backend/orchestration/error_handler.py:362
    81 _async_cleanup_expired_chats autobot-backend/tasks/chat_retention.py:85
    81 main autobot-slm-backend/scripts/remove_orphaned_node.py:119
    80 execute_playbook autobot-slm-backend/services/playbook_executor.py:1198
    79 _ws_connect_and_listen autobot-backend/services/slm_client.py:595
    79 generate_network_env autobot-infrastructure/shared/scripts/generate-env-files.py:384
    78 generate_report autobot-infrastructure/shared/scripts/analyze_duplicates.py:161
    78 create_migration_strategy autobot-infrastructure/shared/scripts/microservice_architecture_evaluator.py:953
    78 verify_embedding_model_consistency autobot-infrastructure/shared/scripts/verify_knowledge_consistency.py:147
    78 init_ui autobot-npu-worker/resources/windows-npu-worker/gui/widgets/connection_info.py:73
    77 _stream_generator autobot-backend/api/openai_compat.py:185
    77 _invoke autobot-backend/llc/adapters/claude_code_adapter.py:209
    76 _async_cleanup_expired_kb_entries autobot-backend/tasks/knowledge_retention.py:48
    75 update autobot-backend/agent_loop/belief_state.py:162
    75 process_chat_message autobot-backend/api/chat.py:786
    75 run_rag_benchmark autobot-backend/api/knowledge_rag.py:453
    75 _get_rs256_keypair autobot-backend/auth_middleware.py:131
    74 create_connector autobot-backend/api/knowledge_connectors.py:445
    74 perform_knowledge_search autobot-backend/chat_workflow/graph.py:1191
    74 _detect_unwired_trackers autobot-backend/code_analysis/src/anti_pattern_detector.py:1865
    74 call_tool autobot-backend/services/realtime_mcp_bridge.py:290
    74 check tools/lint/canonical/rules/py_banned_route_path.py:138
    73 device_poll autobot-backend/api/provider_auth.py:476
    73 _invoke autobot-backend/llc/adapters/claude_code_subscription_adapter.py:59
    73 _synthesize_cluster autobot-backend/services/knowledge/kb_synthesizer.py:327
    73 generate_recommendation autobot-infrastructure/shared/scripts/analysis/redis_vector_analysis.py:484
    73 _build_argument_parser autobot-infrastructure/shared/scripts/logging/log_forwarder.py:1203
    72 _detect_duplicate_class_shapes autobot-backend/code_analysis/src/anti_pattern_detector.py:1722
    72 discover_sources autobot-backend/knowledge/connectors/external_adapter.py:148
    72 _invoke autobot-backend/llc/adapters/copilot_local_adapter.py:81
    72 _handle_due_agent autobot-backend/llc/scheduler/heartbeat_scheduler.py:263
    72 process_recording autobot-backend/transcriber/orchestrator.py:38
    72 generate_localhost_env autobot-infrastructure/shared/scripts/generate-env-files.py:148
    72 main autobot-infrastructure/shared/scripts/utilities/optimize_agents.py:544
    71 ai_propose_regions autobot-backend/api/playwright.py:821
    71 validate_workflow autobot-infrastructure/shared/scripts/utilities/validate_ci_pipeline.py:15
    71 ldap_login autobot-slm-backend/api/sso_auth.py:321
    70 stream_task_events autobot-backend/api/a2a.py:312
    70 check_approval_response autobot-backend/integrations/slack_integration.py:454
    70 _get_tool_handler autobot-backend/tools/tool_registry.py:689
    70 main autobot-infrastructure/shared/scripts/detect_missing_error_handling.py:344
    70 oauth_callback autobot-slm-backend/api/sso_auth.py:227
    69 _fetch_catalog_document autobot-backend/api/marketplace_sources.py:195
    69 _make_request autobot-backend/services/ai_stack_client.py:252
    69 resolve autobot-backend/services/conflict_resolver.py:165
    69 monitor_browser_activity docs/examples/mcp_agent_workflows/vnc_monitoring.py:28
    68 _request_approval autobot-backend/agent_loop/loop.py:1759
    68 process_request autobot-backend/agents/standardized_agent.py:198
    68 _predict_heuristic autobot-backend/context_aware_decision/counterfactual_reasoner.py:223
    68 _init_graph_rag_service autobot-backend/initialization/lifespan.py:1171
    68 send_template_message autobot-backend/integrations/whatsapp_integration.py:451
    68 _execute_node autobot-backend/orchestration/graph_runner.py:643
    67 post_task_completion autobot-backend/integrations/slack_integration.py:164
    67 ingest autobot-backend/llc/kb/artifact_ingestor.py:84
    67 _llm_summarize_and_index autobot-backend/llc/kb/sprint_summarizer.py:271
    67 _handle_rate_limited autobot-backend/llc/scheduler/heartbeat_scheduler.py:569
    67 find_similar_trajectories autobot-backend/memory/trajectory_store.py:387
    66 upload_file_to_knowledge autobot-backend/api/knowledge.py:1279
    66 get_org_chart autobot-backend/llc/api/companies.py:1141
    66 _claude_chat_completion autobot-backend/llm_shared/providers/vertexai.py:311
    66 _register_default_configs autobot-backend/middleware/hook_invoker.py:118
    66 advanced_search autobot-backend/services/rag_service.py:700
    66 manage_channel_mapping autobot-backend/services/slack_approval_integration.py:173
    66 identify_service_boundaries autobot-infrastructure/shared/scripts/microservice_architecture_evaluator.py:786
    66 _migrate scripts/migrate_connector_credentials.py:67
  3. added this to the v0.9.0 milestone on Sep 12, 2026
  4. mrveiss commented on Sep 12, 2026

    @mrveiss
    OwnerAuthor

    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.py scopes via added_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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions