gastownhall/beads · error

check retarget collision in %s: %w

Error message

check retarget collision in %s: %w

What it means

This error is the wrapped-SQL-failure variant from checkRetargetTargetCollision: the collision probe query itself failed at the database level while preparing a wisp<->issue retarget. It is distinct from the deliberate collision error (3543) — here the query errored (driver, syntax, connectivity) rather than finding a conflicting row. Table-not-exist is deliberately tolerated and returns nil.

Source

Thrown at internal/storage/issueops/dependencies.go:864

	query := fmt.Sprintf(`
		SELECT 1 FROM %s moving
		JOIN %s existing ON moving.issue_id = existing.issue_id
		WHERE moving.%s = ?
		  AND (existing.%s = ? OR existing.%s = ?)
		LIMIT 1
	`, table, table, sourceCol, conflictCols[0], conflictCols[1])

	var found int
	err := tx.QueryRowContext(ctx, query, id, id, id).Scan(&found)
	if err == sql.ErrNoRows {
		return nil
	}
	if err != nil {
		if isTableNotExistError(err) {
			return nil
		}
		return fmt.Errorf("check retarget collision in %s: %w", table, err)
	}
	return fmt.Errorf("retarget to %s collides with existing dependency target in %s", id, table)
}

//nolint:gosec // G201: table and typedCol are hardcoded constants.
func checkRenameTargetCollision(ctx context.Context, tx DBTX, table, typedCol, newID string) error {
	var otherCols []string
	switch typedCol {
	case "depends_on_issue_id":
		otherCols = []string{"depends_on_wisp_id", "depends_on_external"}
	case "depends_on_wisp_id":
		otherCols = []string{"depends_on_issue_id", "depends_on_external"}
	default:
		return fmt.Errorf("checkRenameTargetCollision: unsupported typed column %q", typedCol)
	}

	query := fmt.Sprintf(`
		SELECT 1 FROM %s a

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check the wrapped driver error; treat connection/timeout errors as transient and retry the whole move transaction.
  2. Verify the database file/server is healthy and reachable.
  3. Confirm both dependencies and wisp_dependencies exist with the expected typed target columns (run migrations).
  4. Check DB user grants allow SELECT on the dependency tables.

Example fix

// before: transient connection loss aborts the move
err := MoveIssuePersistenceInTx(ctx, tx, id)
// after: retry the whole transaction on transient errors
if err := MoveIssuePersistenceInTx(ctx, tx, id); isTransientDBError(err) {
    err = retryTx(func(tx *sql.Tx) error { return MoveIssuePersistenceInTx(ctx, tx, id) })
}
Defensive patterns

Strategy: retry

Validate before calling

// probe the collision-check tables are readable before starting the move
for _, t := range []string{"dependencies", "wisp_dependencies"} {
    if _, err := db.Query(fmt.Sprintf("SELECT 1 FROM %s LIMIT 1", t)); err != nil {
        return fmt.Errorf("cannot read %s: %w", t, err)
    }
}

Type guard

func isCollisionProbeFailure(err error) bool {
    return err != nil && strings.Contains(err.Error(), "check retarget collision")
}

Try / catch

err := store.MoveIssue(ctx, id)
if isCollisionProbeFailure(err) && isTransient(errors.Unwrap(err)) {
    err = withBackoff(3, func() error { return store.MoveIssue(ctx, id) })
}

Prevention

When it happens

Trigger: RetargetInboundDependenciesToWispInTx or RetargetInboundDependenciesToIssueInTx runs its JOIN probe (SELECT 1 FROM <table> moving JOIN <table> existing ...) and the driver returns an error other than sql.ErrNoRows or a table-not-exist condition — e.g. corrupted schema, connection reset, query timeout.

Common situations: Database connection dropped between transaction start and the probe; Dolt server restarted mid-operation; malformed/partially migrated dependency tables; permissions revoking SELECT on dependencies tables.

Related errors


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