gastownhall/beads · error

check remaining blocker status

Error message

check remaining blocker status: %w

What it means

Wraps a failure from loadStatusByIDInTx while fetching statuses of remaining blocker issues inside GetNewlyUnblockedByCloseInTx. The dependency edges were read fine, but resolving whether each blocker is still open/closed/pinned failed, so the function cannot safely decide which candidates became unblocked.

Solutions

  1. Inspect the wrapped error from loadStatusByIDInTx for the root cause
  2. Verify the issues table exists with the expected id/status columns
  3. Resolve lock contention (retry the transaction) if the error is a deadlock/timeout
  4. Run storage integrity checks/repairs if corruption is indicated
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure issues table is healthy
_ = db.QueryRow("SELECT COUNT(*) FROM issues WHERE status != ''").Scan(&n)

Try / catch

if err := tx.CloseIssue(ctx, id); err != nil {
	if strings.Contains(err.Error(), "check remaining blocker status") {
		// inspect issues-table health, then retry
	}
	return err
}

Prevention

When it happens

Trigger: The batch status lookup for remainingBlockerIDs fails — e.g. the issues table query inside loadStatusByIDInTx errors due to schema problems, lock contention, or driver errors.

Common situations: Issues table missing or migrated to an unexpected shape; transaction deadlock/lock timeout with concurrent writers; embedded DB corruption.

Understand the failure class

Background: "query failed", "%w: SQL error" — wrapped database query errors in Go libraries explained — this error's family across 3 libraries.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/6b660514fc78128f. Report an issue: GitHub.

Appendix: source

Thrown at internal/storage/issueops/dependency_queries.go:874

					return nil, fmt.Errorf("scan remaining blocker: %w", err)
				}
				remainingByCandidate[candidateID] = append(remainingByCandidate[candidateID], blockerID)
				remainingBlockerSet[blockerID] = struct{}{}
			}
			_ = depRows.Close()
			if err := depRows.Err(); err != nil {
				return nil, fmt.Errorf("remaining blocker rows from %s: %w", depTable, err)
			}
		}

		remainingBlockerIDs := make([]string, 0, len(remainingBlockerSet))
		for blockerID := range remainingBlockerSet {
			remainingBlockerIDs = append(remainingBlockerIDs, blockerID)
		}
		sort.Strings(remainingBlockerIDs)
		statusByID, err := loadStatusByIDInTx(ctx, tx, remainingBlockerIDs)
		if err != nil {
			return nil, fmt.Errorf("check remaining blocker status: %w", err)
		}
		for candidateID, blockerIDs := range remainingByCandidate {
			for _, blockerID := range blockerIDs {
				status, ok := statusByID[blockerID]
				if ok && status != types.StatusClosed && status != types.StatusPinned {
					stillBlocked[candidateID] = true
					break
				}
			}
		}
	}

	var unblocked []*types.Issue
	for _, id := range candidateIDs {
		if stillBlocked[id] {
			continue
		}
		issue, err := GetIssueInTx(ctx, tx, id)

View on GitHub (pinned to 71377f2769)