TL;DR: Before adding the decorator, scan the node's existing decorators and skip when it's already there. In libcst, `leave_FunctionDef` gets the updated node - check `updated_node.decorators` for a matching name (including the called form, a decorator with arguments) and return the node unchanged. Prove it with a double-apply test: the second run must be a no-op.

The problem as reported:
```text
the agent's python libcst codemod duplicated decorators on every run - re-running it mid-migration wasnt safe
```

1. Open the transformer and find `leave_FunctionDef` (or `leave_ClassDef`) where the decorator is appended.
2. Add an existence check covering the plain, attribute, and called forms:
```python
def _has_decorator(self, node, name):
    for d in node.decorators:
        target = d.decorator
        if isinstance(target, cst.Call):
            target = target.func
        if isinstance(target, cst.Name) and target.value == name:
            return True
        if isinstance(target, cst.Attribute) and target.attr.value == name:
            return True
    return False

def leave_FunctionDef(self, original_node, updated_node):
    if self._has_decorator(updated_node, "my_decorator"):
        return updated_node
    return updated_node.with_changes(
        decorators=[*updated_node.decorators,
                    cst.Decorator(cst.Name("my_decorator"))]
    )
```
Expected: the transformer now returns early for already-decorated functions.
3. Apply the codemod to a fixture file twice and diff after the second run. Expected: empty diff - the second application changes nothing.
4. Add the double-apply test to CI for every codemod in the repo. Expected: future non-idempotent transforms fail the build instead of stacking decorators.

## Use this when
- A libcst codemod stacks the same decorator once per run.
- Re-running the transform mid-migration isn't safe.
- You need decorator insertion to be idempotent.

## Not for this skill when
- The duplicates come from the codemod running on two copies of the same file (transformed and untransformed) - that's a file-selection problem.
- Decorators are added by a different tool - fix the guard in that tool.

## Variant phrasings
- libcst duplicate decorator on re-run
- codemod adds decorator twice
- python codemod not idempotent decorator
- decorator stacked multiple times by transform

## Why it happens
The transformer appends unconditionally: its input predicate doesn't exclude its own output, so every run sees a function "missing" the decorator and adds another.

## Edge cases
- The decorator imported under an alias: resolve the real name via libcst's scope metadata instead of comparing raw names.
- Decorator factories with arguments: the isinstance(target, cst.Call) unwrap above covers the decorator-with-arguments form.
- Stacked decorators where order matters: appending at the end may be wrong - insert at the correct position, still guarded by the existence check.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_fVxVhIn4v0W8dQIUSWUTJw
