plandex-ai/plandex · error
error storing plan config: %v
Error message
error storing plan config: %v
What it means
StorePlanConfig wraps a failure of `UPDATE plans SET plan_config = $1 WHERE id = $2` with this message. Note the UPDATE silently affects zero rows when planId doesn't exist — this wrap only fires on actual Exec errors: connection failure, driver serialization error for the config, or schema problems.
Source
Thrown at app/server/db/plan_config_helpers.go:34
if err != nil {
return nil, fmt.Errorf("error getting plan config: %v", err)
}
return &config, nil
}
func StorePlanConfig(planId string, config *shared.PlanConfig) error {
query := `
UPDATE plans
SET plan_config = $1
WHERE id = $2
`
_, err := Conn.Exec(query, config, planId)
if err != nil {
return fmt.Errorf("error storing plan config: %v", err)
}
return nil
}
func GetDefaultPlanConfig(userId string) (*shared.PlanConfig, error) {
query := "SELECT default_plan_config FROM users WHERE id = $1"
var config shared.PlanConfig
err := Conn.Get(&config, query, userId)
if err != nil {
return nil, fmt.Errorf("error getting default plan config: %v", err)
}
return &config, nil
}
View on GitHub (pinned to e2d772072e)
Solutions
- Inspect the wrapped cause for connection vs encoding errors; for connection errors check Postgres health and retry
- Confirm migrations have created plans.plan_config with the expected type (JSONB/text)
- Validate the incoming PlanConfig payload (JSON-serializable, size limits) before storing
- After a successful Exec, also check RowsAffected if you need to detect a nonexistent planId, since zero rows is not an error here
Example fix
// before
err := StorePlanConfig(planId, config)
// after: detect zero-row updates too
res, err := Conn.Exec("UPDATE plans SET plan_config = $1 WHERE id = $2", config, planId)
if err != nil { return err }
n, _ := res.RowsAffected()
if n == 0 { return fmt.Errorf("plan %s not found", planId) } Defensive patterns
Strategy: validation
Validate before calling
// validate payload and target row before the UPDATE
b, err := json.Marshal(config)
if err != nil {
return fmt.Errorf("invalid plan config: %w", err)
}
var exists bool
Conn.Get(&exists, "SELECT EXISTS(SELECT 1 FROM plans WHERE id=$1)", planId) Try / catch
err := StorePlanConfig(planId, config)
if err != nil {
log.Printf("StorePlanConfig(%s): %v", planId, err) // inspect cause
http.Error(w, "failed to save config", http.StatusInternalServerError)
return
} Prevention
- Check RowsAffected after the UPDATE to detect nonexistent planIds (zero rows is silent)
- Run migrations with each deploy so plans.plan_config exists
- Sanitize/limit the incoming PlanConfig payload before persisting
- Retry once on transient connection errors before failing the request
When it happens
Trigger: UpdatePlanConfigHandler calls StorePlanConfig and Conn.Exec fails: DB unreachable, plans table/column missing (migration drift), or the *shared.PlanConfig value cannot be encoded by the driver into plan_config.
Common situations: Postgres restart or connection-pool exhaustion during a config save; running against a database without the plan_config column; a PlanConfig containing unsupported field types the lib/pq encoder rejects; concurrent migration changing the schema.
Related errors
- error listing org roles: %v
- error getting plan config: %v
- error getting default plan config: %v
- error storing default plan config: %v
- error adding plan context tokens: %v
AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05).
Data as JSON: /api/errors/a410e4b727b31964.
Report an issue: GitHub.