plandex-ai/plandex · error
replacement not found: %s
Error message
replacement not found: %s
What it means
RejectReplacement parsed the result and scanned result.Replacements for one whose Id matches replacementId; none matched. Note the function marks replacement.RejectedAt in memory but (as written in this region) returns nil without persisting — the error is purely about lookup failure.
Source
Thrown at app/server/db/result_helpers.go:1023
}
if result.RejectedAt != nil {
return nil
}
now := time.Now()
foundReplacement := false
for _, replacement := range result.Replacements {
if replacement.Id == replacementId {
replacement.RejectedAt = &now
foundReplacement = true
break
}
}
if !foundReplacement {
return fmt.Errorf("replacement not found: %s", replacementId)
}
return nil
}
func GetPlanApplies(orgId, planId string) ([]*PlanApply, error) {
appliesDir := getPlanAppliesDir(orgId, planId)
files, err := os.ReadDir(appliesDir)
if err != nil {
if os.IsNotExist(err) {
return nil, nil
}
return nil, fmt.Errorf("error reading applies dir: %v", err)
}
planApplies := []*PlanApply{}View on GitHub (pinned to e2d772072e)
Solutions
- Confirm replacementId is a member of that result's Replacements (list the result's replacements first).
- Check you did not swap resultId and replacementId arguments.
- Re-fetch the result to get fresh replacement ids after any plan updates.
Example fix
// before
db.RejectReplacement(orgId, planId, resultId, replacementId)
// after
result := fetchResult(resultId) // verify replacement belongs to this result
for _, rep := range result.Replacements {
if rep.Id == replacementId {
return db.RejectReplacement(orgId, planId, resultId, replacementId)
}
}
return fmt.Errorf("replacement %s is not on result %s", replacementId, resultId) Defensive patterns
Strategy: validation
Validate before calling
result := getResult(resultId) // fetch and parse the result first
valid := false
for _, rep := range result.Replacements {
if rep.Id == replacementId {
valid = true
break
}
}
if !valid {
return fmt.Errorf("replacement %s is not on result %s", replacementId, resultId)
}
return RejectReplacement(orgId, planId, resultId, replacementId) Try / catch
if err := RejectReplacement(orgId, planId, resultId, replacementId); err != nil {
if strings.Contains(err.Error(), "replacement not found") {
return fmt.Errorf("stale replacementId %s; refresh result and retry", replacementId)
}
return err
} Prevention
- Refresh replacement ids from the result right before rejecting; don't cache them across plan updates.
- Don't confuse resultId and replacementId argument order.
- Handle already-rewritten replacements as a normal, non-fatal case in the UI.
When it happens
Trigger: Calling RejectReplacement with a replacementId that is not in the result's Replacements slice — wrong id, id from a different result, or the result has an empty Replacements list.
Common situations: Passing the result id as the replacement id (or vice versa); the replacement was already removed/rewritten; client cached ids from before the plan changed.
Understand the failure class
Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.
Related errors
- branch not found
- connection to plan stream timed out due to missing heartbeat
- error loading accounts: %v
- error signing in to new account: %v
- error selecting account: %v
AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05).
Data as JSON: /api/errors/5d46424efd8abf2e.
Report an issue: GitHub.