diff --git a/CRM/Batch/BAO/Batch.php b/CRM/Batch/BAO/Batch.php
index 29e38034d..1958be871 100644
--- a/CRM/Batch/BAO/Batch.php
+++ b/CRM/Batch/BAO/Batch.php
@@ -14,6 +14,11 @@ class CRM_Batch_BAO_Batch extends CRM_Batch_DAO_Batch {
    */
   const EXPIRE_DAY = 8;
 
+  /**
+   * stuck expire hour
+   */
+  const EXPIRE_HOUR = 4;
+
   /**
    * Batch id to load
    * @var int
@@ -216,6 +221,73 @@ public static function expireBatch() {
     return '';
   }
 
+  /**
+   * Auto remove stuck batch
+   *
+   * @return null
+   */
+  public static function cancelStuckBatch() {
+    $type = self::batchType();
+    $status = self::batchStatus();
+    $statusRunning = $status['Running'];
+    $statusCanceled = $status['Canceled'];
+
+    $sql = "SELECT id, data, modified_date, description FROM civicrm_batch WHERE type_id = %1 AND status_id = %2 ORDER BY created_date ASC LIMIT 1";
+    $dao = CRM_Core_DAO::executeQuery($sql, array(
+      1 => array($type['Auto'], 'Integer'),
+      2 => array($statusRunning, 'Integer'),
+    ));
+    $dao->fetch();
+    if (!empty($dao->id)) {
+      if ($dao->data) {
+        $meta = unserialize($dao->data);
+        // after 4 hours without any progress, cancel it
+        if (is_array($meta) && empty($meta['processed'])) {
+          $lastSuccessTime = strtotime($dao->modified_date);
+          if (CRM_REQUEST_TIME - $lastSuccessTime > 3600 * self::EXPIRE_HOUR) {
+            CRM_Core_Error::debug_log_message("Canceled running batch id {$dao->id} due to zero progress over ".self::EXPIRE_HOUR." hours.");
+            CRM_Core_DAO::executeQuery("UPDATE civicrm_batch SET status_id = %1, description = %2 WHERE id = %3", array(
+              1 => array($statusCanceled, 'Integer'),
+              2 => array(ts('Batch running failed. Contact the site administrator for assistance.'), 'String'),
+              3 => array($dao->id, 'Integer'),
+            ));
+          }
+        }
+        elseif(!empty($meta['processed'])){
+          if (!empty($dao->description)) {
+            $processHistories = explode(':', $dao->description);
+          }
+          else {
+            $processHistories = array();
+          }
+          $stuck = 0;
+          foreach($processHistories as $lastProcessed) {
+            if ((int)$meta['processed'] == (int)$lastProcessed) {
+              $stuck++;
+            }
+          }
+          if ($stuck <= self::EXPIRE_HOUR) {
+            array_unshift($processHistories, $meta['processed']);
+            $processHistories = array_slice($processHistories, 0, self::EXPIRE_HOUR+2);
+            CRM_Core_DAO::executeQuery("UPDATE civicrm_batch SET description = %1 WHERE id = %2", array(
+              1 => array(implode(':', $processHistories), 'String'),
+              2 => array($dao->id, 'Integer'),
+            ));
+          }
+          else {
+            // no progress after 4 times(have same processed records), cancel it
+            CRM_Core_Error::debug_log_message("Canceled running batch id {$dao->id} due to stuck in progress {$meta['processed']} for {$stuck} times.");
+            CRM_Core_DAO::executeQuery("UPDATE civicrm_batch SET status_id = %1, description = %2 WHERE id = %3", array(
+              1 => array($statusCanceled, 'Integer'),
+              2 => array(ts('Batch running failed. Contact the site administrator for assistance.').' ('.$dao->description.')', 'String'),
+              3 => array($dao->id, 'Integer'),
+            ));
+          }
+        }
+      }
+    }
+  }
+
   /**
    * Constructor
    * 
diff --git a/l10n/pot/civicrm.pot b/l10n/pot/civicrm.pot
index e97ed2359..4553ec4d5 100644
--- a/l10n/pot/civicrm.pot
+++ b/l10n/pot/civicrm.pot
@@ -49591,4 +49591,8 @@ msgstr ""
 #: CRM/Admin/Form/Setting/Receipt.php
 #: templates/CRM/Admin/Form/Setting/Receipt.tpl
 msgid "When the legal ID display option is not set to complete display, email receipt encryption cannot be enabled."
+msgstr ""
+
+#: CRM/Batch/BAO/Batch.php
+msgid "Batch running failed. Contact the site administrator for assistance."
 msgstr ""
\ No newline at end of file
diff --git a/l10n/zh_TW/LC_MESSAGES/civicrm.mo b/l10n/zh_TW/LC_MESSAGES/civicrm.mo
index d6cd994ae..92394def2 100644
Binary files a/l10n/zh_TW/LC_MESSAGES/civicrm.mo and b/l10n/zh_TW/LC_MESSAGES/civicrm.mo differ
diff --git a/l10n/zh_TW/civicrm.po b/l10n/zh_TW/civicrm.po
index 1950bb964..da0f15de2 100644
--- a/l10n/zh_TW/civicrm.po
+++ b/l10n/zh_TW/civicrm.po
@@ -21,7 +21,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: netiCRM\n"
 "PO-Revision-Date: 2013-07-25 09:19+0000\n"
-"Last-Translator: 馮 佳今, 2022-2024\n"
+"Last-Translator: jimyhuang <jimmy@netivism.com.tw>, 2013,2015-2024\n"
 "Language-Team: Chinese (Taiwan) (http://app.transifex.com/netivism-tw/neticrm/language/zh_TW/)\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
@@ -50161,3 +50161,7 @@ msgid ""
 "When the legal ID display option is not set to complete display, email "
 "receipt encryption cannot be enabled."
 msgstr "當身分證字號呈現方式不是完全顯示時,無法啟用電子收據加密功能"
+
+#: CRM/Batch/BAO/Batch.php
+msgid "Batch running failed. Contact the site administrator for assistance."
+msgstr "批次執行失敗。請聯繫網站管理員以取得協助。"