Skip to main content
October 20, 2026

Using AI to Delete Code: Agentic Tech Debt Cleanup at Uber

Daniel Ramos

Sr. Software Engineer

Diego Marcilio

Sr. Software Engineer

Nikhil Parasaram

Software Engineer

1+
Robots clean tangled server wires labeled with coding issues like stale logic, dead branches, and unused test cases.
Share this article

Introduction

At Uber, feature flags (dynamic Boolean parameters) are central to how we ship software. They let engineers roll out features gradually, run experiments, and roll back quickly if something goes wrong. Today, we manage thousands of feature flags across our mobile apps and back-end services, powering experiences for millions of people around the world.

A problem with feature flags is that once they’re fully rolled out, the leftover scaffolding (like conditional branches and tests) often stays behind and quietly becomes tech debt. As stale flags accumulate, they make the codebase harder to understand and test. Even AI agents are affected by this debt, since they spend time parsing dead code, leading to longer and more expensive sessions.

Removing stale flags can look like a simple search-and-delete problem, but a single cleanup can touch dozens of files across boolean logic, helper functions, call sites, configs, and tests. Some tests can be deleted, while others need to be rewritten around the new default behavior. Doing this safely  and automatically requires code understanding, judgment, and careful validation across the system.

In this blog, we share how we built an AI system that autonomously deletes stale production code at scale and what we learned from bringing agentic workflows into the software development lifecycle.

Background

Feature flag cleanup often gets deprioritized after a rollout: teams move on to the next feature, and removing the old flag competes with new product work. At Uber’s scale, that tradeoff adds up quickly. Flags are created and rolled out continuously across many teams and repositories, turning small cleanup delays into a large backlog.

This is where Uber’s Developer Platform comes in. We apply our expertise in AI for code to turn stale flag cleanup into a scalable automated workflow, with safety checks, validation, and review points built into the process.

Why Cleanup Is Harder Than It Looks

The basic idea seems straightforward: figure out if a particular flag is stale, find where it’s used, and inline its permanent value. But even a simple case unravels quickly. Consider the illustrative example in Figure 1.

Go code snippet defining order processing functions with conditional pipeline selection and configuration.

Figure 1: Example usage of a feature flag in an order-processing flow.

In Figure 1, The new_order_pipeline flag controls whether requests are routed through the new pipeline or the legacy pipeline, with the flag value passed from configuration into the processing function.

Let's assume that the flag cfg.UseNewPipeline is fully rolled out, and always true. Inlining the boolean call (line 3) with true is straightforward. But what follows is a chain of propagation decisions:

  1. The processOrder function takes useNewPipeline as a parameter on line 9. In this example, the only shown caller passes cfg.UseNewPipeline on line 20. If every caller now passes the rolled-out value, the parameter can be removed: it only exists to carry the flag. But in real code, boolean parameters may have multiple callers or sources, so the cleanup tool must inspect all call sites first.
  2. After the value is propagated into processOrder, the legacy branch on line 13 becomes unreachable and can be deleted. Looking at lines 9–14 alone, though, both branches still appear possible because the function still accepts a boolean. 
  3. The config field UseNewPipeline on line 3  may also be removable, but only if no other code reads it or depends on the config struct’s shape.

In this example, the flag value starts in config initialization and reaches the dead code through a struct field, a call site, and a function parameter. Inlining the constant is mechanical, but deciding what to remove next requires context: who calls the function, what the tests cover, and whether simplifying the struct or signature is safe. Static tools handle deterministic rewrites well; AI is most useful for propagation decisions.

Architecture (AutoClean: The System)

To automate feature flag cleanup, we built AutoClean, a system that connects to Uber’s feature flag management system, detects stale flags, and turns them into clean, tested diffs ready for human review. AutoClean runs continuously across Uber’s monorepos, handling the cleanup lifecycle end-to-end: identifying flags that are safe to remove, rewriting the code, fixing CI breakages, validating the final diff, and routing it to engineers for review. In this blog, we focus on the core transformation and validation engine, where the most interesting agentic reasoning happens.

Workflow for removing stale feature flags, detailing detection, code rewrite, healing, validation, and review stages.

Figure 2: AutoClean overview.  AutoClean starts from Uber’s feature-flag system of record, identifies a stale flag, and runs it through the core transformation and validation engine: rewriting the code, fixing CI failures, and validating the final diff. Once the diff is review-ready, AutoClean routes it to the owning team and keeps it fresh until a human reviewer makes the final decision.

Stage 1: Agents and PolyglotPiranha Rewrite the Code

We use an agent that handles the cleanup autonomously. The agent has access to the full codebase and a scoped set of tools: file search, code editing, and, importantly, a boolean algebra and conditional simplification tool built on top of PolyglotPiranha, our lightweight code transformation engine.

Using PolyglotPiranha is an important design choice. AutoClean is an agentic system, but that doesn’t mean the agent should handle every step. We want to keep as much determinism in the loop as possible, especially for transformations that can silently change program behavior if they’re wrong. Boolean algebra is well suited to deterministic handling, since a small mistake in simplifying a condition can change which branch executes in production. Rather than asking the agent to perform these rewrites itself, we give it access to PolyglotPiranha through MCP® (Model Context Protocol).

Going back to our example, here’s what the agent does:

  1. Finds the flipr.GetBool("new_order_pipeline", false) call and replaces it with true.
  2. Calls PolyglotPiranha to simplify the resulting expressions and conditionals.
  3. Traces the value into processOrder, sees that useNewPipeline is always true, removes the parameter, and deletes the dead else branch.
  4. Continues iterating until no further cleanup remains.

Stage 2: Fixing What Broke in CI

After the agent finishes the rewrite, AutoClean raises a diff and lets CI run. Agents can run tests locally while they work, and they do. But a single flag cleanup can touch code that fans out across the monorepo. What matters isn’t just whether the nearby tests pass, but whether the change still builds and passes everywhere. CI is the source of truth for that.

Our CI runs a large set of jobs across many targets, and it surfaces issues the rewrite loop may not see locally.  If CI fails, AutoClean reads each CI failure and routes it to a fixer. Cheap deterministic fixers run first. They handle mechanical cases like removing a now-unused default or clearing a lint error, and they pass anything they don’t recognize down the line. The harder cases, like deciding whether a test should be deleted or rewritten, go to agentic fixers. After each fix, we push an update, CI runs again, and we keep looping until the diff is green or we run out of attempts.

We scope each fixer tightly so it can only touch what it is meant to. A test fixer can only edit test files, for example. Without that limit, an agent will sometimes make a failing test pass by changing the production code underneath it, which is exactly the outcome we’re trying to prevent.

Stage 3: Validating the Result 

After CI is green, AutoClean has a diff that builds and passes tests. But that doesn’t always mean the cleanup is safe to send to a human reviewer.

A stale flag cleanup can pass CI and still be wrong in more subtle ways. For example, the agent might fix a test by deleting it, even though the test was covering behavior that should still be preserved. It might also produce an incomplete cleanup by stopping too early, such as inlining a flag’s rolled-out value without fully propagating the simplification through the surrounding code. Neither case is necessarily captured by CI checks.

Therefore, before sending the diff out for review, AutoClean runs an additional validation step. The first layer is static checks. These catch mechanical cleanup issues that we can encode directly, such as a trivial boolean function that now always returns true, a conditional that should have been simplified, or dead code that’s still reachable only through the stale flag path.

After the static checks pass, the diff goes through our agentic validation layer, called CodeCourt. CodeCourt handles cases that are harder to encode as deterministic rules.

If a diff makes it past these layers, it has passed our automated correctness checks and is ready for human review. The human reviewer can focus on business-level judgment, like “should we remove this flag now?” rather than scanning for mechanical correctness, like “did the cleanup accidentally change behavior?”

Flowchart showing CI validation with static checks, four CodeCourt verifiers, and outcomes: human review or abandonment.

Figure 3: Validation gate.  After CI is green, AutoClean first runs deterministic static checks, then CodeCourt runs focused reviewers in parallel. A diff only proceeds to human review if every property passes; any failed property abandons the diff before it reaches a reviewer.

CodeCourt turns the failure modes we observed from the cleanup agent into explicit correctness properties. These properties are complementary to improvements we make to the cleanup agent itself. The cleanup agent is solving a long, multi-step task with many instructions in context: remove the stale flag, propagate the simplification, and so on.  As that instruction set grows, reliable instruction-following becomes harder. Some constraints get buried, some conflict in practice, and the agent may optimize for the most immediate signal, like passing CI, while violating a cleanup requirement we care about.

CodeCourt separates those requirements from the main cleanup loop. Instead of relying on one agent to remember and satisfy every constraint while editing code, we define explicit properties that the final diff must satisfy. Each property describes one thing a cleanup diff must get right before it ships.

For example:

  • No additions: The cleanup should only remove or simplify code, never introduce code
  • Preserves semantics: The program’s observable behavior must remain identical after cleanup.
  • No intent drift: The cleanup should stay focused on the requested flag. It shouldn’t remove other flags even if they’re related
  • No agent artifacts: The cleanup shouldn’t leave behind comments or artifacts from the agent’s cleanup reasoning

Each property gets its own reviewer agent with a prompt focused entirely on checking that one thing. All property agents run in parallel, and their verdicts aggregate into a simple decision:

  • All pass: the diff proceeds to human review
  • Any failure: the diff is abandoned before a human ever sees it

This decomposition is intentional. Early on, we tried having a single agent review each diff end-to-end, but found that using focused reviewer sub-agent yielded better results.

Conclusion

Feature flag cleanup is easy to postpone because each flag looks small on its own. But at Uber’s scale, those small leftovers add up quickly: stale branches, unused parameters, outdated tests, and extra code paths that engineers and agents still have to read and reason about.

AutoClean makes this cleanup continuous. It combines agents, deterministic tooling, CI, and validation checks so each diff is ready for review before it reaches a human. Reviewers can then focus on the final decision: whether the flag is truly ready to remove.

Over the past few years, AutoClean has cleaned up more than 16,000 flags and cleaned over 1,000,000 lines of code. As new flags continue to be introduced across Uber’s codebases, AutoClean keeps the cleanup process moving with them, so stale flag debt doesn’t quietly accumulate over time.

Acknowledgments 

We’d like to thank the Mobile Experimentation team, the Feature Flag Management team, and the many Uber product teams whose support made this work possible. We also gratefully acknowledge the colleagues who maintained AutoClean over the years, including Ameya Ketkar and Stefan Heule.

Cover Photo Attribution: Generated with Gemini

Model Context Protocol is a registered trademark of LF Projects, LLC. 

Written by

Daniel Ramos

Sr. Software Engineer

Daniel Ramos is a Sr. Software Engineer in Uber’s AI Verification team. His interests include machine learning for code, programming languages, and automated code maintenance. His current work focuses on tools for code migration, cleanup, and developer productivity.

Diego Marcilio

Sr. Software Engineer

Diego Marcilio is a Sr. Software Engineer in Uber’s AI Verification team. His interests include program analysis, refactoring, AI for code, agentic software engineering workflows, code review automation, and program repair. He currently works on combining deterministic program transformations with agentic AI systems to make large-scale code changes safer and more effective.

Nikhil Parasaram

Software Engineer

Nikhil Parasaram is a Software Engineer in Uber’s AI Verification team. His research interests include AI for code, code migration, property testing, and formal verification. His current work focuses on performing safer code migration and improving developer productivity.

Yuxin Wang

Senior Software Engineer

Yuxin Wang is on Uber’s Programming Systems team. His research interests include general static analysis, software verification, program repair and synthesis. His work covers building fast tooling for improving software reliability, as well as automated code review tools for developer productivity.

Related Articles
3282 articles
Filter by:
All categories
Legal
October 16, 2026
Driver products
Earn
Updates
October 13, 2026
Products
Ride
Updates
October 13, 2026
Driver products
Earn
October 12, 2026
Drive
October 8, 2026
Driver products
Earn
Updates
October 5, 2026
Prev
1
Next