gastownhall/beads · error

query neighbors from %s: %w

Error message

query neighbors from %s: %w

What it means

deleteNeighborsInTx queries both dependency planes for surviving rows joined to the deletion set in either direction, so their text can later be rewritten with [deleted:<id>] markers. This error wraps a query failure and names the table. Absent optional tables (wisp_dependencies) are skipped via optionalBlockedTable + isTableNotExistError, so reaching this error means a real failure on a table that exists.

Source

Thrown at internal/storage/issueops/delete_role.go:272

	neighborIDs := make(map[string]bool)
	for i := 0; i < len(ids); i += deleteBatchSize {
		end := i + deleteBatchSize
		if end > len(ids) {
			end = len(ids)
		}
		inClause, args := buildSQLInClause(ids[i:end])
		doubled := append(append([]interface{}{}, args...), args...)

		for _, depTable := range []string{"dependencies", "wisp_dependencies"} {
			rows, err := tx.QueryContext(ctx,
				fmt.Sprintf(`SELECT issue_id, %s AS depends_on_id FROM %s WHERE issue_id IN (%s) OR %s`,
					DepTargetExpr, depTable, inClause, depTargetIn("", inClause)),
				doubled...)
			if err != nil {
				if optionalBlockedTable(depTable) && isTableNotExistError(err) {
					continue
				}
				return nil, fmt.Errorf("query neighbors from %s: %w", depTable, err)
			}
			for rows.Next() {
				var source, target string
				if err := rows.Scan(&source, &target); err != nil {
					_ = rows.Close()
					return nil, fmt.Errorf("scan neighbor: %w", err)
				}
				for _, candidate := range [2]string{source, target} {
					if candidate == "" || deleting[candidate] {
						continue
					}
					neighborIDs[candidate] = true
				}
			}
			_ = rows.Close()
			if err := rows.Err(); err != nil {
				return nil, fmt.Errorf("iterate neighbors from %s: %w", depTable, err)
			}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Read the wrapped driver error and the named table in the message
  2. Check connectivity and retry; the transaction rolls back so the delete is safely re-runnable
  3. Raise the context timeout for large cascade batches
  4. Confirm migrations for both dependency tables are applied
  5. Verify driver version matches what isTableNotExistError/optionalBlockedTable expect
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure the backend is reachable before a wide delete
if err := db.PingContext(ctx); err != nil { return err }

Try / catch

err := store.Delete(ctx, req)
if err != nil && strings.Contains(err.Error(), "query neighbors from") {
    // driver/query failure on a dependency plane; inspect unwrapped cause, retry
}

Prevention

When it happens

Trigger: deleteNeighborsInTx (called from DeleteInTx with set.All) runs tx.QueryContext with the doubled-args IN query on 'dependencies' or 'wisp_dependencies' and gets a non-'table not exist' error — connection failure, driver error, or cancelled context.

Common situations: Connection lost between deletion-set resolution and the neighborhood read; context timeout on a large cascade set; driver mismatch on the DepTargetExpr expression; missing migration.

Related errors


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