gastownhall/beads · error
count open children: unsupported target column %q
Error message
count open children: unsupported target column %q
What it means
Beads throws this when a close-policy child count is requested against a parent-dependency column other than the two supported ones: depends_on_issue_id (durable issues) or depends_on_wisp_id (wisps). The column is interpolated into the COUNT query, so it is strictly validated against these two hardcoded identifiers before use. This is an internal invariant guard, not a user-facing validation error.
Source
Thrown at internal/storage/issueops/close.go:195
return 0, err
}
if !found {
return 0, fmt.Errorf("%w: issue %s", storage.ErrNotFound, id)
}
return enforceClosePolicyForTargetInTx(ctx, tx, id, targetColumn, force, closed)
}
func countOpenChildrenInTx(ctx context.Context, tx DBTX, id string) (int, error) {
targetColumn, err := dependencyTargetColumnForIDInTx(ctx, tx, id)
if err != nil {
return 0, err
}
return countOpenChildrenForTargetInTx(ctx, tx, id, targetColumn)
}
func countOpenChildrenForTargetInTx(ctx context.Context, tx DBTX, id, targetColumn string) (int, error) {
if targetColumn != "depends_on_issue_id" && targetColumn != "depends_on_wisp_id" {
return 0, fmt.Errorf("count open children: unsupported target column %q", targetColumn)
}
var durableCount int
//nolint:gosec // G201: targetColumn is validated above against two hardcoded identifiers.
durableQuery := fmt.Sprintf(`
SELECT COUNT(DISTINCT dependency.issue_id)
FROM dependencies AS dependency
JOIN issues AS child ON child.id = dependency.issue_id
WHERE dependency.%s = ?
AND dependency.type = 'parent-child'
AND child.status != 'closed'
`, targetColumn)
if err := tx.QueryRowContext(ctx, durableQuery, id).Scan(&durableCount); err != nil {
return 0, fmt.Errorf("count open durable children for %s: %w", id, err)
}
var wispCount int
//nolint:gosec // G201: targetColumn is validated above against two hardcoded identifiers.
wispQuery := fmt.Sprintf(`View on GitHub (pinned to 71377f2769)
Solutions
- Ensure the value passed as targetColumn comes only from dependencyTargetColumnForIDInTx or the hardcoded literals "depends_on_issue_id"/"depends_on_wisp_id"
- Check for empty-string columns caused by an early-return or error path in the classifier being swallowed upstream
- If you added a new dependency column, extend the guard in internal/storage/issueops/close.go:194 to whitelist it (and audit the fmt.Sprintf query for injection safety)
Example fix
// before
err := countOpenChildrenForTargetInTx(ctx, tx, id, "parent_id") // panics into guard
// after
col := "depends_on_issue_id"
if isWispID(id) { col = "depends_on_wisp_id" }
err := countOpenChildrenForTargetInTx(ctx, tx, id, col) Defensive patterns
Strategy: validation
Validate before calling
func validTargetColumn(col string) bool {
return col == "depends_on_issue_id" || col == "depends_on_wisp_id"
}
if !validTargetColumn(targetColumn) {
return fmt.Errorf("refusing to count children with column %q", targetColumn)
} Type guard
func isDependencyTargetColumn(s string) bool {
return s == "depends_on_issue_id" || s == "depends_on_wisp_id"
} Prevention
- Always source targetColumn from dependencyTargetColumnForIDInTx, never from user input or config
- Never construct column names with fmt.Sprintf from dynamic data other than the two whitelisted literals
- Add a unit test asserting the guard rejects arbitrary strings
- When adding new dependency columns, update the whitelist and audit query interpolation together
When it happens
Trigger: Calling countOpenChildrenForTargetInTx (or countOpenChildrenInTx / enforceClosePolicyForTargetInTx above it) with a targetColumn string other than 'depends_on_issue_id' or 'depends_on_wisp_id' — i.e. a caller-produced column name from a classifier that returned an unexpected value.
Common situations: A fork or local patch added a third dependency table/column and routed it through the close policy; a classifier function was modified to return an empty string or a renamed column; tests injecting a bogus column value to assert the guard fires.
Related errors
- count open durable children for %s: %w
- affected by close for %s: %w
- failed to open server connection: %w
- failed to begin transaction: %w
- failed to recompute is_blocked: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/5aa1dcbc38d582c9.
Report an issue: GitHub.