gastownhall/beads · error

dependency graph: scan %s: %w

Error message

dependency graph: scan %s: %w

What it means

appendDependencyGraphInTx failed scanning a row of a dependency table into (issue_id, depends_on_id, type). A scan error means column types or nullability differ from what the query expects; the function closes the rows and wraps the error with the table name for diagnosis.

Source

Thrown at internal/storage/issueops/cycles.go:108

// graph. DetectCycles intentionally continues to use AppendBlockingGraphInTx.
func AppendSchedulingGraphInTx(ctx context.Context, tx DBTX, depTables []string, graph map[string][]string) error {
	return appendDependencyGraphInTx(ctx, tx, depTables, graph, true)
}

func appendDependencyGraphInTx(ctx context.Context, tx DBTX, depTables []string, graph map[string][]string, includeParentChild bool) error {
	for _, depTable := range depTables {
		rows, err := tx.QueryContext(ctx, fmt.Sprintf(`
			SELECT issue_id, %s AS depends_on_id, type
			FROM %s
		`, DepTargetExpr, depTable))
		if err != nil {
			return fmt.Errorf("dependency graph: query %s: %w", depTable, err)
		}
		for rows.Next() {
			var issueID, dependsOnID, depType string
			if err := rows.Scan(&issueID, &dependsOnID, &depType); err != nil {
				_ = rows.Close()
				return fmt.Errorf("dependency graph: scan %s: %w", depTable, err)
			}
			t := types.DependencyType(depType)
			if t == types.DepBlocks || t == types.DepConditionalBlocks || (includeParentChild && t == types.DepParentChild) {
				graph[issueID] = append(graph[issueID], dependsOnID)
			}
		}
		_ = rows.Close()
		if err := rows.Err(); err != nil {
			return fmt.Errorf("dependency graph: rows %s: %w", depTable, err)
		}
	}
	return nil
}

// CycleThroughEdgesInGraph reports a rendered cycle that traverses
// one of the new edges (issueID -> dependsOnID pairs), or "" when no new edge
// lies on a cycle. An edge u -> v is on a cycle exactly when u is reachable
// from v, so this is precise where cycle enumeration is not: a DFS-based

View on GitHub (pinned to 71377f2769)

Solutions

  1. Find and fix the offending NULL/malformed rows in the named dependency table
  2. Align the schema with the version of beads in use (re-run migrations)
  3. If an upgrade caused it, check release notes for dependency-table schema changes

Example fix

// before
SELECT issue_id, depends_on_id, type FROM dependencies -- NULL rows present
// after
UPDATE dependencies SET type='blocks' WHERE type IS NULL; -- then retry
Defensive patterns

Strategy: validation

Validate before calling

// detect malformed rows before creating dependencies
rows, _ := db.Query("SELECT issue_id, depends_on_id, type FROM dependencies WHERE issue_id IS NULL OR depends_on_id IS NULL OR type IS NULL")
// if any rows returned, repair them first

Try / catch

if err != nil && strings.HasPrefix(err.Error(), "dependency graph: scan ") {
    // locate NULL rows in the named table, repair, then retry
}

Prevention

When it happens

Trigger: A dependency row has NULL in issue_id/depends_on_id/type or an unexpected column type, so rows.Scan fails while iterating during graph assembly.

Common situations: Schema mismatch after version upgrade (columns added/renamed/nullable); hand-edited or imported rows with NULL dependency fields; corruption from a failed migration.

Related errors


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