حلقه بیپایان Jira Automation: وقتی قانون خودش را صدا میزند
اگر trigger و action یک قانون Jira Automation روی یک رویداد بیفتند، قانون خودش را دوباره اجرا میکند و issue با صدها کامنت تکراری پر میشود. این مقاله علت، پاکسازی و راهحل پایدار را نشان میدهد.

نشانه مشکل: issue به یک ربات کامنتساز تبدیل میشود
صبح دوشنبه چند نفر همزمان پیام میدهند که «تسک OPS-1423 دیوانه شده». issue را باز میکنی و میبینی ۴۰۰ کامنت پشت سر هم با متن یکسان زیر آن نشسته، وضعیت بین In Review و Done بالا و پایین میپرد و صندوق ایمیل watcherها پر شده است. مهمترین سرنخ این است که همه کامنتها یک author مشترک دارند: Automation for Jira.
اگر به مسیر Project settings > Automation > Audit log بروی، تصویر واضحتر میشود: یک قانون در چند ثانیه چند صد بار اجرا شده و هر اجرا با وضعیت Success ثبت شده است. این الگو تقریباً همیشه یعنی یک چیز: قانون دارد خودش را trigger میکند.
نشانه قطعی حلقه این است که تعداد اجراهای audit log با تعداد رویدادهای واقعی تیم همخوانی ندارد. اگر ۵ نفر در روز کامنت میگذارند ولی قانون ۹۰۰ بار اجرا شده، مشکل پیکربندی است نه رفتار کاربران.
علت ریشهای: trigger و action روی یک رویداد میافتند
سناریوی رایج این است که قانونی شبیه زیر ساخته شده:
- Trigger:
Issue commented onبا شرطی که متن کامنت شامل کلمهdeployباشد. - Action: انتقال issue به
Doneو افزودن کامنت «✅ Deployed by pipeline».
اجرای اول از یک کامنت واقعی تیم شروع میشود. قانون issue را به Done میبرد و کامنتی میگذارد که... خودش شامل کلمه deploy است. پس دوباره شرط trigger برقرار میشود، دوباره transition، دوباره کامنت. هر دور یک ورودی تازه برای دور بعد میسازد و حلقه تا وقتی چیزی جلویش را نگیرد ادامه دارد.
سه عامل این وضعیت را بدتر میکنند:
- خروجی action همان ورودی trigger است. ریشهی اصلی همین است؛ نه Jira، نه سرعت، نه بار سرور.
- اجازهی trigger شدن قانون توسط قوانین دیگر. در تنظیمات هر قانون گزینهای با عنوان Allow other rule actions to trigger this rule وجود دارد که اگر فعال باشد، action یک قانون میتواند قانون دیگر (یا خودش) را دوباره بیدار کند.
- ترکیب چند قانون روی یک رویداد. یک قانون کامنت میگذارد، قانون دوم روی «Issue commented on» گوش میدهد و وضعیت را عوض میکند، قانون سوم دوباره کامنت میگذارد. حلقه بین دو یا سه قانون شکل میگیرد، نه داخل یکی.
Jira در بعضی موارد پس از تعداد زیادی اجرا در بازه کوتاه، قانون را غیرفعال میکند. این یک تور محافظتی است، نه راهحل؛ تا وقتی قانون را درست نکنی، اولین فعالسازی دستی دوباره حلقه را روشن میکند.
راهحل گامبهگام
۱) قانون را همین حالا خاموش کن
قبل از هر کار دیگری جلوی خونریزی را بگیر. برای قوانین پروژهای: Project settings > Automation و کلید قانون را خاموش کن. برای قوانین سراسری: Settings > System > Automation rules. خاموش کردن، اجرای در جریان را متوقف نمیکند اما از دور بعد جلوگیری میکند.
۲) مقیاس خرابی را از audit log بردار
در audit log فیلدهای زمان اجرا، نام قانون و وضعیت را ببین. اگر زمان شروع مشخص است، میتوانی بازهی آلودگی را دقیق تعیین کنی: آستانهی زمانی قبل از شروع را در جستوجوهای بعدی استفاده کن.
۳) issueهای آلوده را با JQL پیدا کن
متن کامنتی که ربات میگذاشته را بهعنوان اثر انگشت استفاده کن:
project = OPS AND comment ~ "Deployed by pipeline" AND updated >= -1d ORDER BY updated DESC
عملگر ~ روی فیلد comment یک جستوجوی متنی است، پس دقیقاً همان کامنتهای تکراری را برمیگرداند و issueهای سالم را کنار میگذارد.
۴) کامنتهای تکراری را با REST API پاک کن
Jira در UI حذف گروهی کامنت ندارد. اول accountId ربات را از یکی از کامنتهای آلوده بردار:
curl -s -u "$JIRA_EMAIL:$JIRA_TOKEN" \
"https://your-domain.atlassian.net/rest/api/3/issue/OPS-1423/comment?maxResults=1" \
| python3 -c "import sys,json; print(json.load(sys.stdin)['comments'][0]['author']['accountId'])"
سپس اسکریپت زیر را اجرا کن. این اسکریپت فقط کامنتهایی را حذف میکند که author آنها همان اکانت ربات است، پس کامنتهای واقعی تیم دستنخورده میمانند:
import os
import time
import requests
BASE = os.environ["JIRA_BASE"].rstrip("/") # https://your-domain.atlassian.net
AUTH = (os.environ["JIRA_EMAIL"], os.environ["JIRA_TOKEN"])
BOT_ID = os.environ["AUTOMATION_ACCOUNT_ID"]
API = f"{BASE}/rest/api/3"
def search(jql):
keys, token = [], None
while True:
params = {"jql": jql, "maxResults": 100, "fields": "key"}
if token:
params["nextPageToken"] = token
r = requests.get(f"{API}/search/jql", auth=AUTH, params=params, timeout=30)
r.raise_for_status()
data = r.json()
keys += [i["key"] for i in data.get("issues", [])]
token = data.get("nextPageToken")
if not token:
return keys
def iter_comments(key):
start = 0
while True:
r = requests.get(
f"{API}/issue/{key}/comment",
auth=AUTH,
params={"startAt": start, "maxResults": 100},
timeout=30,
)
r.raise_for_status()
data = r.json()
batch = data.get("comments", [])
for c in batch:
yield c
start += len(batch)
if not batch or start >= data.get("total", 0):
return
def purge(key):
removed = 0
for c in iter_comments(key):
if c["author"].get("accountId") == BOT_ID:
r = requests.delete(
f"{API}/issue/{key}/comment/{c['id']}", auth=AUTH, timeout=30
)
if r.status_code in (200, 204):
removed += 1
time.sleep(0.1) # مرز ایمن برای rate limit
return removed
if __name__ == "__main__":
jql = 'project = OPS AND comment ~ "Deployed by pipeline" AND updated >= -1d'
for k in search(jql):
print(k, purge(k))
اگر روی نسخهای از Jira هستید که /search/jql را ندارد، همان تابع search را به /rest/api/3/search و صفحهبندی startAt تغییر بده؛ بقیهی منطق یکی است.
۵) قانون را بدون حلقه بازنویسی کن
سه الگو را به ترتیب اولویت امتحان کن:
- trigger را از «کامنت» به «تغییر فیلد» منتقل کن. اگر تریگر روی
Field value changedبرای یک فیلد مثلDeploy Statusباشد و action آن فیلد را تغییر ندهد، حلقه از پایه غیرممکن میشود. - گارد بگذار. اگر مجبوری روی کامنت تریگر بزنی، بلافاصله بعد از trigger یک
Advanced compare conditionاضافه کن: مقدار اول{{comment.body}}، شرط does not contain، مقدار دومDeployed by pipeline. کامنتی که خود ربات میگذارد شرط را رد میکند و کار تمام است. - عنوان قانون را در متن action تکرار نکن. متن کامنت ربات را طوری بنویس که هیچوقت با کلمهی کلیدی trigger همپوشانی نداشته باشد. کامنت واقعی بهتری هم میشود.
۶) محافظهای سطح پلتفرم
- در تنظیمات قانون، گزینهی Allow other rule actions to trigger this rule را غیرفعال نگه دار.
- قوانین را با یک پروژهی sandbox تست کن و فقط بعد از دیدن یک اجرای تمیز روی پروژهی واقعی فعالش کن.
- مالکیت قانون را به یک سرویساکانت اختصاصی بده تا خروج فرد از تیم باعث از کار افتادن قانون نشود.
چطور مطمئن شویم مشکل واقعاً حل شده
سه بررسی کوتاه کافی است:
- تست دستی روی یک issue آزمایشی: کامنتی بگذار که کلمهی کلیدی را ندارد. باید هیچ اتفاقی نیفتد. بعد کامنتی با کلمهی کلیدی بگذار؛ باید دقیقاً یک transition و یک کامنت اضافه شود.
- شمارش در audit log: بعد از تست، تعداد اجراهای قانون باید برابر تعداد رویدادهای واقعی باشد، نه بیشتر. اگر عدد بزرگتر بود، گارد دوم لازم داری.
- پایش ۱۰ دقیقهای: JQL را دوباره اجرا کن و تعداد کامنتهای ربات را با تعداد قبلی مقایسه کن. عدد باید ثابت بماند.
یک JQL سریع برای این پایش:
project = OPS AND comment ~ "Deployed by pipeline" AND updated >= -10m
پیشگیری و بهترین روشها
- قاعدهی طلایی: هیچ actionی نباید رویدادی تولید کند که trigger همان قانون (یا قانون دیگری) روی آن گوش میدهد.
- برای هر قانون حداکثر یک مسئولیت تعریف کن. قانونی که هم وضعیت را عوض میکند هم کامنت میگذارد هم فیلد ویرایش میکند، سه کاندید حلقه است.
- قوانین
Issue commented onگرانترین تریگرها برای نگهداریاند؛ اگر میتوانی روی transition یا تغییر فیلد تریگر بزن. - قبل از فعالسازی هر قانون جدید، به سؤال «اگر این action دوباره اجرا شود چه میشود؟» جواب بده.
- audit log را هفتگی چک کن. جهش ناگهانی تعداد اجرا اولین نشانهی حلقه است، حتی وقتی هنوز کسی شکایتی نکرده.
- پس از حل مشکل، محتوای قانون را در مخزن IaC یا مستندات تیم ثبت کن تا فرد بعدی همان ابتدا گاردها را ببیند.
Jira Automation ابزار قدرتمندی است و بیشتر خرابیهایش از خود Jira نیست؛ از طراحی rule است. اگر trigger و action را روی دو رویداد جدا نگه داری، ۹۰٪ حلقههای بیپایان هیچوقت شکل نمیگیرند.
نویسنده
تیم محتوای کلودیکپ
تیم فنی و محتوای کلودیکپ؛ مهندسانی که هر روز با DevOps، Kubernetes و زیرساخت ابری کار میکنند و تجربههایشان را اینجا مینویسند.
سوالات متداول این مقاله
در audit log نام قانونی که مکرراً اجرا شده را ببین. اگر فقط یک نام تکرار میشود، حلقه درون همان قانون است. اگر دو یا سه نام بهصورت زنجیرهای پشت سر هم اجرا میشوند، action یک قانون، trigger قانون بعدی را فعال میکند و باید گارد را روی همان حلقهی بینقانونی بگذاری.
کامنت حذفشده در تاریخچه باقی نمیماند، اما transitionها و changelog دستنخورده میمانند. چون اسکریپت فقط کامنتهای متعلق به accountId ربات را حذف میکند، بحثهای واقعی تیم از بین نمیرود. پیش از اجرا، یک نسخه از کلید issueهای هدف را جایی ذخیره کن.
Jira در بعضی موارد پس از تعداد زیادی اجرا در بازه کوتاه، قانون را بهعنوان محافظت غیرفعال میکند، اما این رفتار تضمینی نیست و به بار و نسخهی پلتفرم بستگی دارد. راهحل پایدار، افزودن شرط گارد در خود قانون است تا حلقه هرگز شکل نگیرد.
کلودیکپ در این زمینه چه کمکی میکند؟
خدمات مدیریتشده DevOps
برونسپاری کامل عملیات زیرساخت به تیمی متخصص، با پشتیبانی مستمر.
مشاهده جزئیات خدمتراهاندازی Jira اختصاصی (Self-Hosted)
استقرار، پیکربندی و پشتیبانی Jira بهصورت اختصاصی و خودمیزبان، متناسب با ساختار تیمها، پروژهها و فرآیندهای سازمان شما.
مشاهده جزئیات خدمتبهینهسازی عملکرد و هزینه
شناسایی گلوگاههای عملکردی و منابع بلااستفاده برای کاهش هزینه و افزایش سرعت.
مشاهده جزئیات خدمت
قطع شدن WebSocket در HAProxy هر ۵۰ ثانیه؛ علت و راهحل
اگر WebSocket یا SSE پشت HAProxy هر ۵۰ ثانیه قطع میشود، مقصر تایماوتهای پیشفرض فایل نمونه است. در این مقاله علت ریشهای و پیکربندی درست timeout tunnel را بررسی میکنیم.

پرشدن دیسک PostgreSQL با WAL اسلات replication غیرفعال
یک replication slot غیرفعال میتواند pg_wal را تا پر شدن کامل دیسک نگه دارد و دیتابیس را عملاً از کار بیندازد. در این مقاله ریشهیابی، رفع و پیشگیری از آن را گامبهگام بررسی میکنیم.

«idle in transaction» در PostgreSQL: قاتل خاموش autovacuum
نشستهای idle in transaction تراکنش را باز نگه میدارند، جلوی autovacuum را میگیرند و جدولها را باد میکنند تا جایی که دیسک پر و اتصالها تمام میشود. این مقاله تشخیص و درمان کامل آن است.
در پیادهسازی به کمک نیاز دارید؟
تیم کلودیکپ همین کار را هر روز برای تیمهای دیگر انجام میدهد. اگر جایی گیر کردهاید، با ما صحبت کنید.