TL;DR: The rule must not match its own output. Anchor the pattern on the old construct only, so once the rewrite fires there's nothing left for a second run to match. If the rule also inserts the import, gate that on the old code's presence too - never let an always-firing rule append the import. Verify with two scans: the second must report zero matches.

The problem as reported:
```text
the agent's ast-grep rewrite rule kept appending the same import line in a loop because the pattern matched its own insertion
```

1. Reproduce the loop: run `ast-grep scan --rule rule.yml src` twice. Expected: the import count in matching files grows with every run.
2. Read the rule and find the overly broad pattern - usually it matches a common node (every call, every file) while the fix appends the import unconditionally. Expected: you can see the pattern is still true after the fix runs.
3. Narrow the pattern to the old construct only, e.g.:
```yaml
rule:
  pattern: oldHelper($$$)
fix: newHelper($$$)
```
Expected: the pattern is false for the rewritten code, so re-runs are no-ops by construction.
4. Handle the import separately: use a scan-only rule to find files that still need it, and add the import in the same fix that rewrites the old call - never as a standalone always-firing rule:
```yaml
rule:
  kind: program
  not:
    has:
      kind: import_statement
      regex: my-new-lib
```
Run `ast-grep scan --rule needs-import.yml src`. Expected: lists exactly the files missing the import, and the list shrinks to zero as the rewrite rule does its job.
5. Final check: run the full scan twice. Expected: the first run rewrites, the second run reports no matches.

## Use this when
- An ast-grep rule appends or duplicates its own insertion on every run.
- A scan with update-all grows the file each pass.
- A rewrite rule's pattern matches the code its fix produces.

## Not for this skill when
- The duplicates come from several overlapping rules in a rules directory - that's rule composition; deduplicate the rules instead.
- The looping tool isn't ast-grep - the same principle applies, but the syntax here is ast-grep-specific.

## Variant phrasings
- ast-grep fix runs in loop
- ast-grep appends import every run
- ast-grep rule matches its own output
- ast-grep rewrite not idempotent

## Why it happens
The pattern describes a shape the fix itself produces. Each run's output satisfies the next run's pattern, so the rule keeps firing on its own insertions.

## Edge cases
- A regex in the rule matching more imports than intended: anchor it to the exact module name.
- The inserted import lands in the wrong position and breaks import sorting: let the linter's sort fix it, or insert after the last existing import.
- Multiple rules in one directory where the rewrite rule and the import rule fire in the same pass: order them or combine the guards.

## Provenance

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