Across 2,879 analyzed functions in serverless/serverless, 289 are rated critical and 786 sit in the fire quadrant — structurally complex and actively changing right now. Every function in the top five was touched in the last 11 days, which means the risk is not theoretical. I’d start with getAuthenticatedData in packages/sf-core/src/lib/auth/index.js, which carries an activity-weighted risk score of 19.06 against a cyclomatic complexity of 91 — a combination that makes each commit a live regression risk rather than routine maintenance.
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 |
|---|---|---|---|---|---|
getAuthenticatedData | packages/sf-core/src/lib/auth/index.js | 19.1 | 91 | 7 | 18 |
invoke_agent | packages/serverless/lib/plugins/aws/bedrock-agentcore/examples/python/strands-browser/test-invoke.py | 18.7 | 32 | 11 | 23 |
installRequirements | packages/serverless/lib/plugins/python/lib/pip.js | 18.5 | 71 | 5 | 58 |
compileFunction | packages/serverless/lib/plugins/aws/package/compile/functions.js | 18.2 | 162 | 5 | 41 |
authenticateInteractive | packages/sf-core/src/lib/auth/index.js | 18.1 | 46 | 6 | 25 |
Large Repo Analysis
serverless is a large repository. To stay within memory constraints, this analysis used hybrid touch mode: structural complexity — CC, ND, FO — is measured precisely for every function. Git activity is tracked at the function level (via git log -L) only for files with 5 or more commits in the last 30 days; other files use a file-level approximation. Rankings therefore surface functions that are both structurally complex and in the most actively-changing parts of the codebase. Dormant code with high structural complexity will rank lower than it would under a full per-function analysis — to surface it, run hotspots analyze . --per-function-touches on a machine with sufficient memory.
Codemod / Tooling Files in Results
The function invoke_agent is defined in packages/serverless/lib/plugins/aws/bedrock-agentcore/examples/python/strands-browser/test-invoke.py — an example script shipping alongside a new Bedrock AgentCore plugin rather than production library code. Its high structural metrics reflect the complexity of parsing a streaming SSE response inline in a test harness, not a production code path. If you want to exclude example scripts from future analyses, add the following to your .hotspotsrc.json: { "exclude": ["**/examples/**"] }. That pattern will suppress all files under any examples/ directory across the monorepo packages.
Repository snapshot
2,879 functions analyzed
Every function in this repository sits in either the fire or watch quadrant — there is no dormant debt to defer. The 786 fire-quadrant functions are structurally complex and have seen recent commit activity, which is an unusual distribution. It means the team is actively working in the most complex parts of the codebase right now, and the window between a confusing change and a user-visible defect is narrow.
High cyclomatic complexity — many independent execution paths, each a potential bug surface and required test case.Deeply Nested×5Deeply Nested
Control structures nested 4+ levels deep, making it hard to reason about the full execution state at inner branches.Exit Heavy×5Exit Heavy
Multiple return or throw paths dispersed through the body — each exit needs separate test coverage.God Function×5God Function
Calls an unusually large number of distinct functions (high fan-out), making it the structural centre of gravity for a subsystem.Long Function×4Long Function
Function body is too long to review in a single pass; likely contains multiple distinct responsibilities.
Every function in the top five carries the full complement of complex branching, deep nesting, multiple exit paths, and god-function coupling. Four of the five are also flagged as long functions. That uniform pattern across the top tier is worth paying attention to: these are not edge cases in obscure utilities, they are the functions that authenticate users and compile deployable infrastructure.
getAuthenticatedData — auth/index.js
This is the top-ranked function in the repository, and the source excerpt explains why the complexity is so high. getAuthenticatedData has to reconcile at least four distinct credential sources — two environment variables for V1 access keys, two for V2 license keys, an .serverlessrc file loaded from disk, and a fallback SSM parameter fetch — before it can construct its authenticatedData object. Each credential path branches independently, and the function handles org-level access key counts, dashboard feature flags, SSM resolver injection, and interactive prompts for missing credentials, all within a single async method.
A cyclomatic complexity of 91 means there are 91 independent execution paths through this function. Combined with a nesting depth of 7 and 18 distinct callees, a single misread of a null-check in one branch can silently leave another branch unreachable. The exit-heavy pattern means those 91 paths terminate in many different places, which multiplies the test surface considerably. This file has seen one commit in the last 30 days, 11 days ago, from a single author — and all of that file’s commit history is categorized as corrective work, meaning every recorded change has been a bug fix rather than a feature addition. That is a meaningful historical signal for prioritization even though it does not prove the current code is defective.
My recommendation: decompose getAuthenticatedData by credential source. Each of the four resolution strategies (environment variable, rc file, SSM, interactive prompt) could be extracted into its own async resolver that returns a normalized credential object or null. The top-level function then becomes an orchestration shell that iterates over resolvers in priority order. That alone would cut the cyclomatic complexity by more than half and make each path independently testable.
invoke_agent — test-invoke.py
The nesting depth of 11 is the standout number here. Reading the source excerpt, the structure is a streaming SSE response parser layered inside a chunk iterator layered inside a conditional stream check — each layer adding another level of indentation and another scope in which a reader must track state. The function parses data: lines from an SSE stream, extracts content block deltas for incremental display, identifies a final message structure, and falls back to accumulated text chunks if no final message arrives. That is three distinct result-assembly strategies in a single function body.
With a fan-out of 23, invoke_agent is calling into codec decoders, JSON parsers, UUID generators, the boto3 agent runtime client, and multiple branches of the event data model — more distinct dependencies than any other function in the top five relative to its size. In a Python file living under an examples/ path, this might look like acceptable test script complexity, but the metrics reflect real structural risk: a CC of 32 means a bug in any one of the stream-parsing branches could mask a regression in the others, and a nesting depth of 11 makes the control flow hard to trace without running it. This file was also touched 11 days ago by a single author, with all commits categorized as bug fixes.
The practical fix is to extract the SSE line parser and the event-data classifier into separate functions. invoke_agent should call parse_sse_stream() and receive a structured result, rather than managing the streaming protocol and the response semantics simultaneously.
installRequirements — pip.js
The fan-out of 58 is the highest in the top five by a wide margin and deserves direct attention. installRequirements calls into 58 distinct functions across the plugin infrastructure — it selects between pip and uv installers, resolves local packages, constructs Docker command arrays, handles two separate cache strategies (one of which is deprecated and emits a warning through two different logging APIs depending on whether the new log object or the legacy serverless.cli.log is available), and posts progress updates. The source excerpt alone shows branching on usingUv, options.dockerizePip, options.pipCmdExtraArgs, and the presence of the legacy --cache-dir flag — and that is just the first quarter of the function.
A cyclomatic complexity of 71 with a fan-out of 58 in a JavaScript file means that static analysis almost certainly undercounts the true coupling here. Dynamic property access on options and pluginInstance can hide dependencies that don’t show up in the fan-out metric, so 58 is likely a floor. Every new installer option, cache strategy, or Docker flag added to this function increases the test matrix exponentially. The file has one commit in the last 30 days and all recorded commits are bug fixes.
I’d start by extracting the Docker command construction and the cache-strategy selection into separate functions, each taking a typed options object. The deprecated --cache-dir branch is a good candidate for an early-return guard at the top of the function, which would also reduce the nesting that accumulates around it.
compileFunction — functions.js
A cyclomatic complexity of 162 is the highest raw structural complexity in the top five, and the function sits on the critical path of every serverless deploy invocation — it compiles a named Lambda function into a CloudFormation resource. The source excerpt shows the function managing handler vs. image validation, log group class conflicts, ECR image URI resolution, image config property mapping, memory size defaults, and a capacity-provider memory floor, all before it presumably reaches environment variables, IAM roles, and VPC configuration further down the function body.
At CC 162 there are 162 independent execution paths. With a fan-out of 41, each path can branch into a wide set of provider utilities, template builders, and error constructors. This file shows a higher review comment density (0.33) than the other top functions, which all sit at zero — suggesting it has attracted more review discussion historically, which makes sense given how much Lambda configuration surface it covers. The exit-heavy pattern compounds the CC: with many early throws and conditional returns, exercising every path in tests requires a large number of carefully constructed function config objects.
The most pragmatic decomposition here is vertical by resource concern: image configuration, memory resolution, log group setup, and IAM attachment are all self-contained enough to extract into dedicated compile helpers that compileFunction delegates to. Each helper can be tested with a narrow fixture, and the top-level function becomes a readable orchestration sequence.
authenticateInteractive — auth/index.js
authenticateInteractive lives in the same file as getAuthenticatedData, and the two functions share ownership, commit history, and structural patterns. The source excerpt shows this function managing the interactive login flow: it checks for a non-interactive environment and throws early, reads the rc config to count existing V2 org keys (which changes the prompt label), presents a four-option menu, then dispatches to separate flows for login/register, license key entry, purchase, and license explanation. The error handling wraps the logger.choose call in a try-catch that re-throws as a ServerlessError, and the answer dispatch is a multi-branch conditional that presumably deepens further down the function.
A cyclomatic complexity of 46 with a nesting depth of 6 and 25 callees makes this function the second most complex piece of auth logic in the repository. Because both getAuthenticatedData and authenticateInteractive are in the same file, a change intended for one can affect the other’s test coverage or introduce merge conflicts. The single-author, single-commit, bug-fix-only history on this file applies here too.
The clearest refactoring target is the answer-dispatch block: each authentication scenario (license key, login, purchase, info) is a well-defined state machine transition that could be extracted into its own handler function. authenticateInteractive would then become a prompt-and-dispatch shell with a much lower cyclomatic complexity, and each scenario handler could be tested in isolation without needing to mock the full interactive prompt chain.
What the context functions tell me
The context_only data includes loadDashboardData in packages/sf-core/src/lib/resolvers/manager.js, which has been touched twice in the last 30 days and was last changed 0 days ago — the most recently active function in the dataset. Its structural complexity is moderate (CC 12, nesting depth 2), so it sits in the watch quadrant rather than fire, but its recency and proximity to the auth layer mean I’d keep an eye on it if the auth refactoring work moves into the resolver manager. The findAwsSamProjects and findCloudFormationProjects functions in the MCP package are also watch-quadrant, active but structurally simple — no action needed there.
Patterns Found
Antipatterns detected across the top functions in this snapshot:
| Pattern | Occurrences |
|---|---|
complex_branching | 5 |
deeply_nested | 5 |
exit_heavy | 5 |
god_function | 5 |
long_function | 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.
Reproduce This Analysis
git clone https://github.com/serverless/serverless
cd serverless
git checkout 1dadff40c07e63da575c942f4a94850347e84e2b
hotspots analyze . --mode snapshot --explain-patterns --force --hybrid-touches 5
To run the same analysis on your own codebase, run hotspots analyze . --mode snapshot in any local git repo — no configuration required.
I use Hotspots to highlight structural and activity risk — not “bad code.” I treat these findings as a prioritization aid, not a bug predictor. Editorial policy →