{"record":{"id":"cb0fb22742e00569","repo":"plandex-ai/plandex","slug":"error-scanning-deleted-draft-plan-id-v","errorCode":null,"errorMessage":"error scanning deleted draft plan id: %v","messagePattern":"error scanning deleted draft plan id: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/server/db/plan_helpers.go","lineNumber":367,"sourceCode":"\treturn nil\n}\n\nfunc DeleteDraftPlans(orgId, projectId, userId string) error {\n\tres, err := Conn.Query(\"DELETE FROM plans WHERE project_id = $1 AND owner_id = $2 AND name = 'draft' RETURNING id;\", projectId, userId)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error deleting draft plans: %v\", err)\n\t}\n\n\tdefer res.Close()\n\n\t// get ids\n\tvar ids []string\n\n\tfor res.Next() {\n\t\tvar id string\n\t\terr := res.Scan(&id)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error scanning deleted draft plan id: %v\", err)\n\t\t}\n\t\tids = append(ids, id)\n\t}\n\n\terrCh := make(chan error, len(ids))\n\tfor _, planId := range ids {\n\t\tgo func(planId string) {\n\t\t\tdefer func() {\n\t\t\t\tif r := recover(); r != nil {\n\t\t\t\t\tlog.Printf(\"panic in DeleteDraftPlans: %v\\n%s\", r, debug.Stack())\n\t\t\t\t\terrCh <- fmt.Errorf(\"panic in DeleteDraftPlans: %v\\n%s\", r, debug.Stack())\n\t\t\t\t\truntime.Goexit() // don't allow outer function to continue and double-send to channel\n\t\t\t\t}\n\t\t\t}()\n\t\t\terrCh <- DeletePlanDir(orgId, planId)\n\t\t}(planId)\n\t}\n","sourceCodeStart":349,"sourceCodeEnd":385,"githubUrl":"https://github.com/plandex-ai/plandex/blob/e2d772072efadbe41d2946d97d79be55532dbab5/app/server/db/plan_helpers.go#L349-L385","documentation":"While iterating rows returned by the draft-plan DELETE, res.Scan(&id) failed to copy a RETURNING id value into a string. With DELETE ... RETURNING id this normally only fails on driver/protocol errors, NULL ids, or rows.Next/Scan misuse.","triggerScenarios":"res.Scan(&id) errors during the rows loop: driver connection lost mid-result-set, id column NULL, or the query was changed to return extra columns that no longer match Scan args.","commonSituations":"Connection reset while streaming large result sets; someone added columns to RETURNING without updating Scan; NULL id values from schema changes allowing NULL primary keys.","solutions":["Confirm RETURNING list matches the Scan arguments exactly","Check the wrapped driver error for connection/protocol issues","Ensure the id column is NOT NULL primary key","Use errors.Is/As with pq errors for precise diagnosis"],"exampleFix":"// before\nvar id string\nerr := res.Scan(&id)\nif err != nil {\n    return fmt.Errorf(\"error scanning deleted draft plan id: %v\", err)\n}\n// after\nvar id string\nif err := res.Scan(&id); err != nil {\n    return fmt.Errorf(\"error scanning deleted draft plan id: %w\", err)\n}","handlingStrategy":"try-catch","validationCode":"// verify RETURNING columns match Scan args\n// query: DELETE FROM plans WHERE ... RETURNING id;  -> Scan(&id) only","typeGuard":null,"tryCatchPattern":"if err := DeleteDraftPlans(orgId, projectId, userId); err != nil {\n    if errors.Is(err, driver.ErrBadConn) || errors.Is(err, io.EOF) {\n        // connection lost mid-result-set; retry the operation\n    }\n    return err\n}","preventionTips":["Keep RETURNING column list and Scan args in lockstep","Enforce NOT NULL primary keys in schema","Handle rows.Err() after iteration loops","Use %w wrapping for errors.As diagnostics"],"tags":["database","postgres","sql-scan"],"backgroundTag":"sql-scan-error","analyzedSha":"e2d772072efadbe41d2946d97d79be55532dbab5","analyzedAt":"2026-09-05T20:56:53.631Z","contentChangedAt":"2026-09-05T20:56:53.631Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}