{"record":{"id":"2832b6efeedcdab0","repo":"gastownhall/beads","slug":"get-issues-by-ids-build-wisp-set-w","errorCode":null,"errorMessage":"get issues by IDs: build wisp set: %w","messagePattern":"get issues by IDs: build wisp set: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/dependencies.go","lineNumber":1010,"sourceCode":"// table (issues/wisps). Uses batched IN clauses.\n//\n// wispSet is an optional pre-built set of active wisp IDs scoped to\n// cover ids (see WispIDSetInTx). Pass nil to have the helper build\n// a scoped set internally; callers hydrating multiple batches inside\n// one tx can build the set once over the union of their IDs and\n// reuse it across calls.\n//\n//nolint:gosec // G201: table names come from WispTableRouting (hardcoded constants)\nfunc GetIssuesByIDsInTx(ctx context.Context, tx DBTX, ids []string, wispSet map[string]struct{}) ([]*types.Issue, error) {\n\tif len(ids) == 0 {\n\t\treturn nil, nil\n\t}\n\n\tif wispSet == nil {\n\t\tvar err error\n\t\twispSet, err = WispIDSetInTx(ctx, tx, ids)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"get issues by IDs: build wisp set: %w\", err)\n\t\t}\n\t}\n\n\t// Partition IDs by wisp status.\n\twispIDs, permIDs := partitionByWispSet(ids, wispSet)\n\n\tvar allIssues []*types.Issue\n\tfor _, pair := range []struct {\n\t\ttable    string\n\t\tlabelTbl string\n\t\tids      []string\n\t}{\n\t\t{\"issues\", \"labels\", permIDs},\n\t\t{\"wisps\", \"wisp_labels\", wispIDs},\n\t} {\n\t\tif len(pair.ids) == 0 {\n\t\t\tcontinue\n\t\t}","sourceCodeStart":992,"sourceCodeEnd":1028,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/dependencies.go#L992-L1028","documentation":"Wraps an error from WispIDSetInTx while GetIssuesByIDsInTx builds the set of wisp IDs needed to partition requested IDs into wisps vs permanent issues. The library throws it so callers of the batch-ID fetch know resolution failed before any partitioning or row queries ran.","triggerScenarios":"GetIssuesByIDsInTx called with a nil wispSet (all its callers: GetBlockedIssuesInTx, DeleteInTx, deleteNeighborsInTx, GetDependenciesWithMetadataInTx, GetDependentsWithMetadataInTx, GetDependenciesInTx) and WispIDSetInTx fails querying wisp membership for the given IDs.","commonSituations":"DB connection failure or query timeout while probing wisp_dependencies/wisp tables, transaction aborted by a prior statement, or schema mismatch after version upgrades.","solutions":["Read the wrapped cause — it is the WispIDSetInTx DB error; address that directly","If the caller already computed a wisp set, pass it in so the extra query is skipped","Retry the transaction if the cause is transient (lock timeout, connection reset)","Confirm wisp tables exist and migrations ran after upgrading"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Pre-flight: verify the DB is reachable before batch fetch\nif err := db.PingContext(ctx); err != nil {\n    return fmt.Errorf(\"db unreachable: %w\", err)\n}\n// Optionally pre-compute and pass wispSet so WispIDSetInTx is skipped:\nwispSet, err := WispIDSetInTx(ctx, tx, ids)\nif err != nil { return err }\nissues, err := GetIssuesByIDsInTx(ctx, tx, ids, wispSet)","typeGuard":null,"tryCatchPattern":"issues, err := GetIssuesByIDsInTx(ctx, tx, ids, nil)\nif err != nil && strings.Contains(err.Error(), \"build wisp set\") && isTransientDBError(errors.Unwrap(err)) {\n    issues, err = GetIssuesByIDsInTx(ctx, tx, ids, nil) // one retry\n}","preventionTips":["Pass a precomputed wispSet when the caller already has one to skip the extra query","Keep transactions alive and connections healthy (pool health checks)","Run migrations so wisp tables exist before calling ID-batch fetches","Cap batch size to avoid long-running transactions"],"tags":["database","transaction","go"],"backgroundTag":"wisp-id-resolution-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}