At commit 157c934, harness/harness has 13,307 analyzed functions, 1,002 of which score in the critical band — and every function in the top five is a ‘fire’-quadrant case, structurally complex and touched within the last 30 days. I would start with usePullReqComments in web/src/components/DiffViewer/usePullReqComments.tsx, which carries an activity-weighted risk score of 19.74 — the highest in the repository — driven by a cyclomatic complexity of 81 and a fan-out of 111 distinct function calls. That combination means any engineer shipping a change to the diff viewer this week is working inside a function that has 81 independent execution paths and couples to 111 other callees. Harness is a developer platform covering code review, CI/CD pipelines, and artifact management; the fact that its most urgent structural risk is concentrated in the pull request UI, not in its Go services, is the first thing worth understanding.
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 |
|---|---|---|---|---|---|
usePullReqComments | web/src/components/DiffViewer/usePullReqComments.tsx | 19.7 | 81 | 7 | 111 |
ChangesSection | web/src/pages/PullRequest/Conversation/PullRequestOverviewPanel/sections/ChangesSection.tsx | 19.7 | 90 | 15 | 25 |
AddUpdatePipeline | web/src/pages/AddUpdatePipeline/AddUpdatePipeline.tsx | 17.1 | 35 | 5 | 57 |
MarkdownViewer | web/src/components/MarkdownViewer/MarkdownViewer.tsx | 16.9 | 50 | 5 | 53 |
usePRChecksDecision | web/src/hooks/usePRChecksDecision2.tsx | 16.5 | 21 | 8 | 15 |
Large Repo Analysis
harness 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.
Repository overview
13,307 functions analyzed
The quadrant distribution here is striking: every analyzed function is either ‘fire’ or ‘watch’ — there are zero ‘debt’ and zero ‘ok’ functions. That means the repository has no dormant structural complexity sitting undetected; all the high-complexity code is also the actively-changing code. For engineers triaging where to spend refactoring time, this makes the priority list unusually clean: the critical band is also the live-regression band.
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.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×5Long Function
Function body is too long to review in a single pass; likely contains multiple distinct responsibilities.Exit Heavy×4Exit Heavy
Multiple return or throw paths dispersed through the body — each exit needs separate test coverage.Middle Man×1Middle Man
Mostly delegates to one other function without adding meaningful logic — a refactoring candidate for removal or consolidation.
All five top hotspots share every major antipattern: complex branching, deep nesting, god-function coupling, excessive length, and — in four of the five — multiple exit paths that expand test-coverage surface. These are not isolated quirks; they form a consistent structural signature across the pull request and pipeline UI.
usePullReqComments — usePullReqComments.tsx
This React hook orchestrates comment creation, update, deletion, link-copying, and DOM mutation for the diff viewer — all inside a single function body. The source excerpt confirms it: it wires together useCommentAPI, usePullReqActivities, useConfirmAct, useToaster, useStrings, useAppContext, and route helpers before it even begins the main comment-management logic. That accounts for a fan-out of 111, the highest in the top five and a strong god-function signal — a change to any one of those 111 callees can surface as unexpected behavior here.
The cyclomatic complexity of 81 means there are 81 independent execution paths through this hook. In a React context, many of those paths live inside useCallback and useMemo closures, which makes them harder to reach in isolation with unit tests. The nesting depth of 7 is visible in the excerpt’s updateDataSelectedCount callback, which branches on DOM presence, then on selection state, then on count thresholds — nested conditionals that each add to the path count.
The exit-heavy pattern compounds this: multiple early returns scattered across closures mean coverage tools will report line coverage without actually exercising all decision points. Combined with only a single author having touched this file in the last 90 days, the institutional knowledge required to safely modify this hook is concentrated in one person.
Recommendation: Extract the DOM-mutation logic (updateDataSelectedCount and any similar row-state helpers) into a standalone utility, and pull the comment API wiring into a dedicated useCommentOperations hook. That alone would reduce the fan-out visible at the top level and bring the cyclomatic complexity below 50 — halving the test-path burden without changing any behavior.
ChangesSection — ChangesSection.tsx
With a cyclomatic complexity of 90 and a max nesting depth of 15, ChangesSection is the structurally densest function in the repository’s top five. It renders the approval-and-review-decision panel on the pull request overview page, and the source excerpt reveals why the numbers are this high: it simultaneously resolves code-owner approval state, reviewer approval state, default-reviewer policy state, outdated-SHA detection, and change-request status — all in a single component body, before a single pixel is rendered.
A nesting depth of 15 is a strong refactoring signal by any standard. Visually, that means conditional blocks nested inside filter callbacks nested inside reduce operations nested inside conditional renders — chains of logic where the outer context is easy to lose track of by the time you reach the innermost branch. The extractInfoForCodeOwnerContent inner function visible in the excerpt is itself doing multi-level branching, which contributes directly to that depth count.
The exit-heavy pattern here — four of the five top functions carry it, and this one is no exception — means there are multiple return paths that short-circuit the render cycle under different approval conditions. Each one is a required test case that is rarely written in practice for components this large.
Recommendation: Decompose the approval-state derivation into separate selector functions — one for code-owner state, one for reviewer state, one for default-reviewer policy — and call them from the component rather than computing inline. The component itself should only be responsible for choosing which UI state to render, not for deriving all the inputs to that decision. This would reduce nesting depth to well below 8 and make each approval-logic path independently testable.
AddUpdatePipeline — AddUpdatePipeline.tsx
This component manages the full lifecycle of creating or editing a CI pipeline: fetching pipeline metadata, loading YAML file content from the repository, determining whether the pipeline already exists, detecting YAML version (v0 Drone config vs. v1), tracking editor dirty state, and wiring up save/run/save-and-run action options. The source excerpt confirms all of this happens in a single component body, with multiple useEffect hooks managing side effects sequentially.
A fan-out of 57 is the god-function signature in a different form from usePullReqComments — instead of 111 callees, here we have 57, but the blast-radius principle is the same: the component is coupled to the routing layer, the repository metadata layer, the pipeline API, the YAML content API, the editor reference, the toaster, and the modal system simultaneously. A cyclomatic complexity of 35 is high (the threshold for immediate attention), but it is lower than the two PR-review functions above because the branching is spread across useEffect callbacks rather than concentrated in a single control-flow block.
The long_function and exit_heavy patterns here suggest the component body is doing work that belongs in custom hooks. The useEffect chain for YAML version detection, existence checking, and initial content loading are each independently testable pieces of logic that currently share the component’s closure scope.
Recommendation: Extract the pipeline-load and YAML-resolution logic into a usePipelineEditor hook. This would reduce the component’s fan-out to the hook boundary, lower the cyclomatic complexity of the component itself to near single digits, and give the load/resolve logic its own test surface without React rendering overhead.
MarkdownViewer — MarkdownViewer.tsx
MarkdownViewer renders repository markdown with support for image zoom, mention substitution, suggestion blocks, and routing-aware link rewriting. The source excerpt shows the mention-substitution logic in detail: a custom rehype plugin (rehypeReplaceMentions) that walks the AST of every rendered paragraph, pattern-matching against a three-node sequence of text, anchor, and text children to find @[email] mention syntax and replace it with display names — including XSS sanitization of the display name before injection.
A cyclomatic complexity of 50 here reflects how many distinct content scenarios the component must handle: standard markdown, images (with zoom state), mentions (with email-to-name resolution), suggestion blocks with checksums, dark mode, max-height constraints, and routing context for relative links. Each of these adds branching, and the AST-walk loop adds more — the for loop over paragraph children with a three-way type-check guard contributes multiple paths per paragraph node.
The fan-out of 53 indicates the component reaches broadly into the application’s utilities and plugin ecosystem. A change to the mention resolution logic, the routing helpers, or the suggestion-block rendering can each introduce regressions that only manifest in specific markdown content combinations — exactly the kind of bug that is hard to catch without comprehensive content-fixture tests.
Recommendation: Pull rehypeReplaceMentions out of the component body into a standalone, exportable rehype plugin file. It has no dependency on React state and can be unit-tested against raw AST fixtures without mounting a component. That extraction alone reduces the component’s cyclomatic complexity by removing the entire AST-traversal branch tree, and makes the XSS-sanitization logic independently auditable.
usePRChecksDecision — usePRChecksDecision2.tsx
The filename usePRChecksDecision2.tsx is worth pausing on — the 2 suffix suggests this is a second iteration of an earlier hook, implying the team has already been through one version. That naming pattern often signals an API that is still settling, and a cyclomatic complexity of 21 in a hook that is actively being iterated on is a live regression risk: the next change to CI check-status handling lands in code that already has 21 independent execution paths.
The source excerpt shows the structural driver: a switch statement over ExecutionState values inside a for loop over check results, followed by a cascading if/else if chain that maps count combinations to color, background, and message state. A nesting depth of 8 comes from that combination — the switch inside the for inside the useMemo callback, with the if/else if chain at the same depth. The TODO comment in the excerpt (// TODO: This needs to be revised, as green is shown initially) confirms the maintainer is already aware of a correctness concern in the state initialization, which makes the complexity here a live issue rather than abstract debt.
Recommendation: Replace the if/else if cascade over count combinations with a priority-ordered lookup table — an array of [condition, result] pairs evaluated in order. This converts O(N) branching into a data-driven structure that is easier to read, extend, and test, and it directly reduces the cyclomatic complexity. Address the TODO around initial state before the next change lands; the interaction between initial complete: true state and the async data fetch is a latent correctness risk in any execution path where data arrives slowly.
What the pattern distribution tells me
Every function in the top five carries the god-function pattern — not because any individual one does something unusual, but because the pull request and pipeline UI in harness aggregates a large number of concerns at component and hook boundaries rather than delegating them. Fan-out values of 111, 57, and 53 are not outliers; they reflect a consistent architectural pattern where the top-level component or hook is the integration point for everything its feature needs.
That is a rational short-term approach to building a complex product quickly. The cost shows up in these metrics: when the integration point is also the place where business logic, DOM manipulation, API calls, and rendering all live together, every change to any one of those concerns touches a function that is already hard to reason about in its entirety. The four exit-heavy functions in this list mean that test suites covering these components line-by-line are still missing decision-point combinations that can only be reached by specific sequences of user interaction or data state.
The context-only functions — getString, useQueryParams, onSuccess, useRoutes, clear — are all ‘watch’-quadrant: active but structurally simple. They bear monitoring but are not refactoring priorities at their current complexity levels.
Patterns Found
Antipatterns detected across the top functions in this snapshot:
| Pattern | Occurrences |
|---|---|
complex_branching | 5 |
deeply_nested | 5 |
god_function | 5 |
long_function | 5 |
exit_heavy | 4 |
middle_man | 1 |
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.
See more analyses with these patterns: complex_branching, deeply_nested, exit_heavy, god_function, long_function.
Reproduce This Analysis
git clone https://github.com/harness/harness
cd harness
git checkout 157c934a790d264b254f64e1762df85bac045d25
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 →