Skip to main content

AI-Generated Codemods: Tutorial (2026)

A codemod is a program that transforms source code at scale—renaming a function across 200 files, upgrading a deprecated API, or refactoring a pattern into a new design. Writing codemods manually is tedious and error-prone; AI can generate them in minutes. The challenge is validating that the generated codemod is correct before running it on the entire codebase. Teams using AI-generated codemods report 5-10x faster migrations than manual refactoring and near-zero regression rates when validation is done right.

How AI Generates Codemods

AI can generate codemods in two modes: (1) regex/string-based (simple find-replace), and (2) AST-based (parsing code into a syntax tree, transforming it, then regenerating). String-based codemods are fragile (they break on formatting variations). AST-based codemods are robust but require language-specific libraries (jscodeshift for JavaScript, lib2to3 for Python, rustfmt for Rust). AI is better at generating AST-based codemods when you provide a template and clear examples.

# Example: AI-generated Python codemod for upgrading pytest syntax
# Input: old pytest.mark.asyncio(async_fn) → new: @pytest.mark.asyncio async def async_fn

CODEMOD_GENERATION_PROMPT = """
You are an expert at writing Python codemods using the `libcst` library.

Task: Generate a codemod that transforms old pytest async decorator syntax to new syntax.

Old syntax (pre-pytest 7.0):
```python
@pytest.mark.asyncio
def test_something():
result = await async_function()
assert result == expected

New syntax (pytest 7.0+):

@pytest.mark.asyncio
async def test_something():
result = await async_function()
assert result == expected

Only apply the transformation if:

  1. The function is decorated with @pytest.mark.asyncio
  2. The function body contains at least one await statement

Generate a libcst transformer class that applies this refactoring. Include error handling for edge cases.

Return: Valid Python code that can be run with: python -m libcst.tool codemod my_codemod.MyTransformer target_dir/ """

AI output would be something like:

class PyTestAsyncTransformer(cst.CSTTransformer): def init(self): self.has_pytest_asyncio = False self.has_await = False

def visit_FunctionDef(self, node: cst.FunctionDef) -> bool:

Check for @pytest.mark.asyncio decorator

for decorator in node.decorators: if self._is_pytest_asyncio(decorator): self.has_pytest_asyncio = True

Check for await in body

for statement in node.body.body: if self._contains_await(statement): self.has_await = True

return True

def leave_FunctionDef(self, original_node: cst.FunctionDef, updated_node: cst.FunctionDef) -> cst.FunctionDef: if self.has_pytest_asyncio and self.has_await:

Add async keyword to function

return updated_node.with_changes(asynchronous=cst.SimpleAsyncFunctionDef()) return updated_node


This pattern: AI generates the transformer, you test it on a sample codebase, then run it at scale.

## Validating Codemods Before Running at Scale

Before running a generated codemod on your entire codebase, validate it on a subset:

1. **Dry run on sample files.** Run the codemod on 10–20 representative files, diff the output, and review manually.
2. **Test semantic equivalence.** Run the test suite before and after the transformation; tests should pass identically.
3. **Check for false positives.** Did the codemod transform lines it shouldn't have? For example, a regex-based rename might accidentally change a string literal.
4. **Performance check.** If the codemod rewrites query logic, ensure the new version has similar performance (or better).

```bash
# Example validation workflow
# Step 1: Dry run on sample
codemod --dry-run --sample 20 --transformer my_codemod.py src/

# Step 2: Review diff
git diff # Check if transformations look correct

# Step 3: Run tests
pytest -v

# Step 4: Full run (if everything looks good)
codemod --transformer my_codemod.py src/

Real Example: Upgrading a Deprecated API

Suppose your team is migrating from pkg.old_api() to pkg.new_api(). The API signature changed: old takes (config, callback), new takes (config) and uses context managers. Here's what an AI-generated codemod might look like:

# Old code:
config = Config(...)
pkg.old_api(config, on_complete=lambda result: print(result))

# New code:
config = Config(...)
with pkg.new_api(config) as api:
result = api.run()
print(result)

The AI generates a codemod that:

  1. Finds calls to pkg.old_api()
  2. Extracts the callback function
  3. Wraps the call in a with statement
  4. Moves the callback logic into the with block

This is complex enough that manual refactoring would take hours; AI generates it in 5 minutes (plus 30 minutes validation).

Handling Edge Cases in Codemods

Generated codemods often miss edge cases: nested API calls, conditional logic, exception handling. Include edge cases in your test cases and let the AI know: "The codemod must handle these cases: (1) nested calls, (2) exception handlers wrapping the call, (3) lazy evaluation where the function is wrapped in a lambda." This forces the AI to be thorough.

Codemod TypeComplexityValidation TimeAI Success Rate
String rename (no deps)Low5 min98%
API signature upgradeMedium20 min85%
Pattern migration (e.g., callback → async/await)High60 min70%
Architectural refactoring (monolith → microservices)Very high8+ hours40% (mostly scaffolding)

Distributing Codemods Safely

For large teams, distribute codemods via a shared tool or script repository:

# teams/codemods/
├── pytest_async_transformer.py
├── deprecated_api_upgrade.py
├── logging_formatter_v2.py
└── README.md (how to run each)

# Usage:
git clone [email protected]:teams/codemods.git
python codemods/pytest_async_transformer.py --target src/

This centralizes codemod maintenance and ensures everyone uses the same (validated) transformation.

Key Takeaways

  • AI generates codemods in minutes. Complex transformations that take hours to hand-code, AI produces in 5–10 minutes.
  • Validate on a sample before running at scale. Dry run + test suite + manual diff review prevent silent breakage.
  • AST-based codemods are safer than regex. They're more resilient to formatting variations and edge cases.
  • Document and version codemods. Keep them in a shared repository so the team can review and audit migrations.

Frequently Asked Questions

What is the difference between a codemod and a linting rule?

Linting rules detect issues (e.g., unused variables) but don't fix them. Codemods automatically transform code. Use linting to identify problems; use codemods to fix them at scale.

Can AI generate codemods for my custom DSL or framework?

If your DSL is documented, yes. Include an explanation of the DSL syntax and semantics in your prompt, plus 2–3 example transformations. The AI will learn the pattern and generalize.

Should I commit codemod-generated code without human review?

No. Always review the diff before committing. Codemods are fast but not infallible; a human eye catches edge cases and unintended transformations.

How do I test a codemod on a large codebase without applying it?

Use a --dry-run flag (most codemod tools support this). It applies the transformation in memory and prints diffs without modifying files.

Can a codemod break my build?

Yes, if it's buggy. Always run the test suite after applying a codemod. If tests fail, revert and debug the codemod.

Further Reading