{"record":{"id":"fdaa8b1a264e9d22","repo":"gastownhall/beads","slug":"failed-to-get-epics-w","errorCode":null,"errorMessage":"failed to get epics: %w","messagePattern":"failed to get epics: %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/epic_closure.go","lineNumber":20,"sourceCode":"\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/steveyegge/beads/internal/types\"\n)\n\n// GetEpicsEligibleForClosureInTx returns epics whose children are all closed.\n// nolint:gosec // G201: table names are hardcoded, placeholders contain only ? markers\nfunc GetEpicsEligibleForClosureInTx(ctx context.Context, tx DBTX) ([]*types.EpicStatus, error) {\n\t// Step 1: Get open epic IDs (single-table scan)\n\tepicRows, err := tx.QueryContext(ctx, `\n\t\tSELECT id FROM issues\n\t\tWHERE issue_type = 'epic'\n\t\t  AND status != 'closed'\n\t`)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get epics: %w\", err)\n\t}\n\tvar epicIDs []string\n\tfor epicRows.Next() {\n\t\tvar id string\n\t\tif err := epicRows.Scan(&id); err != nil {\n\t\t\tepicRows.Close()\n\t\t\treturn nil, fmt.Errorf(\"scan epic id: %w\", err)\n\t\t}\n\t\tepicIDs = append(epicIDs, id)\n\t}\n\tepicRows.Close()\n\n\tif len(epicIDs) == 0 {\n\t\treturn nil, nil\n\t}\n\n\t// Step 2: Get parent-child dependencies from both tables (bd-w2w)\n\t// Wisp children store their parent-child deps in wisp_dependencies,","sourceCodeStart":2,"sourceCodeEnd":38,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/epic_closure.go#L2-L38","documentation":"GetEpicsEligibleForClosureInTx first queries for non-closed epic issue IDs. If that query fails at execution time (bad SQL, missing table, connection error), the function aborts with this message. It means epic closure analysis could not even start because the initial epic list could not be fetched.","triggerScenarios":"Calling GetEpicsEligibleForClosureInTx (via epic closure/cleanup commands) when the `SELECT id FROM issues WHERE issue_type = 'epic' AND status != 'closed'` query errors — corrupted schema, locked database, or connection failure.","commonSituations":"Running `bd` commands against a partially migrated or hand-edited database where the issues table or issue_type column is missing; the Dolt server being shut down mid-command.","solutions":["Inspect the wrapped error (%w) for 'no such table'/'unknown column' and run pending migrations.","Verify the database is reachable and the schema has an `issues` table with `issue_type` and `status` columns.","Re-run the command; transient lock/connection failures usually clear on retry.","Restore the database from backup if the schema is corrupted."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"var hasIssues int\nerr := db.QueryRow(\"SELECT COUNT(*) FROM information_schema.tables WHERE table_name = 'issues'\").Scan(&hasIssues)\nif err != nil || hasIssues == 0 { return errors.New(\"run bd migrations before closure analysis\") }","typeGuard":"func isEpicQueryFailed(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"failed to get epics:\")\n}","tryCatchPattern":"epics, err := GetEpicsEligibleForClosureInTx(ctx, tx)\nif err != nil {\n    var sqliteErr *sqlite.Error // or mysql.MySQLError\n    if errors.As(err, &sqliteErr) { return runMigrationsThenRetry(ctx) }\n    return err\n}","preventionTips":["Always run `bd migrate`/upgrades before running maintenance commands.","Never hand-edit the database schema.","Verify DB reachability before long maintenance runs.","Take a backup before epic closure operations."],"tags":["go","sql","storage","query"],"backgroundTag":"sql-query-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}