gastownhall/beads · error
%w: %s is blocked by %v
Error message
%w: %s is blocked by %v
What it means
The close policy refuses to close an issue that is not already closed when the denormalized is_blocked flag is set AND there is at least one live, open direct blocker (blocks/waits-for/conditional-blocks). The error wraps storage.ErrCloseBlocked and lists the blocking issue ids. Force bypasses this refusal (but not the version check). A bare is_blocked=1 with no live direct blocker is deliberately allowed — the live blocker list self-heals stale flags.
Source
Thrown at internal/storage/issueops/close.go:159
// cell, while a close writes both tiers, so Dolt cannot merge a stale count
// with a new edge.
if err := touchDependencyCoordinationInTx(ctx, tx, id); err != nil {
return 0, err
}
openChildren, err := countOpenChildrenForTargetInTx(ctx, tx, id, targetColumn)
if err != nil {
return 0, err
}
if !force && openChildren > 0 {
return 0, &storage.CloseOpenChildrenError{IssueID: id, OpenChildren: openChildren}
}
if !force && !closed {
blocked, blockers, err := IsBlockedInTx(ctx, tx, id)
if err != nil {
return 0, err
}
if blocked && len(blockers) > 0 {
return 0, fmt.Errorf("%w: %s is blocked by %v", storage.ErrCloseBlocked, id, blockers)
}
}
return openChildren, nil
}
// EnforceClosePolicyInTx applies the close policy to id without closing it, for
// a caller that reaches a done status by some other write. It routes the target
// exactly as a checked close does — probing issues then wisps by ID, preferring
// the durable row when both hold it — rather than deriving the table from an
// is-wisp flag, so a dual-resident ID counts children against the same row the
// close guard would.
//
// It returns the observed open-child count. force bypasses both refusals and
// nothing else; the coordination-cell touch happens either way.
func EnforceClosePolicyInTx(ctx context.Context, tx DBTX, id string, force bool) (int, error) {
closed, targetColumn, found, err := isClosedInTx(ctx, tx, id)
if err != nil {
return 0, errView on GitHub (pinned to 71377f2769)
Solutions
- Close or resolve the listed blocker issues first, then retry the close
- Re-run with force=true if the blockers are known-stale or intentionally being overridden
- Recompute blocked state (closing a blocker triggers RecomputeIsBlocked) if the blocker list looks wrong
- Check dependency direction: a wrongly-added blocks edge can be removed so the target is genuinely unblocked
Example fix
err := store.CloseIssue(ctx, id, reason, actor)
var blocked *storage.CloseBlockedError
if errors.As(err, &blocked) {
// close blockers first, or override deliberately
for _, b := range blockers {
_ = store.CloseIssue(ctx, b, "unblocking "+id, actor)
}
err = store.CloseIssue(ctx, id, reason, actor)
} Defensive patterns
Strategy: validation
Validate before calling
blocked, blockers, err := store.IsBlocked(ctx, id)
if err != nil {
return err
}
if blocked && len(blockers) > 0 && !force {
return fmt.Errorf("%s must be unblocked first: %v", id, blockers)
} Try / catch
err := store.CloseIssue(ctx, id, reason, actor)
var cbErr *storage.CloseBlockedError
if errors.As(err, &cbErr) {
for _, b := range cbErr.Blockers {
if err := closeBlocker(ctx, b); err != nil { return err }
}
err = store.CloseIssue(ctx, id, reason, actor)
} Prevention
- Close work in dependency order (leaves before parents)
- Check is_blocked/blockers before scripting bulk closes
- Use force only for deliberate overrides of known-stale blockers
- Recompute blocked state after removing dependency edges
When it happens
Trigger: bd close (or CloseIssueCheckedInTx / EnforceClosePolicyInTx) without force on an open issue that has at least one open blocker dependency; also triggered for a status change that reaches done via updateIssueInTx through EnforceClosePolicyInTx.
Common situations: Trying to close a parent task whose child still blocks it; workflow automation attempting to close work while dependencies remain open; scripts that close in bulk without ordering by dependency.
Related errors
- ErrCloseBlocked
- no store is open for this workspace
- no absolute native user directory is available
- ExternalDoltConfig: set either Socket OR (Host, Port), not b
- ExternalDoltConfig: must set Socket or (Host, Port)
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/e38f967dd8484a01.
Report an issue: GitHub.