3x-ui's bot and inbound paths carry the highest risk — 5 functions to address first

In MHSanaei/3x-ui, answerCallback, fromParamLink, and three inbound-management functions combine high branching, deep nesting, broad fan-out, and recent commit activity, making them the codebase's leading live regression hotspots.

Stephen Collins ·
oss go refactoring code-health
Activity Risk21.03Low
Hottest FunctionanswerCallback

Antipatterns Detected

complex_branching5exit_heavy5god_function5long_function5deeply_nested4

Key Points

What is a god function and why does it matter in 3x-ui?

A god function is a single function that has accumulated so many responsibilities that it acts as the center of gravity for a large portion of the system — it calls a huge number of other functions, handles many distinct cases internally, and is difficult to change without unintended side effects. In concrete terms, a god function has high cyclomatic complexity (many execution paths), high fan-out (many distinct callees), and often high nesting depth. In 3x-ui, `answerCallback` is the clearest example: with a cyclomatic complexity of 101 and a fan-out of 116, modifying it risks breaking behavior across a wide surface area, and testing it in isolation is extraordinarily difficult. God functions like this one are among the top patterns flagged across 3x-ui's critical hotspots.

How do I reduce cyclomatic complexity in Go?

The most effective technique is the extract-method (or in Go, extract-function) refactoring: identify cohesive groups of branches that handle a distinct sub-case and move them into their own named functions, each with a single clear responsibility. As a practical threshold, a cyclomatic complexity above 15 warrants review and splitting; above 30 it should be treated as urgent, and above 100 — as with `answerCallback` at CC 101 — it represents an immediate refactoring priority. A concrete first step for `answerCallback` is to map each top-level callback type it handles to its own handler function and replace the original body with a dispatch table or switch, which can realistically cut the CC by half or more in a single pass. Always capture existing behavior with characterization tests before making structural changes to a function at this complexity level.

Is 3x-ui actively maintained?

Yes — the data shows clear signs of active development. Both top-ranked critical functions, `answerCallback` and `fromParamLink`, were modified within the last 7 days and each received 1 commit touch in the last 30 days at the time of this snapshot (commit 355bb4c). All 540 functions combining structural complexity with recent activity have been touched recently, which means there is no section of the high-complexity code that has been left entirely dormant. Active maintenance and elevated structural complexity are not mutually exclusive — the team is clearly shipping, and the analysis is intended to help prioritize where that shipping activity carries the most structural risk, not to characterize the quality of the work.

How do I reproduce this analysis?

The analysis was produced using the Hotspots CLI, available at github.com/hotspots-dev/hotspots, against commit `355bb4c` of the MHSanaei/3x-ui repository. To reproduce it, run `git checkout 355bb4c` inside a local clone of the repo, then execute `hotspots analyze . --mode snapshot --explain-patterns --force`. The same command works on any local git repository without additional configuration, so you can run it against your own codebase immediately after installation.

What does activity-weighted risk mean?

Activity-weighted risk combines structural complexity — measured through cyclomatic complexity, nesting depth, and fan-out — with recent commit frequency, so that functions which are both hard to understand and actively changing score the highest. A function with a cyclomatic complexity of 80 that has not been touched in two years poses lower near-term regression risk than one with a cyclomatic complexity of 20 that is committed to every week, because the dormant function is unlikely to introduce new bugs until someone changes it. In 3x-ui, `answerCallback` has a risk score of 21.03 precisely because it combines a cyclomatic complexity of 101 with recent commit activity and a last-modified date of 7 days — the complexity is not hypothetical future risk, it is present-tense regression exposure. This prioritization helps engineering teams focus refactoring effort where it reduces the probability of bugs being introduced right now.

In MHSanaei/3x-ui — a popular Xray-based panel managing proxy inbound and outbound configurations — the highest structural risk sits in the Telegram bot layer: answerCallback in web/service/tgbot.go carries a risk score of 21.03, a cyclomatic complexity of 101, and was touched 7 days ago. The project spans 1,766 analyzed functions, of which 173 are rated critical — and 540 functions combine structural complexity with recent change, meaning there is no dormant debt cushion to fall back on. The next four hotspots sit in outbound parsing and inbound management, so the priority is broader than a single bot handler.

The table below ranks functions by activity-weighted risk — a score that multiplies structural complexity by recent commit frequency. A function that is both hard to understand (high cyclomatic complexity) and actively changing is a higher priority than one that is complex but untouched. CC = cyclomatic complexity (independent execution paths); ND = max nesting depth; FO = fan-out (distinct callees).

Top 5 Hotspots

FunctionFileRiskCCNDFO
answerCallbackweb/service/tgbot.go21.01019116
fromParamLinkfrontend/src/models/outbound.js18.236912
UpdateInboundClientweb/service/inbound.go16.811637
useInboundsfrontend/src/pages/inbounds/useInbounds.js16.532430
UpdateInboundweb/service/inbound.go16.410624

Codemod / Tooling Files in Results

One function in the dataset — fromParamLink — lives in frontend/src/models/outbound.js, which is a JavaScript frontend model file rather than Go application code. This is first-party frontend code, not a vendored library, so it should remain in scope for analysis. If you are running a Go-only audit and want to exclude the frontend JavaScript layer, add the following to your .hotspotsrc.json: { "exclude": ["frontend/"] }. Be aware that doing so will hide the second-highest-risk function in the current snapshot.

Hotspot Analysis

answerCallback — web/service/tgbot.go

Based on its name and file path, answerCallback almost certainly handles the dispatch of all incoming Telegram bot callback queries — the central routing hub for every button press or interactive response the bot supports. Its cyclomatic complexity of 101 means there are at least 101 independent execution paths through a single function, each one a potential untested branch; its max nesting depth of 9 means reasoning about any one path requires tracking nine levels of control structure simultaneously. With a fan-out of 116 — 116 distinct functions called from this one — a change anywhere in its body can ripple across a vast portion of the service layer, and the function was modified just 7 days ago, making this a live regression risk rather than a future cleanup concern.

Recommendation: Decompose answerCallback by callback type: extract each logical command handler (e.g., one function per Telegram action category) so the dispatcher becomes a thin routing switch with no business logic of its own. Before any refactoring, add characterization tests that capture the current behavior of each major branch — with 101 paths and 116 callees, test coverage is the only safety net during extraction.

The name fromParamLink and its location in the outbound model strongly suggest this function parses or deserializes an outbound proxy configuration from a share-link or URI parameter string — a format that typically encodes many protocol variants behind a single entry point. Its cyclomatic complexity of 36 reflects the branching required to distinguish those variants, and its max nesting depth of 9 means that branching is stacked rather than flat, making individual paths hard to trace in isolation. With a last-modified date of 7 days ago, this parsing logic is actively evolving — new protocol branches are likely being added — which raises the probability of a regression in an already structurally complex function.

Recommendation: Introduce a protocol-dispatch map or a per-protocol parser module so that fromParamLink becomes a thin entry point delegating to single-responsibility parsers; this would reduce both CC and ND in the central function while making each protocol’s parsing independently testable. Prioritize adding unit tests for the existing branch coverage before adding any new protocol support.

UpdateInboundClient — web/service/inbound.go

UpdateInboundClient likely coordinates edits to an existing inbound client record, which means it sits on a write path where validation, persistence, and side effects can easily become tangled. Its cyclomatic complexity of 11 is lower than the top two hotspots, but its max nesting depth of 6 still forces readers to keep several conditional contexts in mind at once. The fan-out of 37 is the larger concern: even a moderately branched update routine can become fragile when it delegates across that many helpers or service calls.

Recommendation: Split UpdateInboundClient around the update stages: isolate request validation, client lookup, mutation construction, and persistence into named helpers with narrow inputs and outputs. Keep the top-level function as an orchestration path, then add table-driven tests for successful updates, validation failures, and persistence errors so the 37-callee fan-out is covered by behavior rather than manual inspection.

useInbounds — frontend/src/pages/inbounds/useInbounds.js

useInbounds appears to be a frontend hook for loading, transforming, and coordinating inbound configuration state on the inbounds page. Its cyclomatic complexity of 32 shows substantial branching in the client-side workflow, while its max nesting depth of 4 suggests the branches are not as deeply stacked as fromParamLink but still require careful tracing. A fan-out of 30 means the hook likely coordinates many actions, data transforms, or UI-facing helpers from one place.

Recommendation: Separate data fetching, derived-state calculation, and UI command handlers into smaller hooks or pure helper modules. Start by extracting pure transformations that can be tested without rendering the page, then leave useInbounds responsible for composing state rather than owning every branch directly.

UpdateInbound — web/service/inbound.go

UpdateInbound likely handles the broader inbound update flow, adjacent to UpdateInboundClient but focused on the inbound record itself. Its cyclomatic complexity of 10 is right at the threshold where branching deserves review, and its max nesting depth of 6 indicates that several decisions are nested rather than flattened into early returns or separate validation steps. With a fan-out of 24, the function still touches enough collaborators that changes can affect validation, persistence, and downstream service behavior.

Recommendation: Apply the same staged-update structure used for UpdateInboundClient: extract validation, state mutation, and persistence into named helpers, then keep the top-level update path linear. Add regression tests around invalid inbound data, successful updates, and error propagation before changing the control flow.

Patterns Found

Antipatterns detected across the top functions in this snapshot:

PatternOccurrences
complex_branching5
exit_heavy5
god_function5
long_function5
deeply_nested4

These labels belong to two tiers — Tier 1 (structural): complex_branching, deeply_nested, exit_heavy, long_function, god_function. Tier 2 (relational/temporal): hub_function, cyclic_hub, middle_man, neighbor_risk, stale_complex, churn_magnet, shotgun_target, volatile_god.

Key Takeaways

  • Decompose answerCallback (CC 101, FO 116, ND 9) before the next feature lands in web/service/tgbot.go — its 116-callee fan-out means any change today has an unusually wide blast radius across the service layer.
  • fromParamLink in frontend/src/models/outbound.js has a cyclomatic complexity of 36 and a nesting depth of 9 and was last changed 7 days ago — add per-protocol characterization tests immediately to prevent silent regressions as new share-link formats are added.
  • 540 functions in 3x-ui are both structurally complex and actively changing, meaning the codebase has no dormant debt buffer — prioritize test coverage and extract-method refactoring on the critical band (173 functions) before the rate of change compounds regression risk further.

Reproduce This Analysis

git clone https://github.com/MHSanaei/3x-ui
cd 3x-ui
git checkout 355bb4c9c001d252845b4f8bf3ae4225c2cd460e
hotspots analyze . --mode snapshot --explain-patterns --force

To run the same analysis on your own codebase, run hotspots analyze . --mode snapshot in any local git repo — no configuration required.

Hotspots highlights structural and activity risk — not “bad code.” Findings are a prioritization aid, not a bug predictor. Editorial policy →

Run this on your own codebase

Hotspots runs locally in under a minute — no account, no data leaves your machine.

macOS
$ brew install Stephen-Collins-tech/tap/hotspots
Linux / cargo
$ cargo install hotspots-cli
Run in any repo
$ hotspots analyze .
★ Star on GitHub

Related Analyses