headscale’s hscontrol/policy/v2 package is the center of gravity for structural risk: the top two hotspots, compileSSHPolicy and ViaRoutesForPeer, both sit in the fire quadrant — complex code that is being actively modified right now, not dormant debt waiting to be addressed. compileSSHPolicy carries the highest activity risk in this snapshot at 17.36, and ViaRoutesForPeer follows at 17.1 with 2 commits in the last 30 days, making both live regression risks. Across 1,588 total functions, 174 are rated critical — and the policy layer accounts for the top two.
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 |
|---|---|---|---|---|---|
compileSSHPolicy | hscontrol/policy/v2/filter.go | 17.4 | 16 | 7 | 40 |
ViaRoutesForPeer | hscontrol/policy/v2/policy.go | 17.1 | 13 | 7 | 24 |
HandleNodeFromAuthPath | hscontrol/state/state.go | 14.5 | 7 | 4 | 43 |
New | integration/tsic/tsic.go | 14.4 | 8 | 5 | 25 |
Serve | hscontrol/app.go | 14.3 | 7 | 4 | 119 |
Hotspot Analysis
compileSSHPolicy — hscontrol/policy/v2/filter.go
Based on its name and location in the policy/v2 filter layer, compileSSHPolicy likely translates high-level SSH access policy definitions into an evaluated, executable rule set — a function whose correctness is foundational to access control. The metrics tell a demanding story: cyclomatic complexity of 16 means 16 independent execution paths that each require test coverage, a maximum nesting depth of 7 indicates deeply interlocked conditional logic that is hard to reason about locally, and a fan-out of 40 means this function directly calls 40 distinct functions — making it a god function whose changes can ripple across a wide surface area. With 1 commit in the last 30 days, it is in the fire quadrant: structurally complex code that is actively changing, which is the definition of live regression risk.
Recommendation: Before the next modification, add characterization tests that exercise each of the 16 cyclomatic paths — these will act as a safety net given the fan-out of 40. Then apply extract-method refactoring to decompose the function into smaller, independently testable units, targeting the deeply nested blocks (ND 7) as the first extraction candidates.
ViaRoutesForPeer — hscontrol/policy/v2/policy.go
ViaRoutesForPeer, in the core policy evaluation file, most likely determines which routes are reachable for a given peer — a routing-decision function whose output directly shapes network topology in a Tailscale-compatible control plane. Its cyclomatic complexity of 13 and nesting depth of 7 reflect the branching required to evaluate peer-specific routing rules, while a fan-out of 24 indicates broad coupling to other policy and routing components. Critically, it has been touched 2 times in the last 30 days and was last changed just 14 days ago, placing it firmly in the fire quadrant — a function that is both structurally intricate and under active development, compounding the chance that a change introduces a regression.
Recommendation: Given the fan-out of 24, map the blast radius before any further changes — identify which callers depend on the routing decisions this function returns. Consider splitting peer-route resolution logic from the rule-matching logic to reduce both fan-out and cyclomatic complexity, making each piece independently testable.
HandleNodeFromAuthPath — hscontrol/state/state.go
HandleNodeFromAuthPath lives in the core state management layer and orchestrates what happens when a node completes an authentication path — registering it, updating its state, and triggering downstream policy evaluation. Its metrics reflect that orchestration: a fan-out of 43 means this function directly calls 43 distinct functions, placing it in god_function territory, while a nesting depth of 4 indicates conditional branching that tightly couples auth outcomes to state transitions. A cyclomatic complexity of 7 is moderate on its own, but combined with broad fan-out it creates a function where modifications carry an outsized blast radius.
Recommendation: Audit the 43 callees and group them by concern — auth validation, state persistence, and policy notification are likely distinct responsibilities. Extracting each group into a named orchestration step would reduce direct fan-out and make each step independently testable.
New — integration/tsic/tsic.go
New is a constructor function in the integration test infrastructure for the Tailscale client (tsic). That a test constructor appears in the top five reflects how much the setup logic has grown: a cyclomatic complexity of 8 and nesting depth of 5 indicate branching over test configuration options, while a fan-out of 25 shows it wires up a broad set of test dependencies. Complex test constructors slow down writing new integration tests and obscure what each test actually requires.
Recommendation: Consider adopting a functional options or builder pattern for tsic.New to flatten the conditional branching. This would reduce both CC and ND, making it easier to compose only the configuration a given test needs without changing test coverage.
Serve — hscontrol/app.go
Serve is the top-level server function in hscontrol/app.go, responsible for wiring up and launching the full control plane — registering routes, initialising services, and blocking until shutdown. Its fan-out of 119 is the largest in this snapshot by a significant margin, a direct reflection of how much Serve must coordinate: it is a god_function whose breadth of coupling is expected for a top-level bootstrap function, but one that makes any change to the startup sequence a high-blast-radius modification. With CC 7 and ND 4, the structural complexity is moderate; the risk here comes from coupling width rather than deep conditional logic.
Recommendation: Serve’s broad fan-out is partly inherent to top-level bootstrapping, but grouping related setup calls into named initialisation functions — such as initGRPC, initMetrics, or initStateWatcher — would reduce direct fan-out, improve readability, and make it possible to test each subsystem’s startup logic in isolation.
Patterns Found
Antipatterns detected across the top functions in this snapshot:
| Pattern | Occurrences |
|---|---|
exit_heavy | 5 |
god_function | 5 |
long_function | 5 |
deeply_nested | 3 |
complex_branching | 2 |
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
- compileSSHPolicy in hscontrol/policy/v2/filter.go has a fan-out of 40 and was touched within the last 30 days — map its callee graph before making any further changes to contain blast radius.
- ViaRoutesForPeer has been touched twice in the last 30 days and last changed just 14 days ago, with a nesting depth of 7 and cyclomatic complexity of 13 — add path-coverage tests to guard against regressions during active development.
- All 174 highest-risk functions and both top hotspots are in the fire quadrant, meaning headscale has zero functions in the debt quadrant — structural risk is concentrated in actively changing code, not dormant legacy logic, so refactoring pressure is time-sensitive rather than deferred.
Reproduce This Analysis
git clone https://github.com/juanfont/headscale
cd headscale
git checkout af7e7a45604dd7fe5e0c64d5e2c907a3108c7c36
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 →