{"record":{"id":"42dd069574bcd82f","repo":"gastownhall/beads","slug":"delete-classify-planes-w","errorCode":null,"errorMessage":"delete: classify planes: %w","messagePattern":"delete: classify planes: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/delete_role.go","lineNumber":38,"sourceCode":"// writes that must see one snapshot.\n//\n// It assumes a request already refused by workapi.ValidateDeleteRequest and\n// already normalized by workapi.NormalizeDeleteIDs. The accessors do both\n// BEFORE opening a transaction, so a malformed request costs no database work.\n//\n// THE REWRITE IS INSIDE THE TRANSACTION. A route that deleted the rows in one\n// transaction and rewrote the neighbors' text afterwards left, on a failure\n// between the two, a workspace whose rows were gone and whose descriptions\n// still cited them.\nfunc DeleteInTx(ctx context.Context, tx *sql.Tx, req publicops.DeleteRequest) (publicops.DeleteResult, error) {\n\tids := req.IDs\n\tresult := publicops.DeleteResult{DryRun: req.DryRun}\n\n\t// The existence probe comes FIRST, so `bd delete typo real` reports the\n\t// typo rather than whatever the graph says about the id that resolved.\n\twispSet, err := WispIDSetInTx(ctx, tx, ids)\n\tif err != nil {\n\t\treturn publicops.DeleteResult{}, fmt.Errorf(\"delete: classify planes: %w\", err)\n\t}\n\tfound, err := GetIssuesByIDsInTx(ctx, tx, ids, wispSet)\n\tif err != nil {\n\t\treturn publicops.DeleteResult{}, fmt.Errorf(\"delete: resolve ids: %w\", err)\n\t}\n\tpresent := make(map[string]bool, len(found))\n\tfor _, issue := range found {\n\t\tif issue != nil {\n\t\t\tpresent[issue.ID] = true\n\t\t}\n\t}\n\tvar missing []string\n\tfor _, id := range ids {\n\t\tif !present[id] {\n\t\t\tmissing = append(missing, id)\n\t\t}\n\t}\n\tif len(missing) > 0 {","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/delete_role.go#L20-L56","documentation":"DeleteInTx begins by classifying each requested ID as a regular issue or a wisp via WispIDSetInTx, so the delete can address the right plane. Failure of that probe is wrapped with 'delete: classify planes'. This runs before existence checking, so the typo-reporting order can work.","triggerScenarios":"bd delete (DeleteInTx) where the WispIDSetInTx query against the wisps table fails — wisps table missing (old schema with wisp mode enabled), SQL error, lock contention, or context cancellation at the very start of the transaction.","commonSituations":"Upgraded binary expecting a wisps table against an unmigrated database; concurrent writer locking the DB at delete time; connection drop to remote Dolt immediately after the transaction opens.","solutions":["Inspect the wrapped WispIDSetInTx error; if 'no such table: wisps', run schema migration (bd doctor/migrate)","Retry the delete once lock contention clears or the connection is restored","Confirm the database file/server matches the binary version before deleting"],"exampleFix":"// before\nerr := deleteOp(ctx, store, ids)  // fails: classify planes: no such table: wisps\n// after — migrate schema first\n// bd doctor && bd migrate\nerr := deleteOp(ctx, store, ids)","handlingStrategy":"try-catch","validationCode":"// verify wisps table exists before delete\nvar n int\nif err := db.QueryRow(\"SELECT COUNT(*) FROM sqlite_master WHERE name='wisps'\").Scan(&n); err != nil || n == 0 {\n\treturn fmt.Errorf(\"wisps table missing; run bd doctor / migrate\")\n}","typeGuard":null,"tryCatchPattern":"_, err := DeleteInTx(ctx, tx, req)\nif err != nil && strings.Contains(err.Error(), \"delete: classify planes:\") {\n\tif strings.Contains(err.Error(), \"no such table\") { migrate(db); return retry(ctx, req) }\n\treturn err\n}","preventionTips":["Run bd doctor/migrate after upgrading the binary","Confirm database version matches binary version","Ensure the DB is not locked by another writer at delete time"],"tags":["database","delete","wisps","classification"],"backgroundTag":"sql-query-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}