diff --git includes/ajax.inc includes/ajax.inc
index 808cefb..e745d71 100644
--- includes/ajax.inc
+++ includes/ajax.inc
@@ -253,10 +253,43 @@ function ajax_form_callback() {
     // Remove the value for form validation.
     unset($_REQUEST['ajax_triggering_element']);
   }
+  // To either validate or avoid validation we need to know whether this is
+  // a button or not. Grab the marker sent by js to see.
+  $triggered_by_button = TRUE;
+  if (!empty($_REQUEST['ajax_triggered_by_button'])) {
+    $triggered_by_button = ($_REQUEST['ajax_triggered_by_button'] == 'true');
+    // Remove the value for form validation.
+    unset($_REQUEST['ajax_triggered_by_button']);
+  }
+
   list($form, $form_state, $form_id, $form_build_id) = ajax_get_form();
 
-  // Build, validate and if possible, submit the form.
-  drupal_process_form($form_id, $form, $form_state);
+  // If this has been triggered by a button, we can safely validate
+  // with the normal form validation.
+  if ($triggered_by_button == TRUE) {
+    // Build, validate and if possible, submit the form.
+    drupal_process_form($form_id, $form, $form_state);
+  }
+  // However, if it was not triggered by a button, then there's no way
+  // to know how much of the form is ready to validate, form errors should
+  // not be emitted, even though most of the rest of validation must take place.
+  else {
+    $form_state['values'] = array();
+    $form_state['ajax_activated_element'] = TRUE;
+    // Build the form.
+    $form = form_builder($form_id, $form, $form_state);
+    // Validate the form. However, $form_state['ajax_activated_element']
+    // will tell the validation to use the equivalent of
+    // #limit_validation_errors to avoid validation errors.
+    drupal_validate_form($form_id, $form, $form_state);
+
+    // drupal_html_id() maintains a cache of element IDs it has seen,
+    // so it can prevent duplicates. We want to be sure we reset that
+    // cache when a form is processed, so scenarios that result in
+    // the form being built behind the scenes and again for the
+    // browser don't increment all the element IDs needlessly.
+    drupal_static_reset('drupal_html_id');
+  }
 
   // This call recreates the form relying solely on the $form_state that
   // drupal_process_form() set up.
@@ -286,7 +319,7 @@ function ajax_form_callback() {
     $triggering_element = $form_state['clicked_button'];
   }
   // Now that we have the element, get a callback if there is one.
-  if (!empty($triggering_element)) {
+  if (!empty($triggering_element) && !empty($triggering_element['#ajax']['callback'])) {
     $callback = $triggering_element['#ajax']['callback'];
   }
   if (!empty($callback) && function_exists($callback)) {
@@ -328,7 +361,7 @@ function ajax_deliver($page_callback_result) {
   }
   elseif (is_array($page_callback_result) && isset($page_callback_result['#type']) && ($page_callback_result['#type'] == 'ajax_commands')) {
     // Complex AJAX callbacks can return a result that contains a specific
-    // set of commands to send to the browser. 
+    // set of commands to send to the browser.
     if (isset($page_callback_result['#ajax_commands'])) {
       $commands = $page_callback_result['#ajax_commands'];
     }
diff --git includes/form.inc includes/form.inc
index 41967f9..023efce 100644
--- includes/form.inc
+++ includes/form.inc
@@ -396,6 +396,7 @@ function form_state_keys_no_cache() {
   return array(
     // Public properties defined by form constructors and form handlers.
     'always_process',
+    'ajax_activated_element',
     'cache',
     'no_cache',
     'must_validate',
@@ -942,8 +943,18 @@ function _form_validate(&$elements, &$form_state, $form_id = NULL) {
     // #limit_validation_errors property is ignored if the button doesn't also
     // define its own submit handlers, because it's too large a security risk to
     // have any invalid user input when executing form-level submit handlers.
+    // form_set_error() is also suppressed in the case of a non-button AJAX
+    // activation of the form, in which case we want validation functions to
+    // succeed, but it's not the right time for errors.
+    $limit_validation_errors = FALSE;
     if (isset($form_state['clicked_button']['#limit_validation_errors']) && isset($form_state['clicked_button']['#submit'])) {
-      form_set_error(NULL, '', $form_state['clicked_button']['#limit_validation_errors']);
+      $limit_validation_errors = $form_state['clicked_button']['#limit_validation_errors'];
+    }
+    elseif (!empty($form_state['ajax_activated_element'])){
+      $limit_validation_errors = array();
+    }
+    if ($limit_validation_errors !== FALSE) {
+      form_set_error(NULL, '', $limit_validation_errors);
     }
     else {
       // As an extra security measure, explicitly turn off error suppression.
diff --git misc/ajax.js misc/ajax.js
index ee56aad..4149357 100644
--- misc/ajax.js
+++ misc/ajax.js
@@ -203,7 +203,7 @@ Drupal.ajax.prototype.beforeSubmit = function (form_values, element, options) {
   // Server-side code needs to know what element triggered the call, so it can
   // find the #ajax binding.
   form_values.push({ name: 'ajax_triggering_element', value: this.formPath });
-
+  form_values.push({ name: 'ajax_triggered_by_button', value: typeof(this.button) == 'object' });
   // Insert progressbar or throbber.
   if (this.progress.type == 'bar') {
     var progressBar = new Drupal.progressBar('ajax-progress-' + this.element.id, eval(this.progress.update_callback), this.progress.method, eval(this.progress.error_callback));
