How SCARF finds code that can safely go away

Meta’s Systematic Code and Asset Removal Framework (SCARF) was originally built to manage product deprecations end to end. But cleanup of a deprecated product only gets you so far when code and assets reference each other in ways that are hard to see statically. SCARF’s dead code subsystem attacks that problem by combining static analysis with runtime and application-level signals, then automatically opening change requests that delete the unreachable code.

The core idea is an augmented dependency graph. SCARF extracts a per-language dependency graph from compilers via Glean, then layers on domain-specific usage information: whether an API endpoint actually appears in operational logs, whether an internal tool’s script is ever invoked, whether a template hook is used by the Instagram Django backend’s URI handler and routing, and whether a dispatch method is referenced by Async, Meta’s deferred job execution service. The point is to capture dynamic references that a purely static view would miss.

SCARF works across languages — client code in Java, Objective-C, and JavaScript, server code in Hack, and backend infrastructure in Python — because those pieces all belong to the same logical dependency graph when connected through APIs and other cross-language references. It also operates at symbol granularity rather than file granularity. An unused variable inside a function gets its own fully qualified symbol, so cleanup can be much finer than deleting whole files.

From individual symbols to whole graphs

Early versions of SCARF analyzed each discoverable symbol individually. At runtime, classifiers queried for static and dynamic references to find “dead root nodes” — code with no inbound dependencies. That avoided the upfront cost of constructing a complete dependency graph and made it easy to prototype new classifiers over small subsets of the codebase.

The tradeoff was end-to-end latency. Building the full augmented graph requires processing all indexed code plus the data analysis pipelines that supply metrics, which is expensive but pays off in coverage. Switching from symbol-by-symbol analysis to whole-graph analysis increased dead code removed from one of Meta’s largest codebases by nearly 50%. The system also gained better visibility into how much code is alive, how much is dead, and how much gets removed per pass.

Once the full graph is in place, SCARF runs a garbage collection pass to identify unreachable nodes and subgraphs, including cycles where different parts of the codebase depend on each other. Deleting entire subgraphs speeds up deprecation work and is more convenient for the engineers using the automation.

Teaching the graph about application semantics

A static dependency edge is not always a blocker for deletion. Consider a class PhotoRenderer that is only referenced in a conditional branch. If nothing ever instantiates the class, the branch is provably dead and both the reference and the class can be removed — SCARF can inline away the code based on a rule derived from Python’s semantics.

if isinstance(renderer, PhotoRenderer):
    return renderer.render_photo()
else:
    return renderer.render_generic()

return renderer.render_generic()

Other rules come from application semantics rather than language semantics. A controller class referenced via a URI dispatch dictionary, for example, cannot be proven dead from Python semantics alone. But if operational logs show the /photos/ endpoint never receives requests, SCARF can safely remove the dictionary entry and the corresponding controller code.

uri_dispatch = {
  '/home/': HomeController,
  '/photos/': PhotosController,
  ...
}

Automated change requests

SCARF relies on CodemodService, Meta’s internal service for deploying configuration-driven code changes at scale. SCARF was the first fully automated, company-wide use of CodemodService, which now powers hundreds of other automated change types — code formatting, experiment cleanup, API migrations, and stronger typing in partially typed languages like Python and Hack.

Each change request SCARF opens includes a human-readable description of the analysis that proved the targeted code dead, so engineers can review the reasoning before approving. In some languages the analysis is trusted enough that change requests merge without human review.

Scale and safety

SCARF now analyzes hundreds of millions of lines of code. Over five years it has automatically deleted more than 100 million lines across over 370,000 change requests. When engineers catch false positives during review, the cases are triaged and the analysis is improved — typically by adding a new source of dynamic usage to the augmented graph.

Some incorrect deletions do reach production. Meta’s rapid release infrastructure catches those problems, and the company treats such incidents seriously. As a safety fallback beyond Glean-derived graphs and dynamic usage augmentations, SCARF searches for textual references through BigGrep. That catches code and data referenced by name from other languages, including string-based dispatch and uses of eval. The result is more false negatives, but far fewer false positives — the more serious failure mode when automating deletion.

Limits of the automation

Dead code removal doesn’t fully solve product deprecation. Interconnectivity between systems remains a challenge, and SCARF’s coverage across all of Meta’s languages, systems, and frameworks is still being improved. The harder problem is accurately capturing every way code and data get used.

There is also a practical point about developer workflow: engineers can sometimes move faster than the automation. If a change makes a whole subgraph unreachable, the engineer who made that change can delete the code immediately rather than wait for SCARF to index the new state, analyze it, and open its own change request. Manual deletion still wins in those cases.