iflytek/astron-agent · critical

lock tenant bootstrap app failed

Error message

lock tenant bootstrap app failed: %w

What it means

After ensuring the bootstrap app row, the code takes a row lock with SELECT ... FOR UPDATE on tb_app for the reserved tenant ID and scans it back; any query/scan failure is wrapped as 'lock tenant bootstrap app failed'. The lock serializes concurrent replicas before credential rotation.

Solutions

  1. Inspect the wrapped cause: on lock-wait timeout, check innodb_lock_wait_timeout and which transaction holds the row (SHOW ENGINE INNODB STATUS).
  2. Ensure no other job deletes or modifies the reserved tenant row concurrently; protect it from cleanup scripts.
  3. Verify the bootstrap user has SELECT and locking capability on tb_app.
  4. Retry bootstrap after transient contention; the transactional design makes re-runs safe.

Example fix

// before: row deleted concurrently, Scan returns sql.ErrNoRows
err := transaction.QueryRowContext(ctx, `SELECT app_id, is_disable, is_delete FROM tb_app WHERE app_id = ? FOR UPDATE`, id).Scan(...)
// after: distinguish missing row from real failures
row := transaction.QueryRowContext(ctx, lockSQL, id)
if err := row.Scan(&lockedAppID, &lockedAppDisabled, &lockedAppDeleted); err != nil {
    if errors.Is(err, sql.ErrNoRows) {
        return fmt.Errorf("reserved tenant app %s disappeared during bootstrap", id)
    }
    return fmt.Errorf("lock tenant bootstrap app failed: %w", err)
}
Defensive patterns

Strategy: retry

Validate before calling

var one int
if err := db.QueryRow(`SELECT 1 FROM tb_app WHERE app_id = ?`, reservedID).Scan(&one); err != nil {
    return fmt.Errorf("reserved app missing before bootstrap: %w", err)
}

Try / catch

row := tx.QueryRowContext(ctx, lockSQL, id)
err := row.Scan(&a, &b, &c)
switch {
case errors.Is(err, sql.ErrNoRows):
    return fmt.Errorf("reserved app vanished; rerun bootstrap")
case isLockWaitTimeout(err):
    return retryErr // backoff and retry the whole transaction
case err != nil:
    return fmt.Errorf("lock tenant bootstrap app failed: %w", err)
}

Prevention

When it happens

Trigger: QueryRowContext(...).Scan fails because the row vanished between insert and lock (concurrent delete), a connection error occurred, the MySQL user lacks SELECT/LOCK privilege, or Scan types mismatch the columns.

Common situations: Another process deleted the reserved row mid-transaction; connection dropped during long lock waits (innodb_lock_wait_timeout exceeded surfaces as a query error); replicas racing at startup contend on the same row and hit lock timeouts.

Understand the failure class

Background: Database query failed: Internal Server Error 500s wrapping SQL, Prisma, and connection failures — what to check first — this error's family across 16 libraries.

Related errors


AI-assisted analysis of iflytek/astron-agent@5e758547a8 (2026-09-12). Data as JSON: /api/errors/fa3c7d0d7d601a32. Report an issue: GitHub.

Appendix: source

Thrown at core/tenant/tools/database/bootstrap_credentials.go:147

		false,
		"星辰租户",
		false,
		"",
	); err != nil {
		return fmt.Errorf("ensure tenant bootstrap app failed: %w", err)
	}

	// Serialize reconciliation across replicas on the reserved app row before
	// taking any auth-index gap locks or rotating managed credentials.
	var lockedAppID string
	var lockedAppDisabled sql.NullBool
	var lockedAppDeleted sql.NullBool
	if err := transaction.QueryRowContext(
		ctx,
		`SELECT app_id, is_disable, is_delete FROM tb_app WHERE app_id = ? FOR UPDATE`,
		credentials.TenantID,
	).Scan(&lockedAppID, &lockedAppDisabled, &lockedAppDeleted); err != nil {
		return fmt.Errorf("lock tenant bootstrap app failed: %w", err)
	}
	if lockedAppID != credentials.TenantID {
		return errors.New("locked tenant bootstrap app does not match the reserved tenant ID")
	}
	if !lockedAppDisabled.Valid || lockedAppDisabled.Bool ||
		!lockedAppDeleted.Valid || lockedAppDeleted.Bool {
		return errors.New("reserved tenant bootstrap app is disabled or deleted")
	}
	return nil
}

func findTenantBootstrapCredential(
	ctx context.Context,
	transaction bootstrapTransaction,
	credentials config.TenantBootstrapCredentials,
) (bool, error) {
	var collisionOwner string
	err := transaction.QueryRowContext(

View on GitHub (pinned to 5e758547a8)