the agent's ast-grep rewrite rule kept appending the same import line in a loop because the pattern matched its own...
Fixes an ast-grep rewrite rule that loops, appending the same import on every scan because the pattern matches its own insertion. Use when ast-grep update-all grows the file each run. Key trigger: the import count increases with every scan.
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:
the agent's ast-grep rewrite rule kept appending the same import line in a loop because the pattern matched its own insertion- Reproduce the loop: run
ast-grep scan --rule rule.yml srctwice. Expected: the import count in matching files grows with every run. - 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.
- Narrow the pattern to the old construct only, e.g.:
rule:
pattern: oldHelper($$$)
fix: newHelper($$$)Expected: the pattern is false for the rewritten code, so re-runs are no-ops by construction.
- 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:
rule:
kind: program
not:
has:
kind: import_statement
regex: my-new-libRun 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.
- 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
Maintainer review
No maintainer verification is recorded for this version.
This records the version a maintainer checked. It does not assert that the version is the latest upstream release.