{"record":{"id":"847dfc9bc71882fb","repo":"gastownhall/beads","slug":"read-status-from-s-w","errorCode":null,"errorMessage":"read status from %s: %w","messagePattern":"read status from (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/close.go","lineNumber":312,"sourceCode":"\tfor _, target := range []struct {\n\t\ttable  string\n\t\tcolumn string\n\t}{\n\t\t{table: \"issues\", column: \"depends_on_issue_id\"},\n\t\t{table: \"wisps\", column: \"depends_on_wisp_id\"},\n\t} {\n\t\tvar status string\n\t\terr := tx.QueryRowContext(ctx, \"SELECT status FROM \"+target.table+\" WHERE id = ?\", id).Scan(&status)\n\t\tif err == nil {\n\t\t\treturn types.Status(status) == types.StatusClosed, target.column, true, nil\n\t\t}\n\t\tif errors.Is(err, sql.ErrNoRows) {\n\t\t\tcontinue\n\t\t}\n\t\tif optionalBlockedTable(target.table) && isTableNotExistError(err) {\n\t\t\tcontinue\n\t\t}\n\t\treturn false, \"\", false, fmt.Errorf(\"read status from %s: %w\", target.table, err)\n\t}\n\treturn false, \"\", false, nil\n}\n\n//nolint:gosec // G201: table names come from WispTableRouting (hardcoded constants)\nfunc closeIssueInTx(ctx context.Context, tx DBTX, id string, reason, actor, session string, recordEvent bool) (*CloseResult, error) {\n\tisWisp := IsActiveWispInTx(ctx, tx, id)\n\tissueTable, _, eventTable, _ := WispTableRouting(isWisp)\n\n\tvar affectedIssues, affectedWisps []string\n\tvar aerr error\n\tif isWisp {\n\t\taffectedIssues, affectedWisps, aerr = AffectedByStatusChangeForWispInTx(ctx, tx, id)\n\t} else {\n\t\taffectedIssues, affectedWisps, aerr = AffectedByStatusChangeInTx(ctx, tx, id)\n\t}\n\tif aerr != nil {\n\t\treturn nil, fmt.Errorf(\"affected by close for %s: %w\", id, aerr)","sourceCodeStart":294,"sourceCodeEnd":330,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/close.go#L294-L330","documentation":"Beads throws this when reading the status of an id for the already-closed check fails with a non-'no rows' error on one of the probed tables (issues, then wisps). Missing optional wisp tables are skipped; missing rows just continue the probe; any other error is wrapped with the table name and aborts the close policy evaluation.","triggerScenarios":"CloseIssue or EnforceClosePolicyInTx → isClosedInTx when 'SELECT status FROM <table> WHERE id = ?' fails: connection failure, lock wait timeout, permission denied, unreadable/corrupt table, or context cancellation — on either the issues or wisps table.","commonSituations":"Connection drop mid-close-policy check; lock contention from concurrent closes or bulk operations; partial wisp migration leaving wisps present but damaged; context timeout on a slow database.","solutions":["Read the wrapped driver error after 'read status from <table>:' for the true cause","Retry the close if transient (connection, lock timeout)","Verify schema integrity and run migrations if the table exists but errors on read","Adjust context timeouts; check for blocking transactions holding locks on the target table"],"exampleFix":"// before\nctx := context.Background()\nclosed, col, found, err := isClosedInTx(ctx, tx, id) // times out on huge table\n// after\nctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)\ndefer cancel()\nclosed, col, found, err := isClosedInTx(ctx, tx, id)","handlingStrategy":"try-catch","validationCode":"// Verify both probed tables are readable before close-policy checks\nfor _, t := range []string{\"issues\", \"wisps\"} {\n\tvar one int\n\terr := db.QueryRow(\"SELECT 1 FROM \" + t + \" LIMIT 1\").Scan(&one)\n\tif err != nil && !dberrors.IsTableNotExist(err) {\n\t\treturn fmt.Errorf(\"%s unreadable before close: %w\", t, err)\n\t}\n}","typeGuard":null,"tryCatchPattern":"err := CloseIssue(ctx, id, opts)\nif err != nil && strings.Contains(err.Error(), \"read status from\") {\n\tswitch {\n\tcase dberrors.IsLockTimeout(err):\n\t\treturn retryCloseWithBackoff(ctx, id)\n\tcase dberrors.IsConnectionError(err) || errors.Is(err, context.DeadlineExceeded):\n\t\treturn retryClose(ctx, id)\n\tdefault:\n\t\treturn fmt.Errorf(\"permanent failure closing %s: %w\", id, err)\n\t}\n}","preventionTips":["Avoid running concurrent bulk updates against issues/wisps while closing issues","Size context timeouts for your table sizes and network latency","Run migrations atomically; a half-migrated wisps table will surface here","Watch lock-wait metrics and tune innodb_lock_wait_timeout if timeouts recur"],"tags":["sql","database","status-check"],"backgroundTag":"sql-query-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}