{"record":{"id":"2aa6a670bdf4da5d","repo":"gastownhall/beads","slug":"wisp-id-set-w","errorCode":null,"errorMessage":"wisp id set: %w","messagePattern":"wisp id set: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/wisp_routing.go","lineNumber":92,"sourceCode":"\t} else if empty {\n\t\treturn set, nil\n\t}\n\tfor start := 0; start < len(ids); start += queryBatchSize {\n\t\tend := start + queryBatchSize\n\t\tif end > len(ids) {\n\t\t\tend = len(ids)\n\t\t}\n\t\tbatch := ids[start:end]\n\t\tplaceholders := make([]string, len(batch))\n\t\targs := make([]any, len(batch))\n\t\tfor i, id := range batch {\n\t\t\tplaceholders[i] = \"?\"\n\t\t\targs[i] = id\n\t\t}\n\t\tq := fmt.Sprintf(\"SELECT id FROM wisps WHERE id IN (%s)\", strings.Join(placeholders, \",\"))\n\t\trows, err := tx.QueryContext(ctx, q, args...)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"wisp id set: %w\", err)\n\t\t}\n\t\tfor rows.Next() {\n\t\t\tvar id string\n\t\t\tif err := rows.Scan(&id); err != nil {\n\t\t\t\t_ = rows.Close()\n\t\t\t\treturn nil, fmt.Errorf(\"wisp id set: scan: %w\", err)\n\t\t\t}\n\t\t\tset[id] = struct{}{}\n\t\t}\n\t\t_ = rows.Close()\n\t\tif err := rows.Err(); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"wisp id set: rows: %w\", err)\n\t\t}\n\t}\n\treturn set, nil\n}\n\n// partitionByWispSet splits ids into (wispIDs, permIDs) using the provided","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/wisp_routing.go#L74-L110","documentation":"Thrown when the batched SELECT id FROM wisps WHERE id IN (...) fails to execute. After confirming the wisps table is non-empty, WispIDSetInTx queries ids in batches of queryBatchSize; any driver, connection, or SQL error in that query is wrapped here. The result determines which of the caller's ids are routed as wisps.","triggerScenarios":"Calling DeleteInTx, GetIssuesByIDsInTx, ExecuteAddDependencies, or ReconcileChildCounters with ids present in the codebase while the wisps SELECT fails: broken connection, wisps table dropped mid-operation, or SQL error from a malformed batch.","commonSituations":"Schema migration removed/renamed the wisps table while old code still runs; connection dropped during a mixed write/read transaction; server killed the IN-query for resource reasons.","solutions":["Check the wrapped driver error for connection vs missing-table vs syntax causes.","Run migrations to recreate the wisps table if it is missing.","Retry the operation once the database connection is healthy.","Check server logs if the batch query was killed for resource limits."],"exampleFix":"// before: wisps table dropped\nDROP TABLE wisps;\n// after: recreate via migration\nCREATE TABLE wisps (id VARCHAR(255) NOT NULL PRIMARY KEY, ...);","handlingStrategy":"retry","validationCode":"var exists string\nif err := db.QueryRow(\"SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_NAME='wisps'\").Scan(&exists); err != nil {\n    return errors.New(\"wisps table missing: run beads migration\")\n}","typeGuard":null,"tryCatchPattern":"err := retry.Do(func() error {\n    _, err := issueops.GetIssuesByIDsInTx(ctx, tx, ids)\n    return err\n}, retry.Attempts(3), retry.Delay(500*time.Millisecond))","preventionTips":["Keep the wisps table in place; never drop it during rolling upgrades.","Retry routing operations — id set computation is read-only and safe to repeat.","Check server logs if IN-batch queries are killed for resources.","Keep batches within queryBatchSize (library handles this; do not bypass)."],"tags":["database","wisp-routing","select-failed"],"backgroundTag":"sql-select-query-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}