The Drift Log · Audit, then actually repair
Consolidating Duplicate Internal Utilities into Monorepo Packages
30 October 2026 · 4 min read · 930 words · established

A concrete method for auditing, supersetting, and consolidating duplicate monorepo utility functions without breaking subtle consumer invariants.
The retry loop in packages/billing handles exponential backoff, jitter, and HTTP 429 status codes. The retry loop in packages/auth handles backoff and jitter, but ignores status codes because it wraps an internal gRPC client. The one in apps/dashboard is eight lines of setTimeout inside a while loop that someone copied from Stack Overflow in 2021.
There are eleven implementations of this loop across the monorepo. None of them share imports.
When an outage forces an update to how the company handles downstream rate limits, engineering spends three days patching five of those implementations. The other six remain invisible until they fail in production under load.
Code reuse audit reports usually label this technical debt and recommend creating a shared library. Most teams never do it. The cost of auditing every variation, designing a superset interface, and proving that the swap will not break edge cases feels higher than the cost of living with the duplicates.
That calculation is wrong, but only if you approach consolidation as an extraction problem rather than a design problem.
Why Duplicate Utilities Multiply
Engineers do not copy-paste code because they are lazy. They copy-paste code because finding, assessing, and modifying an existing utility inside a large codebase carries high cognitive overhead and social friction.
If a developer in services/notifications needs an ISO timestamp parser that tolerates microsecond precision, searching the codebase reveals four candidates:
- One depends on a legacy date library.
- One throws on invalid input instead of returning
null. - One lives in
packages/shared-uiand pulls in React as a peer dependency. - One lives in another team's private service directory.
Importing candidate 3 bloats the service bundle. Importing candidate 4 creates an unmaintained cross-boundary dependency. Modifying candidate 2 to return null risks breaking the three consumers who rely on the thrown error.
Writing a fifth implementation takes twenty seconds and passes CI immediately.
This is how technical debt compounds. The friction of safe reuse is higher than the friction of safe duplication. Over time, basic operational logic drifts across services, and monorepo boundaries become purely cosmetic.
Mapping the Surface: The Signature Matrix
Extracting a shared utility cannot begin with an idealized rewrite. It must begin with an inventory of reality.
When executing a repository audit for duplicate functions, extract the AST or signatures of all eleven candidates into a single matrix. You are looking for three properties:
- Input permutations: What types and structures does each variant accept?
- Error contracts: Does the helper return
undefined, return an error tuple, or throw? - Hidden invariants: Does the function rely on ambient state, timezone assumptions, or specific logger interfaces?
// Variant A: services/web/src/utils/retry.ts
async function withRetry<T>(fn: () => Promise<T>, attempts = 3): Promise<T>;
// Variant B: packages/billing/src/lib/backoff.ts
async function retryWithBackoff<T>(
fn: () => Promise<T>,
opts: { retries: number; delayMs: number; shouldRetry?: (err: unknown) => boolean }
): Promise<T>;
In this case, Variant B is a functional superset of Variant A. Variant A can be fully expressed as Variant B with default options.
The canonical utility must satisfy the union of all eleven requirement sets without carrying their environment baggage. It must be zero-dependency, pure, and strictly typed. If a variant requires custom logging, accept an optional logger callback rather than importing a logging singleton.
Constructing the Invariant Test Suite
Before swapping a single line of code in consumer packages, the new package needs a test suite derived directly from the call sites it will replace.
Do not write tests only for the new, elegant API. Write unit tests that match the exact behavioral oddities of the existing implementations:
- If the auth service expected the helper to throw a specific error instance on exhaustion, write a test for that.
- If the billing service expected a delay of
0on the first attempt, write a test for that. - If the dashboard client passed non-integer attempt counts, test the coercion.
Once the invariant suite passes, the refactoring step is mechanical. You are not changing logic in eleven services; you are changing eleven call sites to point to a consolidated package whose behavior is already verified against their requirements.
This approach transforms refactoring from a high-risk manual sweep into an automated migration. As detailed in our guide on closing the loop from repository audit to merged repair, static analysis is only useful when it generates verified, merge-ready changes rather than static warnings.
Where Consolidation Fails
Consolidation fails when teams try to consolidate utilities that look identical syntactically but differ semantically.
A common example is validation. A validateEmail function in an ingestion worker might intentionally accept loose formatting to avoid rejecting valid customer inputs. A validateEmail function in a compliance export pipeline might strictly enforce RFC 5322.
Merging these into a single utility creates a coupling problem. A change made to satisfy the export pipeline silently breaks edge-case ingestion.
If two functions require conflicting invariant assertions that cannot be resolved through clean configuration parameters, do not merge them. Keep them separate, name them according to their domain constraints (e.g., isPermissiveEmail vs isStrictRfcEmail), and document the difference.
What to Do on Monday
Pick one ubiquitous helper in your monorepo. Retries, deep object cloning, and string truncation are good candidates.
- Locate every instance. Run a structural search across the codebase and list every file implementing that logic.
- Extract the signatures. Document the inputs, outputs, and failure modes across all variants.
- Build the superset module. Create an isolated package in your monorepo (e.g.,
packages/resilienceorpackages/primitives) containing a pure, typed implementation that satisfies all identified use cases. - Draft the call-site PRs. Replace one consumer at a time, starting with the simplest variant. Run that consumer's existing integration tests.
Stop discussing technical debt in the abstract. Find the eleventh retry loop, extract the contract, and delete the copies.
Keep reading
Next in the log
- Scaffolding Surgical Refactor Branches from Static Audit Outputs
Turn static analysis reports into merged fixes by clustering diagnostics into bounded, single-file branches with attached test verification.
- Converting Static Debt Reports into Automated Refactor PRs
Static analysis alerts rot in unread dashboards unless bound to single-rule AST transforms with strict pull request constraints.
- Why Rewrites Win Over Audits and How to Change the Math
Standard audits catalogue structural decay without offering an extraction path. Auditing for capability makes incremental refactoring viable.
The Strategic Master Library · written and reviewed under the house's own epistemic rules: nothing claimed that we cannot show.