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
| Function | File | Risk | CC | ND | FO |
|---|---|---|---|---|---|
answerCallback | web/service/tgbot.go | 21.0 | 101 | 9 | 116 |
fromParamLink | frontend/src/models/outbound.js | 18.2 | 36 | 9 | 12 |
UpdateInboundClient | web/service/inbound.go | 16.8 | 11 | 6 | 37 |
useInbounds | frontend/src/pages/inbounds/useInbounds.js | 16.5 | 32 | 4 | 30 |
UpdateInbound | web/service/inbound.go | 16.4 | 10 | 6 | 24 |
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.
fromParamLink — frontend/src/models/outbound.js
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:
| Pattern | Occurrences |
|---|---|
complex_branching | 5 |
exit_heavy | 5 |
god_function | 5 |
long_function | 5 |
deeply_nested | 4 |
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 inweb/service/tgbot.go— its 116-callee fan-out means any change today has an unusually wide blast radius across the service layer. fromParamLinkinfrontend/src/models/outbound.jshas 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 →