یکی از گیجکنندهترین خطاها در محیطهای توسعه و نرمافزارهای مدرن وب، مشاهده پیام قطعی اینترنت در اتصال به لوکالهاست است! تصور کنید مرورگر را باز میکنید تا به پنل وب هرمس ایجنت روی سیستم شخصی خود متصل شوید، اما با صفحهای تیره و خطای You are offline مواجه میشوید. در این تحلیل فنی و ریشهیابی عمیق (Root Cause Analysis)، سه لایه مختلف شامل Service Worker فرانتاند، ناهماهنگی مرگبار در Launchd سیستمعامل مک و شلیکهای عجولانه اسکریپت مانیتورینگ سلامت را کالبدشکافی کرده و راهکار درمان قطعی آن را بررسی میکنیم.
پیام خطای مشاهدهشده در مرورگر (حتی در غیاب هرگونه قطعی شبکه):
You are offline Hermes requires a server connection. Please check your network and try again. Target URL: http://localhost:8787/session/49da4896b271
رفع فوری چرخه مرگ Launchd و آزادسازی وبسرور (زیر ۵ ثانیه)
اگر روی مک با این خطا روبرو شدید، برای شکستن حلقه مرگ پروسهها و حذف کلید مخرب KeepAlive، دستور زیر را در ترمینال کپی و اجرا کنید:
python3 -c "import pathlib; p = pathlib.Path.home() / 'Library/LaunchAgents/ai.hermes.autoupdate.plist'; c = p.read_text('utf-8'); c = c.replace('KeepAlive
', '').replace('KeepAlive', ''); p.write_text(c, 'utf-8'); print('[+] کلید تضاددار KeepAlive با موفقیت حذف شد!')" && launchctl unload ~/Library/LaunchAgents/ai.hermes.autoupdate.plist 2>/dev/null; launchctl load ~/Library/LaunchAgents/ai.hermes.autoupdate.plist 2>/dev/null; launchctl kickstart -k gui/$(id -u)/ai.hermes.webui
۱. مقدمه و معمای صورت مسئله
هنگام تلاش برای باز کردن یک نشست کاری روی پورت پیشفرض هرمس (http://localhost:8787)، کاربر با خطای آفلاین روبرو میشد. واکنش غریزی اولیه این است که اتصال اینترنت یا پروکسی چک شود. اما هرمس روی لوکالهاست اجرا میشود؛ یعنی ترافیک شبکه اصلاً از کارت شبکه و سیستمعامل خارج نمیشود که وابسته به اینترنت جهانی باشد.
پس چه چیزی باعث شده بود سیستم این خطا را نمایش دهد؟ پاسخ در کالبدشکافی ۳ لایه پنهان فنی نهفته است:
۲. لایه اول: پیام خطا دقیقاً از کجا صادر شد؟ (رفتار پنهان Service Worker در PWA)
برخلاف برداشت رایج، این پیام توسط هسته پایتون یا وبسرور صادر نشده بود. در حقیقت در لحظه درخواست، وبسرور اصلاً زنده نبود که پاسخی بدهد! فرانتاند وب هرمس بر پایه معماری PWA (Progressive Web App) طراحی شده است. در این معماری، فایلی به نام static/sw.js به عنوان واسط میان مرورگر و شبکه قرار دارد و تمام درخواستهای ناوبری صفحه را رهگیری میکند.
قطعه کد زیر در سورس سرویسورکر وظیفه مدیریت وضعیت ناوبری را بر عهده دارد:
if (event.request.mode === 'navigate') {
event.respondWith(
fetch(new Request(event.request, { cache: 'no-store' }))
.then((response) => response)
.catch(() => {
// زمانی که ارتباط سوکت با پورت ۸۷۸۷ برقرار نمیشود:
return caches.match('./').then((cached) => cached || new Response(
'<html><body style="...">' +
'<h2>You are offline</h2>' +
'<p>Hermes requires a server connection. Please check your network and try again.</p>' +
'</body></html>',
{ headers: { 'Content-Type': 'text/html' } }
));
})
);
}
نتیجهگیری لایه اول: مرورگر تلاش میکرد صفحه را از پورت 8787 دریافت کند. چون هیچ پروسهای روی این پورت شنود نمیکرد، درخواست fetch() به خطای شبکه (Network Error) خورد. در نتیجه بلافاصله بلوک catch. فعال شد و قالب استاتیک آفلاین را تحویل کاربر داد.
پرسش کلیدی به لایه بعدی منتقل میشود: چرا پورت ۸۷۸۷ بسته بود و پروسه وبسرور کجاست؟
۳. لایه دوم: ریشهیابی در سطح سیستمعامل (Launchd Thrashing Loop)
با ورود به لایه سیستمعامل مک و بررسی وضعیت سرویسها از طریق دستور launchctl، یک رفتار عجیب و بحرانی آشکار شد:
$ launchctl list | grep ai.hermes - -15 ai.hermes.webui
کد خروجی 15- به معنی دریافت سیگنال SIGTERM (خاتمه اجباری پروسه توسط سیستمی دیگر) است. علاوه بر این، متغیر شمارنده دفعات اجرای سرویس عدد ۱۷۱ بار اجرا در چند دقیقه را نشان میداد! وبسرور مدام متولد میشد و بلافاصله به قتل میرسید.
مظنون اصلی: تضاد ساختاری در کانفیگ `ai.hermes.autoupdate.plist`
سرویس بروزرسانی و مراقبت دورهای هرمس دارای فایل پیکربندی زیر در لانچد بود:
<key>RunAtLoad</key> <true/> <key>StartInterval</key> <integer>3600</integer> <key>KeepAlive</key> <true/>
خطای فلسفی و معماری کجاست؟
- کلید
StartInterval = 3600: به دیمن لانچد اعلام میکند که این اسکریپت را مانند یک کرونجاب (Cron Job) هر یک ساعت یکبار صدا بزن. - کلید
KeepAlive = true: همزمان به سیستمعامل دستور میدهد که این برنامه یک سرویس پیوسته و همیشهروشن است؛ بنابراین به محض اینکه وظیفهاش تمام شد و خارج شد (حتی با کد خروجی صفر)، بلافاصله و بدون یک میلیثانیه مکث مجدداً آن را احیا کن!
وجود KeepAlive عملاً کلید زمانبندی یکساعته را بیاثر کرده بود. در نتیجه اسکریپت آپدیت و مراقبت در یک حلقه نامتناهی، هر ۲ الی ۳ ثانیه از اول اجرا میشد.
۴. لایه سوم: ترور ناخواسته وبسرور توسط اسکریپت سلامتسنج
داخل اسکریپت آپدیت خودکار (hermes_stack_autoupdate.sh)، قطعه کدی برای بررسی سلامت وبسرور قرار داشت:
if lsof -iTCP:8787 -sTCP:LISTEN -P >/dev/null 2>&1; then
log "OK: Hermes WebUI is listening on port 8787"
else
log "ALERT: Hermes WebUI is down. Restarting via launchctl..."
launchctl kickstart -k "gui/$UID_NUM/ai.hermes.webui" 2>/dev/null || true
fi
تراژدی چطور رخ میداد؟
- وبسرور پایتون برای بارگذاری ماژولهای هوش مصنوعی، تنظیمات شبکه و کش به ۳ الی ۴ ثانیه زمان احتیاج دارد تا سوکت پورت ۸۷۸۷ را باز کند.
- اسکریپت سلامتسنج به علت لوپ
KeepAlive، هر ۲ ثانیه یکبار از راه میرسید. - در ثانیه اول که سرور هنوز در مرحله آمادهسازی اولیه (Cold Boot) بود، واچداگ پورت را بررسی میکرد و میدید پورت باز نیست.
- واچداگ به سرعت نتیجه میگرفت که سرور کرش کرده و مرده است!
- دستور
launchctl kickstart -kصادر میشد. سوییچk-یعنی ارسال فوری سیگنال SIGTERM و ترور پروسه قبلی. - سرور قبل از آنکه پورت را باز کند کشته میشد، پردازه جدید شروع میشد، و ۲ ثانیه بعد دوباره با یک سیگنال دیگر به قتل میرسید!
- این چرخه مرگ بیش از ۱۷۰ بار تکرار شد، لاگها به بیش از ۵۰ مگابایت افزایش پیدا کردند و با اشغال ظرفیت، خطای
Errno 28: No space left on deviceسیستم را دچار خفگی کرد.
۵. دیاگرام مقایسهای چرخه مرگ در برابر چرخه پایدار
LaunchAgent (KeepAlive: true)
│
├──▶ اجرای مکرر اسکریپت هر ۲ ثانیه
│ │
│ ├──▶ بررسی پورت ۸۷۸۷ با lsof (سرور هنوز در ثانیه اول بوت است)
│ └──▶ هشدار: پورت بسته است!
│ │
│ └──▶ شلیک SIGTERM با launchctl kickstart -k
│ │
│ └──▶ ترور وبسرور در حال بالا آمدن (Exit Code -15)
│
└──▶ حلقه نامتناهی (۱۷۰+ ریاستارت، پر شدن لاگ و خطای You are offline در PWA)
- - - - - - - - - - - - - - - - - - - - - - - - - - -
[۲. چرخه پایدار و نجاتیافته پس از اصلاح]:
LaunchAgent (فقط StartInterval: 3600)
│
├──▶ اجرای کنترلشده فقط هر ۱ ساعت یکبار
│
├──▶ حذف شلیکهای پیدرپی SIGTERM
│
├──▶ وبسرور پایتون فرصت کافی (۳ الی ۴ ثانیه) برای بوت و باز کردن سوکت پیدا میکند
│
├──▶ پورت ۸۷۸۷ در وضعیت LISTEN قرار میگیرد
│
└──▶ سرویسورکر PWA ترافیک را دریافت میکند (HTTP 200 OK)
۶. مراحل اجرای راهکار دائمی (Step-by-Step Fix)
گام اول: اصلاح فایل LaunchAgent
کلید <key>KeepAlive</key><true/> را از فایل کانفیگ حذف کنید تا اسکریپت فقط در فواصل یکساعته اجرا شود و به دیمن مداوم تبدیل نگردد:
sed -i '' '/KeepAlive/d' ~/Library/LaunchAgents/ai.hermes.autoupdate.plist sed -i '' '//d' ~/Library/LaunchAgents/ai.hermes.autoupdate.plist
گام دوم: ایجاد مهلت تنفس (Grace Period) در اسکریپت واچداگ
به جای شلیک فوری با فلگ k-، واچداگ باید حداقل چند ثانیه تامل کند و در صورت عدم پاسخگویی، ابتدا بررسی کند که آیا پردازهای در حال حاضر مشغول بوت است یا خیر:
# مهلت تنفس پیش از تصمیمگیری نهایی
if ! lsof -iTCP:8787 -sTCP:LISTEN -P >/dev/null 2>&1; then
sleep 5
if ! lsof -iTCP:8787 -sTCP:LISTEN -P >/dev/null 2>&1; then
log "ALERT: Hermes WebUI is unresponsive after grace period. Restarting..."
launchctl kickstart -k "gui/$UID_NUM/ai.hermes.webui" 2>/dev/null || true
fi
fi
گام سوم: پاکسازی لاگهای متورم
فایل لاگ که به دلیل چرخه مرگ صدها بار خطای تکراری ثبت کرده بود را پاکسازی کنید تا دیسک آزاد شود:
truncate -s 0 ~/.hermes/webui.log
گام چهارم: استارت تمیز سرویس و راستیآزمایی سلامت
با اجرای دستور زیر سرویس را لود کرده و با اندپوینت سلامتسنج وضعیت را چک کنید:
launchctl kickstart -k gui/$(id -u)/ai.hermes.webui sleep 4 curl -s http://127.0.0.1:8787/health | jq
پاسخ استاندارد و پایدار به شکل زیر دریافت میشود:
{
"status": "ok",
"sessions": 4,
"active_streams": 1,
"active_runs": 1,
"model": "ag/gemini-3.8-flash-medium",
"provider": "custom:9router",
"uptime_seconds": 136.6
}
۷. درسهای کلیدی برای مهندسان سیستم و دواپس (Takeaways)
- هرگز
StartIntervalرا باKeepAlive: trueدر Launchd ترکیب نکنید: این دو دستور ماهیت متضاد دارند. اولی برای اجرای پریودیک است و دومی برای دیمنهای همیشه بیدار. ترکیب این دو، پردازنده را وارد حلقه اجرای مداوم میکند. - واچداگ نباید شتابزده باشد (Defensive Monitoring): در اسکریپتهای بررسی سلامت، همواره زمان راهاندازی (Warm-up Period) سرویسها را لحاظ کنید. کشتن ناگهانی پردازهای که هنوز در حال بارگذاری است، مانع رسیدن آن به نقطه پایدار میشود.
- پیامهای خطای PWA میتوانند گمراهکننده باشند: خطای
You are offlineاز جانب سرویسورکر، لزوماً به معنای قطعی اینترنت نیست؛ بلکه نشاندهنده شکست کلاینت در برقراری ارتباط با سرور مبدا (حتی روی لوکالهاست) است. هنگام طراحی PWA، پیامهای خطای دقیقتری مانند «عدم پاسخگویی سرور» پیادهسازی کنید.
دیدگاهها و پرسش و پاسخ
پرسش و پاسخ و نظرات (0)