gastownhall/beads · error

journal: scan dependency removal from %s: %w

Error message

journal: scan dependency removal from %s: %w

What it means

While scanning dependency-removal edges from one table into journalDependencyEdge (source, target, kind, metadata), rows.Scan failed. This wraps column-count/type mismatches between the queried table's projection and the four-field scanner. The rows are explicitly closed before returning, so the error path is clean.

Source

Thrown at internal/storage/issueops/journal_dependencies.go:99

		inClause, args := buildSQLInClause(ids[start:end])
		queryArgs := append(append([]any{}, args...), args...)
		//nolint:gosec // table is validated above and inClause contains only placeholders.
		rows, err := tx.QueryContext(ctx, fmt.Sprintf(`
			SELECT issue_id, %s AS target, type, metadata
			FROM %s
			WHERE issue_id IN (%s) OR %s IN (%s)
		`, DepTargetExpr, table, inClause, DepTargetExpr, inClause), queryArgs...)
		if err != nil {
			if optionalBlockedTable(table) && isTableNotExistError(err) {
				continue
			}
			return nil, fmt.Errorf("journal: query dependency removals from %s: %w", table, err)
		}
		for rows.Next() {
			var edge journalDependencyEdge
			if err := rows.Scan(&edge.source, &edge.target, &edge.kind, &edge.metadata); err != nil {
				_ = rows.Close()
				return nil, fmt.Errorf("journal: scan dependency removal from %s: %w", table, err)
			}
			byKey[dependencyEdgeKey(edge)] = edge
		}
		if err := rows.Err(); err != nil {
			_ = rows.Close()
			return nil, fmt.Errorf("journal: iterate dependency removals from %s: %w", table, err)
		}
		if err := rows.Close(); err != nil {
			return nil, fmt.Errorf("journal: close dependency removals from %s: %w", table, err)
		}
	}
	return sortedDependencyEdges(byKey), nil
}

func dependencyEdgeKey(edge journalDependencyEdge) string {
	return edge.source + "\x00" + edge.target + "\x00" + edge.kind + "\x00" + edge.metadata
}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Run migrations/`bd doctor` to reconcile the dependency table schema with the expected projection.
  2. Diff the table's columns against the SELECT in dependencyEdgesInTableForIssueIDsInTx and ALTER to restore the expected shape.
  3. Repair rows with unexpected NULLs in source/target/kind columns.
  4. Align the driver version with the app's tested version.

Example fix

// before
ALTER TABLE issue_dependencies CHANGE kind dep_type VARCHAR(32); -- scanner expects `kind`
// after
ALTER TABLE issue_dependencies CHANGE dep_type kind VARCHAR(32); -- restore expected column name
Defensive patterns

Strategy: validation

Validate before calling

cols, _ := db.Query("SHOW COLUMNS FROM issue_dependencies")
want := map[string]bool{"source": true, "target": true, "kind": true, "metadata": true}
for cols.Next() { var c, t, null, key, extra string; _ = cols.Scan(&c, &t, &null, &key, &extra); delete(want, c) }
if len(want) > 0 { return fmt.Errorf("dependency table schema drift, missing: %v", want) }

Try / catch

if err != nil && strings.Contains(err.Error(), "journal: scan dependency removal from") {
    return fmt.Errorf("dependency table schema drifted vs scanner; reconcile via migrations: %w", err)
}

Prevention

When it happens

Trigger: RecordDependencyRemovalsForTableInTx / dependencyEdgesForIssueIDsInTx encountering rows whose shape differs from the SELECT: schema drift on a dependency table (renamed/dropped/extra columns), NULL in a column scanned into a non-nullable field, or a driver returning unexpected types for kind/metadata.

Common situations: Manually altered dependency tables; restoring tables from an older backup with a different schema; a driver version changing how TEXT/JSON metadata is returned.

Related errors


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