{"record":{"id":"4f18835f16b712d3","repo":"gastownhall/beads","slug":"wisp-id-set-probe-w","errorCode":null,"errorMessage":"wisp id set: probe: %w","messagePattern":"wisp id set: probe: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/wisp_routing.go","lineNumber":73,"sourceCode":"\n// WispIDSetInTx returns the subset of ids that are currently-active wisps\n// within the tx. The set is consistent for the tx's lifetime (Dolt MVCC).\n// Intended for hot-path partitioning where a batch of IDs must be split\n// into wisps vs permanents; one scoped query amortized over the batch\n// replaces N per-ID IsActiveWispInTx calls without paying for a full\n// wisps-table scan when callers have a small batch against a large\n// wisps table.\n//\n// Returns an empty set when ids is empty; never issues a query.\n//\n//nolint:gosec // G201: query uses placeholder-only interpolation\nfunc WispIDSetInTx(ctx context.Context, tx DBTX, ids []string) (map[string]struct{}, error) {\n\tset := make(map[string]struct{})\n\tif len(ids) == 0 {\n\t\treturn set, nil\n\t}\n\tif empty, err := wispsTableEmptyOrMissingInTx(ctx, tx); err != nil {\n\t\treturn nil, fmt.Errorf(\"wisp id set: probe: %w\", err)\n\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 {","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/wisp_routing.go#L55-L91","documentation":"WispIDSetInTx first probes whether the wisps table is empty or missing before batch-querying ids; this error wraps a failure of that probe itself. A missing table is treated as empty (not an error), so reaching this error means a real probe failure: connection problems, permission denied, or an unexpected SQL error from the existence check.","triggerScenarios":"Calling any of WispIDSetInTx's callers (ReconcileChildCounters, DeleteInTx, GetIssuesByIDsInTx, ExecuteAddDependencies) when the wisps-table emptiness probe fails due to a broken connection, insufficient privileges, or probe-query incompatibility with the backend.","commonSituations":"User lacks SELECT/SHOW permission on the wisps table; connection dropped; non-MySQL backend where the probe's SQL (information_schema lookups or similar) does not exist.","solutions":["Inspect the wrapped probe error to distinguish permissions vs connection vs SQL incompatibility.","Grant the database user SELECT privileges on the wisps table (or create it via migration).","Verify the backend supports the probe query (MySQL/Dolt-compatible driver required).","Retry after the connection is restored."],"exampleFix":"// before: restricted user\nGRANT SELECT ON db.issues TO 'beads'@'%';\n// after\nGRANT SELECT ON db.* TO 'beads'@'%';","handlingStrategy":"try-catch","validationCode":"// verify the user can read the wisps table\nvar one int\nerr := db.QueryRow(\"SELECT 1 FROM wisps LIMIT 1\").Scan(&one)\nif err != nil {\n    var myErr *mysql.MySQLError\n    if errors.As(err, &myErr) && (myErr.Number == 1142 || myErr.Number == 1146) {\n        return fmt.Errorf(\"wisps table inaccessible/missing: grant SELECT or run migration\")\n    }\n}","typeGuard":null,"tryCatchPattern":"if err != nil {\n    var myErr *mysql.MySQLError\n    if errors.As(err, &myErr) && myErr.Number == 1142 {\n        return fmt.Errorf(\"insufficient privileges on wisps table: %w\", err)\n    }\n    return err\n}","preventionTips":["Grant the beads DB user broad SELECT on the schema, not per-table.","Ensure the backend is MySQL/Dolt-compatible (probe SQL dependency).","Run migrations so the wisps table exists with current code.","Ping the database before routing-heavy operations."],"tags":["database","wisp-routing","probe-failed"],"backgroundTag":"sql-probe-query-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}