תיעוד 140
פריסת סביבות באירוח עצמי לייצור
הרצת runners באירוח עצמי בייצור: הקשחת אבטחה, בקרת תעבורת יציאה ברשת, אישורי git, מתכוני Kubernetes ו-Compose, ופתרון בעיות.
[!NOTE]
סביבות באירוח עצמי נמצאות בבטא ציבורית בתוכניות Team ו-Enterprise. זמינות ומגבלות מכסה את נתיב ההפעלה. הדף הזה מכסה הרצת הצי בייצור. ראו את ההתחלה המהירה עבור ה-runner והסשן הראשונים שלכם.
סביבה באירוח עצמי מריצה סשנים בענן של Claude Code על runners שאתם פורסים בתוך הרשת שלכם, ובייצור הסשנים האלה מריצים קוד שהמודל מכוון, בשם כל מי שיכול לשגר סשן לסביבה. הדף הזה מיועד למפעיל שלוקח סביבה עובדת לייצור. הוא עובר על הפריסה לפי הסדר: מה לנעול לפני חיבור מערכות אמיתיות, תעבורת היציאה שהצי צריך, איך סשנים מאמתים מול מארח ה-git שלכם, מתכוני הפריסה עצמם, ומה לבדוק כשסשנים מתנהגים לא כשורה.
#הקשיחו את הפריסה
runner באירוח עצמי מריץ קוד שרירותי שהמודל מכוון על התשתית שלכם, בשם כל מי שיכול לשגר סשן לסביבה שלו. זה כל חבר בארגון Anthropic שלכם, וכל מי שיכול להתחיל סשן ערוץ Claude Tag בטווח ש-Owner ניתב לסביבה. עברו על כל פריט לפני שאתם מחברים סביבה למערכות ייצור:
קונטיינרים חולפים, אחד לכל סשן: הריצו כל תהליך runner בקונטיינר או VM חדש שנהרס כשהתהליך יוצא, עם
--capacity 1ועם ברירת המחדל--drain-grace-sec 0, כך שכל קונטיינר משרת בדיוק סשן אחד. בקיבולת גבוהה יותר, או עם drain grace חיובי, קונטיינר אחד משרת כמה סשנים מאותו בעלים נעול. ראו מחזור חיי runner. אל תעשו שימוש חוזר במערכת קבצים בין הפעלות מחדש של runner, חוץ מההגדרה המכוונת של checkout שחומם מראש, ולעולם לא בין בעלים.- כשה-runner עוצר סשן, הוא לא שולח אות לתהליך שעדיין רץ אחרי שפקודת ה-shell שלו הסתיימה, כמו שירות שעבר ל-daemon. השמדת הקונטיינר או ה-VM מסיימת את התהליך.
בלי אישורים רחבים בתמונה: אל תכללו מפתחות SSH ארוכי טווח, אישורים של ספק ענן, או אסימוני גישה אישית שמעניקים יותר ממה שסשן צריך. הנפיקו אישורים שמשמשים במהלך סשן, כמו אסימוני push או API, לכל סשן מתוך סקריפט העטיפה שלכם. השכפול הראשוני קורה לפני שסקריפט העטיפה רץ, לכן טפלו בו עם hook של מחזור חיים מסוג
checkout, או עם--use-anthropic-git-proxyכשכל המאגרים של הסשן נמצאים ב-github.com. לשני המקרים, ראו הגדרת git.הרחיקו את אישורי GitHub של המארח מהסשנים: Claude יכול להשתמש בכל אישור GitHub שסשן יכול לקרוא, עם כל הגישה שהאישור הזה מעניק. הרחיקו את אישורי GitHub בעלי ההיקף הרחב של מארח ה-runner עצמו מכל דבר שסשן יכול לקרוא. אישור כזה יכול להיות אסימון גישה אישית, האסימון ש-
gh auth loginשומר עבור החשבון שלכם, אוGH_TOKENבסביבת ה-runner.- עם git בניהול Anthropic: עם אישור כזה, Claude מגיע ל-GitHub ישירות ולא דרך git בניהול Anthropic.
- בלי git בניהול Anthropic: אישור שכפול יכול להישאר בתמונה אם אתם מצמצמים את ההיקף שלו באותה מידה שמתוארת ב-משלוח הגדרות git בתמונה.
הרחיקו את סוד הסביבה ממארחים שמריצים סשנים: סוד הסביבה יכול לרשום runners ולאסוף כל סשן שנמצא בתור על הסביבה. בצי קבוע הוא נמצא על כל מארח runner, ושם הקוד של כל סשן יכול לקרוא את קובץ הסוד. העדיפו runners לפי דרישה, שבהם הסוד נשאר על מארח ה-orchestrator, שאינו מריץ קוד משתמש, וכל runner מקבל הזמנת עבודה חד פעמית שרושמת בדיוק runner אחד. בצי קבוע, התייחסו לקובץ סוד הסביבה כקריא לכל סשן, וסובבו את הסוד אחרי כל חשד שהסשן נפגע.
תעבורת יציאה ברשת עם ברירת מחדל של חסימה: הגבילו תעבורה יוצאת של קונטיינרי runner ושל קונטיינרי סשן בגבול הרשת שלכם, בכל סביבה. תעבורת יציאה עם ברירת מחדל של חסימה מכסה מה לאפשר ולמה.
IAM של המארח לפי הרשאה מזערית: זהות המחשוב שמחוברת למארח ה-runner, כמו instance profile או חשבון שירות של node, צריכה להעניק רק את מה שה-runner עצמו צריך. סשנים צריכים להשיג את האישורים שלהם דרך סקריפט העטיפה שלכם, במקום לרשת את אישורי המארח.
חסמו את נקודת הקצה של metadata בענן מהסשנים: הרחקת סשנים מזהות המארח דורשת חסימת הגישה שלהם לנקודת הקצה של metadata בענן, ומדיניות יציאה ברמת תת רשת לא מיירטת תעבורת metadata מסוג link-local, לכן חסמו אותה בתוך הקונטיינר עצמו:
- IMDSv2 עם מגבלת hop של אחד
- GKE Workload Identity עם הסתרת metadata
- דחייה מפורשת עבור
169.254.169.254במרחב השמות של הרשת של קונטיינר הסשן
החסימה חלה גם על סקריפט העטיפה ועל hooks של מחזור החיים, כי הם חולקים את הקונטיינר. אמתו כל החלפת אסימון באמצעות JWT של הסשן מול שירות האסימונים שלכם, דרך תעבורת יציאה שברשימת ההיתר, או השתמשו בזהות web מבוססת קובץ, כמו IAM Roles for Service Accounts (IRSA) ב-Amazon EKS.
בידוד מערכת קבצים לכל runner: כל תהליך runner מקבל תיקיית עבודה משלו, ששום תהליך אחר על המארח לא יכול לקרוא אותה או לכתוב אליה. הפכו את
--hooks-dir, את סקריפט העטיפה ואת~/.claude/של המארח לקריאה בלבד עבור הסשן, כשהם בנויים לתוך התמונה או מחוברים לקריאה בלבד.לשיגור אין בקרת גישה לפי סביבה: כל חבר בארגון Anthropic שלכם יכול לשגר סשן לכל אחת מהסביבות שלו. אם Owner מנתב ערוצי Claude Tag לסביבה, כל מי שהגדרת הגישה של Claude Tag מתירה יכול להתחיל סשני ערוץ שרצים שם. כברירת מחדל זה כל אדם בסביבת העבודה המחוברת ב-Slack, עם חשבון Claude או בלעדיו. התייחסו לכל מארח runner כאל מארח שנגיש להרצת קוד לכל מי שיכול לשגר אליו, ושימו על מארח runner רק נתונים ואישורים שכל האנשים האלה רשאים לקרוא.
--lock-to-accountתוחם אילו סשנים של איזה חשבון מארח נתון מריץ, אבל הוא לא מצמצם מי יכול לשגר לתוך הסביבה. כדי להפוך סביבות באירוח עצמי לאפשרות היחידה בבורר, Owner יכול להסתיר סביבות באירוח Anthropic עבור כל הארגון מדף Cloud environments.אכפו את מגן הגדרות המאגר: בחרו את מצב המגן עם
--confine-repo-settings. ברירת המחדלwarnרושמת הפרה ביומן ועדיין מקימה את הסשן,enforceמסרבת לסשן, ו-offמשביתה את הסריקה. ה-runner סורק את ההגדרות שנשמרו ב-commit של כל מאגר, ומחפש:- הענקה שנפתרת מחוץ לסביבת העבודה של אותו סשן: רשומת
additionalDirectories, כללEdit,WriteאוNotebookEditב-permissions.allow, או רשומתsandbox.filesystem.allowWriteאוallowRead - בלוק
envשאינו ריק - דריסת מצב מפעיל כמו
sandbox.enabled: false
המגן רץ בלי קשר ל-
--trust-workspace, והוא לא מכסה hooks של מאגר,.mcp.jsonאו כללי Bash. ראו הרשאות ואישור כלים כדי לראות לאן ההענקות האלה שייכות.- הענקה שנפתרת מחוץ לסביבת העבודה של אותו סשן: רשומת
[!NOTE]
אם בארגון שלכם מופעלת רשימת היתר כתובות IP, הוסיפו את כתובות היציאה הציבוריות של ה-runners ושל קונטיינרי הסשן לרשימת ההיתר לפני שאתם מפעילים אותם. אם אתם מריצים runners לפי דרישה, הוסיפו גם את הכתובת של מארח ה-orchestrator. אל תסתמכו על רשימת ההיתר כבקרת רשת לתעבורת runner או סשן. החילו תעבורת יציאה עם ברירת מחדל של חסימה בגבול הרשת שלכם.
#דרישות רשת
ה-runner וילדי הסשן שהוא מפעיל יוצרים חיבורים יוצאים אל המארחים שלהלן. הגבל את תעבורת היציאה של קונטיינר הסשן למארחים אלה ולשירותים הפנימיים הספציפיים שהסשנים צריכים להגיע אליהם. דחייה כברירת מחדל לתעבורת יציאה מכסה איך ולמה.
המארחים האלה נדרשים תמיד:
| מארח | פורט | משמש עבור |
|---|---|---|
api.anthropic.com | 443, HTTPS. WSS עבור git שמנוהל על ידי Anthropic | מישור הבקרה של ה-runner וסטרימינג של סשנים, הסקת מודל, דגלי תכונות, אנליטיקת מוצר, שליפות מפתחות JWKS, חתימה על קומיטים, ו-git שמנוהל על ידי Anthropic כאשר --use-anthropic-git-proxy מוגדר |
מארח ה-git שלך, כגון github.com או מארח GitHub Enterprise שלך | 443 או 22 | שכפול ודחיפה של מאגרים בכל מארח git שהסשנים של ה-runner משתמשים בו. ב-runner שמשתמש ב---use-anthropic-git-proxy, ראה מתי נתיב github.com עדיין נדרש. |
runner שמשתמש ב---use-anthropic-git-proxy מנתב את תעבורת ה-git של github.com דרך api.anthropic.com, ולכן הוא לא צריך את נתיב מארח ה-git עבור github.com. הוא עדיין צריך את הנתיב הזה אם מגדירים --push-outcome-on-release או דוחפים מ-hook של post-session.
האם המארחים האלה נדרשים תלוי בתצורה שלך:
| מארח | פורט | מתי נדרש |
|---|---|---|
api.github.com | 443 | עם או בלי --use-anthropic-git-proxy, כאשר תמונת ה-runner כוללת את GitHub CLI ונותנים לסשנים אישור גישה ל-github.com, כגון GH_TOKEN |
downloads.claude.ai | 443 | בזמן התקנה, כאשר מתקינים או מעדכנים את Claude Code על המארח עם המתקין המקורי. סקריפט install.sh עצמו מוגש מ-claude.ai. בזמן ריצת סשן, רק כאשר סשנים מתקינים תוספים משוק Anthropic הרשמי. |
storage.googleapis.com | 443 | בזמן ריצת סשן, עבור ספירות התקנת תוספים ומטא-נתונים שמוצגים ב-/plugin. |
code.claude.com ו-claude.com | 443 | חיפושי תיעוד על ידי סוכן claude-code-guide המובנה ובקשות WebFetch שאושרו מראש במהלך סשנים. חסימת המארחים האלה משפיעה רק על חיפושי תיעוד. |
*.frame.claudeusercontent.com | 443 | רק כאשר כלי Artifact זמין לסשנים בארגון שלך. ברירות המחדל משתנות לפי תוכנית, לפי טבלת הזמינות שם. הגדר CLAUDE_CODE_DISABLE_ARTIFACT=1 על ה-runner כדי להשאיר את הכלי מושבת בלי קשר להגדרת הארגון. |
registry.npmjs.org | 443 | כאשר סשן מתקין תוסף, גם לשליפת חבילות תוסף ממקור npm וגם להתקנת תלויות Node.js של תוסף, או כאשר שרת MCP שמופעל באמצעות npx רץ |
| מראה פנימית של רישומי החבילות שהבניות שלך משתמשות בהם, או הרישומים עצמם | 443, או הפורט של המראה שלך | כאשר סשנים מתקינים תלויות כדי לבנות או לבדוק את המאגרים שלך |
http-intake.logs.us5.datadoghq.com | 443 | מדדים תפעוליים של Anthropic. רק כאשר CLAUDE_CODE_BYOC_ENABLE_DATADOG=1 מוגדר. כבוי כברירת מחדל בסביבות self-hosted. |
browser-intake-us5-datadoghq.com | 443 | העלאות דוחות שגיאה של Anthropic, שנשלחות רק כאשר דיווח שגיאות מופעל עבור חשבון הסשן. מדוכא על ידי DISABLE_ERROR_REPORTING=1 או DISABLE_TELEMETRY=1. |
נקודות הקצה של ספק הענן שלך לבקשות מודל, חיפושי מודל וחידוש אישורים, כגון bedrock-runtime.us-east-1.amazonaws.com או aiplatform.googleapis.com | 443 | רק כאשר ה-runner שולח בקשות מודל אל Amazon Bedrock או Agent Platform של Google Cloud |
אין צורך להוסיף את המארחים האלה לרשימת ההיתרים עבור תעבורת runner או סשן:
statsig.anthropic.com,*.sentry.io,claude.aiו-platform.claude.com: המארחים האלה מופיעים בחלק מרשימות הבדיקה הישנות יותר של רשת ארגונית, אבל ה-runner לא מגיע אליהם. שליפות דגלי תכונות הולכות אלapi.anthropic.com, וה-runner מאמת באמצעות סוד הסביבה ולא באמצעות OAuth אינטראקטיבי.mcp-proxy.anthropic.com: סשנים self-hosted לא משתמשים בו. כאשר אספקת מחברים מופעלת עבור הארגון שלך, המחברים של claude.ai של הארגון מגיעים לסשנים דרךapi.anthropic.com. ראה שרתי MCP.
הזרימות האלה בצד המארח כן מגיעות אל claude.ai, ולכן הרץ אותן ממארח שתעבורת היציאה שלו מתירה זאת, במקום להרחיב את תעבורת היציאה של קונטיינר הסשן:
- המתקין בשורה אחת: שולף את
install.shמ-claude.aiבזמן התקנה. claude auth loginאינטראקטיבי: מתחבר דרךclaude.ai,claude.comו-platform.claude.com. ההגדרה המודרכת, מצב המחובר שלdoctor, ו-שיגור CI משתמשים בו. הדפדפן שבו מתחברים טוען גם את בדיקות הדפדפן של דף ההתחברות של claude.ai מ-hcaptcha.com,*.hcaptcha.comו-challenges.cloudflare.com. אם הארגון משתמש ב-single sign-on, הדפדפן הזה יכול גם להיות מופנה דרךsso.claude.com.
#דחייה כברירת מחדל לתעבורת יציאה
פרוס קונטיינרים של runner ושל סשן במקטע רשת או ב-namespace שתעבורת היציאה שלו מוגבלת למארחים בטבלת דרישות הרשת, למארח ה-git שלך ולשירותים הפנימיים הספציפיים שהסשנים צריכים להגיע אליהם. המוצר לא יכול לאמת או לאכוף זאת, ולכן החל זאת בגבול הרשת שלך בכל סביבה. קוד הסשן מונחה על ידי המודל ויכול לנסות חיבורים למארחים שרירותיים. דחייה כברירת מחדל לתעבורת יציאה בשכבת הרשת תוחמת לאן הניסיונות האלה יכולים להגיע. זה חל בלי קשר למצב הרשאות: קבוצת הכלים שאושרה מראש כברירת מחדל כבר כוללת את Bash, ולכן יציאת מעטפת רצה בלי בקשה גם בלי מצב auto.
לפרטים על איזו טלמטריה כל סשן פולט ואיך לכבות אותה, ראה טלמטריה.
#אימות מול פרוקסי יציאה
חלק מפרוקסי היציאה הארגוניים דורשים כותרת Proxy-Authorization בכל חיבור. הטוקן בכותרת הזו לעתים קרובות מתחלף מהר מדי מכדי לכתוב אותו לתוך כתובת ה-URL של הפרוקסי שמגדירים ב-HTTPS_PROXY. הגדר את HTTPS_PROXY או HTTP_PROXY לכתובת ה-URL של הפרוקסי כרגיל, ואז הגדר --proxy-authorization-command או --proxy-authorization-file כדי לומר ל-runner מאיפה לקרוא את ערך הכותרת. שני הדגלים דורשים Claude Code v2.1.238 או חדש יותר.
#בחר מאיפה מגיע ערך Proxy-Authorization
בחר את הדגל שתואם לאופן שבו מייצרים את טוקן Proxy-Authorization:
--proxy-authorization-command <command>: בחר בזה עבור טוקן שמייצרים לפי דרישה. ה-runner מריץ את פקודת המעטפת ומשתמש ב-stdout הגזום שלה כערך הכותרת, לדוגמהBearer <token>.--proxy-authorization-file <path>: בחר בזה עבור טוקן שתהליך אחר מחליף במקום. ה-runner קורא את הקובץ ומשתמש בתוכן הגזום שלו כערך הכותרת.
#תצורות שה-runner מסרב להתחיל איתן
לכל דגל יש גם צורת משתנה סביבה, שמפורטת לידו ב-הפניה לדגלי CLI של runner. לפני שה-runner יוצר קשר עם הפרוקסי או עם מישור הבקרה, הוא בודק את הדגלים ואת המשתנים שלהם, ומסרב להתחיל בשלושה מקרים:
- שני הדגלים מוגדרים: דגל אחד בתוספת משתנה הסביבה של הדגל השני נחשב להגדרת שניהם.
- אין כתובת URL של פרוקסי: לא
HTTPS_PROXYולאHTTP_PROXYמחזיקים כתובת URL שלhttp://אוhttps://. ה-runner קורא את שני המשתנים באותיות גדולות או קטנות, ולא מתייעץ ב-ALL_PROXY. - אחד מהדגלים הועבר לתת-פקודת orchestrator:
self-hosted-runner orchestratorלא מקבל את הדגלים או את משתני הסביבה שלהם. העבר את הדגל לכל runner שה-orchestrator מפעיל במקום זאת.
#מה ה-runner משנה כאשר דגל proxy-authorization מוגדר
כאשר אחד מהדגלים מוגדר, ה-runner מפעיל מאזין משלו ושולח תעבורת פרוקסי מעצמו, מ-lifecycle hooks שלו ומהסשנים שלו דרך המאזין הזה. המאזין מוסיף את כותרת Proxy-Authorization בדרך אל הפרוקסי שלך.
- מאזין: המאזין הוא forward proxy על
127.0.0.1. ה-runner מפעיל את המאזין לפני הרישום מול מישור הבקרה, ויוצא בהפעלה אם המאזין לא יכול להתחיל. - משתני פרוקסי: ה-runner כותב מחדש את מי מבין
HTTPS_PROXYו-HTTP_PROXYשהגדרת כך שהוא מצביע אל המאזין. הערך שנכתב מחדש מגיע אל ה-runner עצמו, אל ה-lifecycle hooks שלו ואל כל סשן שהוא מריץ. - החלפת טוקן: טוקן שהוחלף נכנס לתוקף בלי הפעלה מחדש. עבור כל חיבור שהמאזין פותח אל הפרוקסי שלך, ה-runner מריץ את הפקודה שלך או קורא את הקובץ שלך שוב ומוסיף את התוצאה ככותרת.
- סביבת סשן: סשן מגיע אל הפרוקסי שלך רק דרך המאזין. בסביבה של כל סשן ה-runner מסיר את
ALL_PROXY, מסיר כל איות שלHTTPS_PROXYאוHTTP_PROXYשלא הגדרת, ומצמיד אתNO_PROXYלערך של ה-runner עצמו. - לוגים: ה-runner אף פעם לא רושם בלוג את ערך הכותרת.
#הגדרת git
ה-runner מנהל checkouts של מאגרים אבל לא מגדיר זהות git או אישורים כברירת מחדל. אתה שולט בתמונה של ה-runner ובסביבת התהליך, ולכן אתה שולט בתצורת git. בחר באחת משתי גישות:
- תן ל-runner להגדיר git: הפעל את ה-runner עם
--configure-gitכדי שהוא יכתוב את אותה זהות ואת אותה תצורת חתימת קומיטים שסשנים שמתארחים ב-Anthropic משתמשים בהן - כלול תצורת git בתמונה שלך: הגדר זהות ואישורי דחיפה בעצמך, לדוגמה כדי לבצע commit תחת זהות הבוט שלך
עבור מאגרים ב-github.com, אפשר גם להפעיל את ה-runner עם --use-anthropic-git-proxy, או להגדיר CLAUDE_RUNNER_USE_GIT_PROXY=1, כדי לבקש מ-Anthropic לשרת git עבור הסשנים של ה-runner.
גרסאות Git המינימליות על מארח ה-runner: חתימת קומיטים ב-SSH של --configure-git דורשת Git 2.34 או חדש יותר, --use-anthropic-git-proxy דורש 2.32 או חדש יותר, וחידוש סשנים מענפים שנדחפו על ידי --push-outcome-on-release דורש 2.29 או חדש יותר. Git 2.24 מספיק אם משמיטים את שלושתם ומנהלים את זהות git בעצמך.
#תן ל-runner להגדיר git
הפעל את ה-runner עם --configure-git, או הגדר SELF_HOSTED_RUNNER_CONFIGURE_GIT=1, כדי שהוא יכתוב תצורת git גלובלית בהפעלה:
user.name = Claudeו-user.email = [email protected], בהתאמה לסשנים שמתארחים ב-Anthropic- חתימה על קומיטים ותגיות בפורמט SSH, שמנותבת דרך shim שמנוהל על ידי ה-runner וחותם על כל קומיט באמצעות שירות החתימה של Anthropic תוך שימוש באישורים של הסשן עצמו. החתימות ניתנות לאימות ב-GitHub מול מפתח החתימה SSH ש-Anthropic פרסמה.
push.negotiate = true, כך ש-git שואל את מארח ה-git שלך אילו קומיטים כבר יש לו לפני אריזת דחיפה. דורש Claude Code v2.1.257 או חדש יותר.core.hooksPathשמצביע אל ספריית hooks שמנוהלת על ידי ה-runner. ה-hookscommit-msgו-prepare-commit-msgשלו מוסיפים trailer שלCo-authored-by:עבור יוצר הסשן לכל קומיט. ה-trailer נבנה מהאימייל ב-CCR_SESSION_ACCOUNT_EMAILומושמט כאשר המשתנה הזה לא מוגדר. אם התמונה שלך כבר מגדירהcore.hooksPathוה-runner לא משתמש ב-git שמנוהל על ידי Anthropic, ה-runner שומר על ההגדרה שלך, מדלג על התקנת ה-hooks האלה, ומדפיס אזהרת[runner:git].
חתימה על קומיטים דורשת git 2.34 או חדש יותר. ה-runner בודק בהפעלה ויוצא עם שגיאה אם ה-git שלך ישן יותר. הדגל הזה לא מגדיר אישורי דחיפה, שעדיין מספקים בתמונה.
ב-runner בגרסה v2.1.280 או חדשה יותר, קומיטים שעושים מ-lifecycle hook של checkout או post-session נחתמים גם הם בתור הסשן, בלי trailer של Co-authored-by:. תצורת Git בתוך lifecycle hooks מתארת את הגדרות ה-git שה-runner מתקן בתוך ה-hooks האלה.
עם או בלי --configure-git, Claude Code מורה ל-Claude לסיים את הודעות הקומיט שלו ב-trailer של Claude-Session: <url> ואת תיאורי ה-pull request שלו בכתובת ה-URL של הסשן. כדי להשמיט את שניהם, הגדר attribution.sessionUrl ל-false ב-~/.claude/settings.json של מארח ה-runner, ואז הפעל מחדש את ה-runner.
#כלול תצורת git בתמונה שלך
זהות git נדרשת לכל קומיט. הגדר אותה ברמת המערכת ב-Dockerfile שלך כדי שהתצורה תחול בלי קשר למשתמש שתהליך ה-runner רץ בתורו:
RUN git config --system user.name "Claude" && \
git config --system user.email "[email protected]"בלי זהות, git commit נכשל עם Please tell me who you are וסשנים לא יכולים להתקדם. אפשר להשתמש בזהות הבוט שלך במקום. ה-runner לא דורס את הערכים האלה.
אל תאפה אישורי דחיפה ארוכי טווח או בעלי היקף רחב לתוך תמונת runner משותפת: אישור בתמונה זמין לכל סשן שהתמונה מריצה, מי שהתחיל אותו. במקום זאת, הנפק טוקן קצר טווח ובהיקף המצומצם ביותר לכל סשן מסקריפט ה-wrapper שלך, תוך שימוש בזהות של יוצר הסשן שמפוענחת מ-JWT של הסשן. צמד אותו לקונטיינר ארעי לכל סשן, שדורש --capacity 1, כך שאף אישור לא מאריך ימים מעבר לסשן שהנפיק אותו. ראה את סעיף החיזוק.
אם חייבים להגדיר אישורי דחיפה ברמת התמונה, לדוגמה עבור מפתח deploy לקריאה בלבד, הגבל את ההיקף שלהם עד כמה שמארח ה-git שלך מאפשר:
- מפתח deploy ב-SSH שמוגבל למאגר אחד עם שכתוב
url.<base>.insteadOf credential.helperשמחזיר טוקן בהיקף מינימליGIT_SSH_COMMANDשמצביע אל מפתח בהיקף צר
כל מנגנון שמגדירים חייב לעבוד בלי בקשה, כי ה-clone וה-fetch המובנים של ה-runner משביתים את הבקשות ש-git, SSH ו-Git Credential Manager היו מציגים אחרת:
- ה-runner מגדיר
GIT_TERMINAL_PROMPT=0, כך ש-git לא מבקש שם משתמש או סיסמה. - ה-runner מריץ SSH עם
BatchMode=yes, שמתווסף ל-GIT_SSH_COMMANDשלך אם הגדרת אחד, כך ש-SSH לא מבקש passphrase או אישור מארח. - ה-runner מגדיר
GCM_INTERACTIVE=never, כך ש-Git Credential Manager לא פותח תיבת התחברות. לפני v2.1.229, הכנת מאגר יכלה להיתקע על בקשה של Git Credential Manager. - ה-runner מנקה את
core.askPass, כך שאם משתמשים בעוזר askpass, מגדירים אותו דרך משתנה הסביבהGIT_ASKPASS.
אם מארח ה-git שלך דוחה את האישור, או שלא הגדרת אישור, ה-runner מנסה שוב כמה פעמים ואז נכשל בהכנת המאגר כאשר המאגר הוא זה שהסשן דוחף אליו תוצאות. עבור מאגר שהסשן רק קורא ממנו, פתרון בעיות מכסה מתי ה-runner מדלג עליו במקום זאת. ה-runner לא מעביר את ההגדרות האלה לסביבה של הסשן.
שמור כל תוכנית שנוקבים בשמה ב-GIT_SSH_COMMAND או ב-GIT_ASKPASS במקום שסשנים לא יכולים לכתוב אליו, באופן שרשימת הבדיקה לחיזוק מבקשת עבור ספריית ה-hooks וסקריפט ה-wrapper. כך גם לכל מפתח או קובץ בשורת הפקודה של התוכנית הזו. ה-git של ה-runner עצמו מריץ את התוכנית הזו כאשר הוא משכפל או שולף.
אם ספריות checkout בבעלות uid שונה מזה של תהליך ה-runner, git מסרב לפעול עליהן. הוסף safe.directory:
RUN git config --system --add safe.directory '*'#שימוש בפרוקסי ה-git של Anthropic
עם פרוקסי ה-git של Anthropic, שנקרא גם git מנוהל על ידי Anthropic, תמונת ה-runner לא צריכה מפתחות SSH, credential helper, .netrc, או פרטי גישה אחרים של git עבור הסשן עצמו. במקום זאת, ה-runner מבקש מ-Anthropic לשרת git עבור הסשנים שלו. עבור סשן של משתמש ש-Anthropic משרתת, ה-clone של ה-runner ופעולות ה-fetch וה-push של הסשן עצמו עוברים דרך Anthropic, שמשתמשת בטוקן OAuth של GitHub השמור עבור יוצר הסשן. איך Anthropic משרתת git עבור סשן מכסה סשני בוט וסוכן.
פרוקסי ה-git כבוי אלא אם מפעילים אותו. runner שמגיע למארח ה-git שלך עם פרטי הגישה שלו לא צריך אותו, וה-git שלו עובד עם כל מארח git.
בתמורה, פרוקסי ה-git מגביל את מה שה-runner תומך בו ומשנה את מה שהוא צריך:
רק github.com: Anthropic משרתת סשן רק כאשר כל המאגרים שלו נמצאים ב-github.com, ופרוקסי ה-git עדיין לא תומך ב-GitHub Enterprise Server. ב-runner עם פרוקסי ה-git, סשן עם מאגר על מארח git אחר נכשל בהתחלה.
פרטי גישה רק למאגרים של הסשן: Anthropic מספקת פרטי גישה של git למאגרים שהם חלק מהסשן, לא למאגרים אחרים על אותו מארח git. submodule פרטי, תלות שמנהל החבילות שלך מביא עם git, או שוק תוספים במאגר אחר לא מקבלים פרטי גישה מ-Anthropic. בקשו מהאנשים שיוצרים סשנים להוסיף כל מאגר שהסשן צריך כשהם יוצרים אותו.
כל הרצה של משימה מתוזמנת בענן היא סשן עם המאגרים של המשימה, אז בקשו מבעל כל משימה להוסיף כל מאגר שההרצות שלה צריכות.
דחיפות לענפים בלבד: דחיפה שמוחקת ענף נכשלת, וכך גם דחיפה לכל סוג אחר של ref, כמו תג. לגבי אילו ענפים דחיפה יכולה לעדכן, ראו פרוקסי GitHub.
חשבונות GitHub מחוברים: בקשו מכל אדם שיוצר סשני משתמש עם מאגר לחבר את GitHub. בלי החיבור שלו, הסשנים האלה לא מתחילים.
--capacity 1: פרוקסי ה-git דורש סשן אחד לכל תהליך runner, אז הריצו יותר replicas למקביליות. הפעלת פרוקסי ה-git של Anthropic מפרטת את הדרישות.החלפת תצורת git גלובלית: ה-runner מוחק ומחליף את תצורת ה-git הגלובלית של המשתמש שהוא רץ בתורו. הריצו אותו כמשתמש ייעודי או בתוך קונטיינר.
פרטי גישה של המארח לדחיפות של המארח: הדחיפה
--push-outcome-on-releaseשל ה-runner וכל דחיפה שה-hook שלpost-sessionמבצע עדיין משתמשות בפרטי הגישה של git של מארח ה-runner ובנתיב הרשת שלו אלgithub.com. השאירו את פרטי הגישה האלה מחוץ לתצורת ה-git הגלובלית, וראו שינוע תצורת git בתמונה לדרכים לספק אותם.החלטה לכל סשן: Anthropic מחליטה עבור כל סשן על ה-runner אם לשרת את ה-git שלו, וסשן שהיא לא משרתת נכשל בהתחלה. כאשר סשנים נכשלים בהתחלה על runner עם פרוקסי ה-git מכסה את הסיבות.
[!WARNING]
כש-
--use-anthropic-git-proxyמוגדר, ה-runner מוחק ומחליף את תצורת ה-git הגלובלית של המשתמש שהוא רץ בתורו, ולא שומר גיבוי. הוא עושה זאת בעת ההפעלה ולפני כל סשן. התחברות אוcredential helperששמרתם שם אובדים. הגדרות ש---configure-gitכותב נשמרות. הריצו את ה-runner כמשתמש ייעודי או בתוך קונטיינר, לעולם לא כמשתמש שלכם.
השאירו הגדרות git שאינן סודיות, כמו זהות ו-safe.directory, בתצורת ה-git של המערכת.
#הפעלת פרוקסי ה-git של Anthropic
לפני שמפעילים את ה-runner עם --use-anthropic-git-proxy, אשרו שמארח ה-runner עומד בכל אחת מהדרישות האלה. ה-runner מסרב להתחיל כאשר דרישת הקיבולת או דרישת ה-git לא מתקיימת:
- Claude Code v2.1.267 או חדש יותר: גרסאות מוקדמות מקבלות את הדגל אבל לא מדווחות על הבקשה ש-Anthropic תשרת git ולא מדפיסות את השורה
Registering as opted in, ולכן Anthropic לא משרתת את הסשנים שלהן. --capacity 1, ברירת המחדל: כל תהליך runner מטפל בסשן אחד בכל פעם, אז הריצו יותר replicas למקביליות.- Git 2.32 או חדש יותר: git ישן יותר מתעלם מתצורת ה-git לכל סשן שה-runner מגדיר עבור פרוקסי ה-git.
[!WARNING]
המתכונים של Kubernetes ושל Docker Compose בעמוד זה משתמשים ב-
--capacity 4. אם מוסיפים--use-anthropic-git-proxyאוCLAUDE_RUNNER_USE_GIT_PROXY=1לאחד מהם בלי לשנות את הקיבולת ל-1, ה-runner יוצא בהפעלה בכל פעם שהמתזמר מפעיל אותו מחדש. הגדירו--capacity 1והריצו יותר replicas למקביליות. כאשר ה-runner יוצא מציג את השורה שה-runner מדפיס.
כדי להפעיל את פרוקסי ה-git, הוסיפו --use-anthropic-git-proxy לפקודה של ה-runner, או הגדירו CLAUDE_RUNNER_USE_GIT_PROXY=1 בסביבה של ה-runner. הפקודה הזו, שרצה במעטפת על מארח ה-runner, מפעילה את ה-runner של ההתחלה המהירה עם פרוקסי ה-git מופעל:
claude self-hosted-runner --environment-secret-file '/etc/claude/environment-secret' --base-dir '<writable-dir>' --use-anthropic-git-proxyבהפעלה, ה-runner מדפיס Registering as opted in to Anthropic-managed git (--use-anthropic-git-proxy). Anthropic מחליטה אז עבור כל סשן על אותו runner אם לשרת את ה-git שלו. עבור כל סשן שהיא משרתת, ה-runner רושם שורת [runner:session] שמכילה governed git ACTIVE. אם סשן נכשל בהתחלה במקום זאת, ראו כאשר סשנים נכשלים בהתחלה על runner עם פרוקסי ה-git.
#איך Anthropic משרתת git עבור סשן
עבור סשן ש-Anthropic משרתת, ה-clone של ה-runner ופעולות ה-fetch וה-push של הסשן עצמו עוברים דרך Anthropic, עם אימות באמצעות הטוקן קצר החיים של הסשן עצמו:
- סשני משתמש: Anthropic משתמשת בטוקן OAuth של GitHub השמור עבור יוצר הסשן.
- סשני בוט וסוכן: Anthropic משתמשת בטוקן ההתקנה של GitHub App של הארגון שלכם.
- שכתוב כתובות:
--git-host-rewriteו---git-ssh-rewriteלא משפיעים על מאגר שפרוקסי ה-git משרת.
#כאשר סשנים נכשלים בהתחלה על runner עם פרוקסי ה-git
על runner שהופעל עם --use-anthropic-git-proxy, סשן נכשל בהתחלה כאשר Anthropic לא משרתת את ה-git שלו. חפשו ביומן של ה-runner שגיאת git שמציינת כתובת api.anthropic.com שמכילה /git_proxy/.
עבור כל סשן, runner על Claude Code v2.1.267 או חדש יותר גם רושם או שורת [runner:session] שמכילה governed git ACTIVE כאשר Anthropic משרתת את ה-git של הסשן, או שורת [runner:warn] אחת שמכילה the server withheld Anthropic-managed git for this session כאשר היא לא. מצאו את השורה שאתם רואים בין המקרים האלה:
- לא
governed git ACTIVEולא שורת ה-withheld: runner ישן יותר מ-Claude Code v2.1.267 לא רושם אף אחת מהשורות, ו-Anthropic לא משרתת את הסשנים שלו. עדכנו את ה-runner ל-v2.1.267 או חדש יותר לפי נעיצת הגרסה. - שורת ה-
withheld: Anthropic לא שירתה את הסשן. runner שעבד עם פרוקסי ה-git קודם יכול להיכשל כך בלי שינוי מצדכם.- מאגר לא נמצא ב-github.com: סשן עם אפילו מאגר אחד על מארח git אחר, כמו GitHub Enterprise Server, לא מקבל שירות, כולל המאגרים שלו ב-github.com. כבו את פרוקסי ה-git של Anthropic עבור ה-runners של אותה סביבה.
- כל המאגרים נמצאים ב-github.com: דווחו על הכשל לצוות החשבון שלכם ב-Anthropic עם מזהה הסשן משורת ה-
withheld. Anthropic רושמת את הסיבה אצלה.
- שורה שמכילה
remote: access denied by the git proxy: סשן ש-Anthropic משרתת עדיין יכול להידחות, למשל כאשר מדיניות הארגון מונעת גישת git עבור הסשן, או שהסשן לא מורשה למאגר. יומן ה-runner מציג אז שורה שמכילהremote: access denied by the git proxy, ושאר השורה אומר למה. GitHub authentication required: זה מופיע כאשר ליוצר הסשן אין חיבור GitHub עובד ב-claude.ai. ה-clone של הסשן נכשל, ושגיאת ה-git מכילהGitHub authentication required. Please reconnect your GitHub account.בקשו מאותו אדם לחבר או לחבר מחדש את GitHub בהגדרות claude.ai שלו.
אחרי שמתקנים את הסיבה, התחילו שוב את הסשנים שנכשלו.
#כיבוי פרוקסי ה-git של Anthropic
אם סשנים בסביבה משתמשים במאגר על מארח git שאינו github.com, כמו GitHub Enterprise Server, כבו את --use-anthropic-git-proxy עבור ה-runners של אותה סביבה.
1. הסירו את הדגל
הסירו את --use-anthropic-git-proxy מפקודת ה-runner. אם הגדרתם CLAUDE_RUNNER_USE_GIT_PROXY בסביבה של ה-runner, כמו מפרט pod או קובץ Compose, הסירו אותו שם. במעטפת, בטלו אותו:
unset CLAUDE_RUNNER_USE_GIT_PROXY2. תנו ל-runner פרטי גישה של git
ספקו פרטי גישה שעובדים בלי בקשה לכל מארח git שהסשנים של ה-runners משתמשים בו, כולל github.com. כל פרטי גישה שהיו בתצורת ה-git הגלובלית של משתמש ה-runner נעלמו, כי ה-runner מחק את התצורה הזו בזמן ש---use-anthropic-git-proxy היה מוגדר. שנעו פרטי גישה בתמונה או השתמשו ב-hook מחזור חיים של checkout.
3. פתחו את נתיב הרשת
אפשרו ל-runner להגיע לכל מארח git שהסשנים של ה-runners משתמשים בו בפורט 443 או 22. ראו את שורת מארח ה-git בדרישות הרשת.
4. הפעילו מחדש את ה-runners
הפעילו מחדש את ה-runners כדי שהם יירשמו בלי פרוקסי ה-git. אחר כך התחילו שוב כל סשן שנכשל.
#גישה ל-API של GitHub בלי ה-CLI של GitHub
אם תמונת ה-runner לא כוללת את ה-CLI של GitHub, Claude Code יכול לספק gh מובנה, כך ש-Claude עדיין יכול לפתוח בקשות משיכה, להגיב ולקרוא תוצאות CI. ה-gh המובנה מיועד ל-runners שמשתמשים ב-git מנוהל על ידי Anthropic. הוא תומך בפקודה אחת, gh api, שקוראת ל-REST API של GitHub. נדרש Claude Code v2.1.287 או חדש יותר בתמונת ה-runner.
הפקודה הזו פותחת בקשת משיכה במקום gh pr create. ה-gh המובנה ממלא את {owner} ואת {repo} עבור המאגר הנוכחי:
gh api repos/{owner}/{repo}/pulls -f title='Fix' -f head='my-branch' -f base='main'- פרטי גישה: ה-
ghהמובנה שולח את בקשות ה-REST שלו דרך git מנוהל על ידי Anthropic, שמספק את פרטי הגישה של GitHub בצד של Anthropic, כך שהתמונה לא צריכה טוקן GitHub עבורו - אילו סשנים מקבלים אותו: Anthropic מחליטה לכל סשן אם git מנוהל על ידי Anthropic משרת את ה-
ghשל הסשן. כאשר כן, שורת[runner:session] governed git ACTIVEשה-runner רושם עבור הסשן מציגהgh_path_shim=true. כאשר לא, לסשן איןgh jq: התקינוjqבתמונה אם אתם רוצים ש---jqיעבודCLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: אם סביבת הסשן מגדירה אותו, Claude Code לא מספק את ה-ghהמובנה, ולסשן איןgh
#גישה ל-API של GitHub עם ה-CLI של GitHub
אם תמונת ה-runner כוללת את ה-CLI של GitHub לשימוש הסשנים, גרמו ל-סקריפט העטיפה שלכם להנפיק GH_TOKEN קצר חיים ובהיקף מזערי לכל סשן עבור יוצר הסשן. כאשר gh נמצא ב-PATH ש-Claude Code מתחיל איתו, לסשן יש את ה-gh הזה ואין gh מובנה. עם הטוקן, ה-gh הזה מגיע ל-GitHub עם כל הגישה שהטוקן מעניק ומחוץ ל-git מנוהל על ידי Anthropic, אז אפשרו את api.github.com.
עם git מנוהל על ידי Anthropic, השאירו את ה-CLI של GitHub מחוץ לתמונה כדי להשתמש ב-gh המובנה במקום.
#אמון ברשות אישורים פרטית עם git מנוהל על ידי Anthropic
הסעיף הזה חל אם מגדירים GIT_SSL_CAINFO או GIT_SSL_NO_VERIFY בסביבה של runner שהסשנים שלו משתמשים ב-git מנוהל על ידי Anthropic. הטיפול שהוא מתאר דורש שה-runner יריץ Claude Code v2.1.283 או חדש יותר.
כאשר git על ה-runner חייב לתת אמון ברשות אישורים (CA) פרטית, כמו זו שפרוקסי שבודק TLS חותם איתה, הגישות הרגילות פועלות כך:
- מאגר אישורים של המערכת: התקינו את ה-CA שלכם במאגר האישורים של מערכת מארח ה-runner, ו-git נותן בה אמון בלי אף אחד מהמשתנים.
GIT_SSL_CAINFO: הגדירו אותו לקובץ PEM של רשויות האישורים שלכם, למשלGIT_SSL_CAINFO=/etc/ssl/corp-ca.pem.GIT_SSL_NO_VERIFY: לא עוזר מאחורי פרוקסי שחותם מחדש. ה-clone של ה-runner עצמו דרך git מנוהל על ידי Anthropic בודק אישורים גם כאשר המשתנה מוגדר, כך שה-clone נכשל עד ש-git נותן אמון ב-CA שלכם דרך אחת משתי הגישות האחרות.
עבור חיבורי git שנושאים טוקן של סשן אל git מנוהל על ידי Anthropic, ה-runner מחיל את שני המשתנים כך. hook של command מתחיל עם סביבת הסשן, אז הוא מקבל את מה ש-git בתוך הסשן מקבל:
GIT_SSL_CAINFO: מול מה git בודק את git מנוהל על ידי Anthropic תלוי איפה git רץ:- ה-clone וה-fetch של ה-runner עצמו: רצים בלי המשתנה ובודקים את git מנוהל על ידי Anthropic מול קובץ אישורים לכל סשן שה-runner כותב. ה-runner בונה את הקובץ הזה מחבילת ה-CA של מערכת מארח ה-runner ומהאישורים בקובץ שלכם.
- git בתוך הסשן: מקבל תצורת
http.sslCAInfoשמציינת את הקובץ שלכם במקום המשתנה, ועוד רשומותhttp.<url>.sslCAInfoשבודקות את git מנוהל על ידי Anthropic מול הקובץ לכל סשן. - hooks של
checkoutו-post-session: יורשים את המשתנה בלי שינוי.
GIT_SSL_NO_VERIFY: אילו בדיקות אישור נשארות כבויות תלוי איפה git רץ:- ה-clone וה-fetch של ה-runner עצמו: רצים בלי המשתנה ובודקים את האישור שמוצג להם.
- git בתוך הסשן: מקבל תצורת
http.sslVerify=falseבמקום המשתנה, כך שהבדיקות נשארות כבויות עבור מארחים אחרים. הוא גם מקבל רשומותhttp.<url>.sslVerify=trueשמשאירות את הבדיקות דלוקות עבור git מנוהל על ידי Anthropic. - hooks של
checkoutו-post-session: כאשר לסשן יש מאגר על git מנוהל על ידי Anthropic, מקבלים תצורתhttp.sslVerify=falseבמקום המשתנה. הם גם מקבלים רשומותhttp.<url>.sslVerify=trueשמשאירות את הבדיקות דלוקות עבור git מנוהל על ידי Anthropic.
קובץ האישורים לכל סשן צריך חבילת CA של המערכת ב-/etc/ssl/certs/ca-certificates.crt או ב-/etc/pki/tls/certs/ca-bundle.crt על מארח ה-runner. הוא גם צריך קובץ GIT_SSL_CAINFO שמשתמש ה-runner יכול לקרוא, שמחזיק בלוקי PEM של CERTIFICATE, ושגודלו לכל היותר 1 MiB. כאשר ה-runner לא יכול לבנות את הקובץ לכל סשן, הוא רושם שורת [runner:warn] שמכילה did not build the certificate file ואת הסיבה. git משתמש אז בקובץ שלכם כפי שהוא עבור git מנוהל על ידי Anthropic. תקנו את מה שהשורה מציינת.
עבור כל סשן שמשתמש ב-git מנוהל על ידי Anthropic, ה-runner גם רושם שורת [runner:warn] שמתחילה ב-governed git: GIT_SSL_CAINFO is set או ב-governed git: GIT_SSL_NO_VERIFY is set. השורה אומרת מה ה-runner עשה עם המשתנה הזה עבור ה-git שלו, עבור git בתוך הסשן, ועבור hooks מחזור החיים שלכם. היא מסתיימת בציון אם צריך לשנות משהו.
#הוספת מאגרי github.com לסשנים של ערוץ Claude Tag
אם Claude לא יכול להוסיף מאגר github.com לסשן של ערוץ Claude Tag, ודאו שלערוץ יש גישה אליו, גם כאשר ה-runner שלכם יכול לשכפל אותו. הגישה הזו מגיעה מהגדרות ב-GitHub ובארגון Claude שלכם, ולא מאישורי ה-git של ה-runner.
1. התקנת Claude GitHub App
התקינו את Claude GitHub App על ארגון או חשבון GitHub שבבעלותו המאגר, וכללו את המאגר הזה בהתקנה.
2. קישור ארגון או חשבון GitHub
ב-Organization settings > Git providers, קשרו את הארגון או החשבון הזה לארגון Claude שלכם. Owner של ארגון Claude שלכם, שהוא גם הבעלים של ארגון GitHub, יכול לקשר אותו. ראו קישור ארגון GitHub שלכם.
3. הענקת המאגר
ב-הגדרות הניהול של Claude Tag, העניקו את המאגר לערוץ או להיקף רחב יותר שכולל את הערוץ: סביבת העבודה שלו ב-Slack, או כל Slack. מישהו עם תפקיד Owner או עם הרשאת Claude Tag Admin יכול להעניק אותו. מנהל ערוץ יכול גם להוסיף אותו לערוץ שלו, אם חשבון GitHub שלו הוא admin של המאגר. ראו הענקת גישה למאגר.
לאחר מכן התחילו שרשור חדש בערוץ, וציינו את שם המאגר בהודעה הראשונה שלכם.
אם Claude עדיין לא יכול להוסיף את המאגר, ראו אם Claude לא יכול להגיע למאגר כדי לדעת היכן לבדוק את קישור GitHub ואת הענקת המאגר.
#כתיבה מחדש של כתובות git לרשתות פרטיות
כתובות URL של מאגרים מגיעות ממישור הבקרה כ-HTTPS, עם שם המארח של מארח ה-git שלכם. עבור GitHub Enterprise, זה שם המארח שהגדרתם עבור שילוב GitHub Enterprise ב-claude.ai. שני דגלים שניתן לחזור עליהם כותבים מחדש את הכתובות האלה לפני clone:
--git-host-rewrite <from>=<to>: עבור split-horizon DNS, שבו Anthropic מגיעה למארח ה-git שלכם דרך שם מארח חיצוני, אבל runners חייבים להשתמש בשם מארח פנימי--git-ssh-rewrite <host>: עבור מארחי git שמקבלים רק SSH, או מארח runner שיש לו רק מפתח SSH. לדוגמה, כדי לשכפל מאגרי github.com דרך SSH, הוסיפו--git-ssh-rewrite github.comלפקודת ה-runner. ה-runner כותב אז מחדש אתhttps://github.com/owner/repoל-[email protected]:owner/repo. SSH חייב להתחבר בלי בקשת קלט.
כתיבת המארח מחדש רצה קודם, ולכן ציינו את שם המארח הפנימי ב---git-ssh-rewrite אם אתם צריכים את שניהם. לשליטה מלאה ב-checkout, השתמשו ב-lifecycle hook מסוג checkout.
#התחלת סשן על מאגר מחוץ ל-GitHub
כדי להריץ סשן על מאגר ממארח git שאינו GitHub, התחילו את הסשן מהטרמינל שלכם, ב-checkout של המאגר הזה. קודם בדקו את אישורי ה-git של ה-runner, את הרשת ואת פרוקסי ה-git, את כתובת המאגר, ואת הענף שלכם:
- אישורי Git ורשת: ספקו אישורים שעובדים בלי בקשת קלט עבור
https://<host>/<owner>/<name>, הכתובת שה-runner משכפל, ואפשרו ל-runner להגיע למארח ה-git שלכם, כפי שדרישות רשת מפרטות. עבור מארח git שמקבל רק SSH, ראו--git-ssh-rewrite. - פרוקסי Git: אם ה-runners של הסביבה משתמשים בפרוקסי ה-git של Anthropic, כבו אותו.
- כתובת המאגר: בדקו את ה-remote בשם
originשל ה-checkout. המארח שלו חייב להיות שם מתחם כגוןgit.example.com, והנתיב שלו חייב להיות בצורה<owner>/<name>. נתיב מקונן כגוןgroup/subgroup/nameלא עובר. - ענף: דחפו את הענף שעליו אתם נמצאים למארח ה-git שלכם, כי ה-runner משכפל את הענף הזה.
ב-checkout, כשאתם מחוברים ל-claude.ai כפי שאימות מ-CI מתאר, הריצו את claude -p עם --environment ועם מזהה ccpool_... של הסביבה שלכם:
claude -p "Fix the login bug" --environment ccpool_abc123 --output-format jsonכאשר הסשן נוצר, הפקודה מדפיסה שורת JSON אחת שמתחילה ב-{"ok":true. אם ה-remote בשם origin לא מתאים, הפקודה נכשלת עם requires a git source, but no GitHub remote was detected.
סקרו ומזגו במארח ה-git שלכם את הענפים שסשנים דוחפים.
#בניית תמונת ה-runner
Anthropic לא מפרסמת תמונת runner מוכנה מראש. בנו תמונה משלכם סביב הבינארי claude, והוסיפו בשכבות את ה-toolchain שהמאגרים שלכם צריכים: סביבות ריצה של שפות, מהדרים, מנהלי חבילות, ו-sidecars של MCP.
המתכונים למטה משתמשים ב---capacity 4, כך שמכולה אחת משרתת עד ארבעה סשנים במקביל מאותו בעלים נעול. זה לא מספק את בידוד המכולה לכל סשן שבסעיף ההקשחה: לפני חיבור סביבה למערכות ייצור, או הריצו את המתכונים ב---capacity 1 עם מכולה אחת לכל סשן, או השתמשו ב-runners לפי דרישה, שגם שומרים את סוד הסביבה מחוץ למארחים שמריצים סשנים. אם אתם מוסיפים את פרוקסי ה-git של Anthropic לאחד מהמתכונים האלה, שנו גם את --capacity ל-1.
Dockerfile זה הוא נקודת התחלה מינימלית. הוא מתקין את python3 כי ה-hooks שמישור הבקרה של Anthropic מספק לסשנים של Claude Tag, כולל זה שגורם ל-Claude לפרסם את התשובה שלו ב-Slack, מריצים python3. השאירו את python3 על PATH בכל תמונה שמשרתת סשנים של Claude Tag:
FROM debian:bookworm-slim
ARG CLAUDE_CODE_VERSION
RUN apt-get update && apt-get install -y --no-install-recommends git curl ca-certificates openssh-client jq python3 \
&& rm -rf /var/lib/apt/lists/*
RUN curl -fsSL "https://downloads.claude.ai/claude-code-releases/${CLAUDE_CODE_VERSION:?set with --build-arg CLAUDE_CODE_VERSION}/linux-x64/claude" \
-o /usr/local/bin/claude && chmod +x /usr/local/bin/claude
RUN git config --system user.name "Claude" \
&& git config --system user.email "[email protected]" \
&& git config --system --add safe.directory '*'
ENTRYPOINT ["claude"]החליפו את linux-x64 ב-linux-arm64 אם הצמתים שלכם הם ARM, או ב-linux-x64-musl או linux-arm64-musl בתמונה מבוססת musl כגון Alpine. ראו הגדרת Alpine Linux לחבילות הנוספות שתמונות musl צריכות. ה-URL הוא מיקום ההפצה הסטנדרטי של Claude Code, כך שאתם יכולים לאמת את הבינארי שהורד מול המניפסט החתום של ההפצה, כפי שמתואר ב-שלמות בינארית וחתימת קוד. ה-runner דורש את Claude Code בגרסה 2.1.224 או מאוחרת יותר, או 2.1.229 או מאוחרת יותר כדי לשרת סשנים של Claude Tag. בנו את התמונה, ואז דחפו אותה ל-registry שלכם והפנו אליה במתכונים למטה:
docker build \
--build-arg CLAUDE_CODE_VERSION="$(curl -fsSL https://downloads.claude.ai/claude-code-releases/stable)" \
-t <your-registry>/claude-runner:latest .ה-command substitution מחפש את מספר הפצת stable הנוכחי ומעביר אותו כארגומנט הבנייה, כך שהרצת אותה פקודה אחרי הפצת stable חדשה בונה מחדש את שכבת ההורדה עם הבינארי החדש יותר. כדי לקבע הפצה מסוימת לבניות שניתן לשחזר, העבירו את מספר הגרסה ישירות כ-CLAUDE_CODE_VERSION. החליפו את stable ב-latest ב-URL של החיפוש כאשר אתם צריכים הפצה חדשה יותר מערוץ ה-stable, כגון הפצה שמודל שהושק לאחרונה דורש.
#קביעת גודל CPU וזיכרון לסשנים
גודל את המכולה או המארח של runner לפי הסשנים שהוא מריץ, במקום לפי תהליך ה-runner. ה-runner עצמו מבצע polling לעבודה, מכין את ה-checkout של כל סשן, מריץ את lifecycle hooks שלך, ומפעיל ומפקח על תהליכי הסשן. העומס מגיע מהסשנים: כל אחד הוא תהליך Claude Code בתוספת כל מה שהוא מפעיל, כמו בניות, מערכי בדיקות, התקנות חבילות ושרתי MCP.
לסשן אחד, התחל מהערכים הבאים, כפי שהם מנוסחים כ-requests ו-limits של Kubernetes או כמקבילה בפלטפורמה שלך, והתייחס אליהם כנקודת התחלה במקום כדרישה:
- זיכרון: request ו-limit של 4 GiB כל אחד, שעומדים במינימום של 4 GB בדרישות המערכת של Claude Code. שמור על השניים שווים כדי שהמתזמן יביא בחשבון את מלוא הזיכרון של המכולה. כשהמכולה מגיעה למגבלת הזיכרון שלה, הליבה הורגת תהליכים בתוכה, וזה יכול לסיים סשן באמצע משימה.
- CPU: request של 2 CPUs ו-limit של 4 CPUs, כדי שסשן יוכל לעלות בפרץ מעל ה-request במהלך בניות. הליבה מבצעת throttling למכולה במגבלת ה-CPU שלה במקום להרוג תהליכים בתוכה, כך שסשנים במגבלה רצים לאט יותר אבל ממשיכים לרוץ.
במפרט מכולה של Kubernetes, הגדר את ערכי ההתחלה האלה עם בלוק ה-resources הבא:
resources:
requests:
cpu: "2"
memory: 4Gi
limits:
cpu: "4"
memory: 4Giבניות ובדיקות הן בדרך כלל החלק הגדול ביותר והמשתנה ביותר בעומס של סשן, אז הרץ בנייה מייצגת של המאגר שלך, מדוד את שיא ה-CPU והזיכרון שלה, והעלה כל ערך התחלה שלא משאיר מקום לתהליך Claude Code מעל השיא הזה.
ה-runner משתמש ב---capacity כדי לקבוע תקרה למספר הסשנים שהוא מריץ בו זמנית. הוא לא מחלק CPU או זיכרון ביניהם, כך שהסשנים על runner חולקים את ה-CPU ואת הזיכרון של המכולה. כדי ששימוש בזיכרון של סשן אחד לא יסיים את האחרים, הרץ סשן אחד לכל runner עם מגבלת זיכרון משלו.
סשן אחד לכל runner: תן לכל מכולה את הערכים של סשן אחד. השתמש בגודל הזה ב-
--capacity 1, שסעיף ההקשחה ממליץ עליו, ולrunners לפי דרישה, שם אתה מגדיר את הערכים על עומס העבודה שhook שלspawn-runnerשולח, כמו תבנית pod של Kubernetes Job.כמה סשנים לכל runner: ב-
--capacityמעל אחד, הכפל את הערכים של סשן אחד בקיבולת, כי עד אותו מספר סשנים יכולים לרוץ במכולה באותו זמן. המתכונים של Kubernetes וDocker Compose מריצים--capacity 4בלי מגבלות CPU או זיכרון, אז הוסף מגבלות בגודל שמתאים לקיבולת שאתה מריץ.כמה מכולות runner על מארח אחד: גודל את המארח לפי סכום הקיבולות של ה-runners, כי כל runner סופר רק את הסשנים שלו מול
--capacity:- זיכרון: הכפל את הזיכרון של סשן אחד בסכום, ואז הוסף את מה שתהליכי המארח עצמו משתמשים
- CPU: הכפל את ה-CPU request של סשן אחד בסכום. כדי לאפשר לכל סשן לעלות בפרץ בבת אחת, הכפל את מגבלת ה-CPU שלו במקום זאת.
תן לכל runner מכולה משלו. השתמש באותו נתיב
--base-dirבכל אחד, על אחסון שאף מכולה של runner אחר לא מעגנת, כדי שלכל runner תהיה ספריית עבודה משלו.
#תן לכל סשן מגבלת זיכרון משלו
כדי לעצור סשן אחד מלסיים את האחרים כשהזיכרון נגמר, הרץ כל runner ב---capacity 1 תחת מגבלת זיכרון משלו, שמוגדרת לזיכרון של סשן אחד:
- במכולה: הרץ runner אחד לכל מכולה, והגדר את מגבלת הזיכרון של המכולה, כמו
limits.memoryב-Kubernetes,--memoryב-Docker, אוmem_limitבקובץ Compose. שמור את סכום המגבלות של המכולות מתחת לזיכרון המארח, כי כשהמארח עצמו נגמר לו הזיכרון, הליבה יכולה להרוג תהליכים בכל מקום על המארח. - תחת systemd בלי מכולה: הגדר
MemoryMaxעל ה-unit של ה-runner, והרץ runner אחד לכל מארח, כי runners שמשתמשים באותו--base-dirעל מערכת קבצים אחת חולקים את המאגרים ששוכפלו שם. לדוגמה, ב---capacity 1בלי hook שלcheckout, הסשנים שלהם על מאגר אחד יכולים לעבוד באותו checkout בבת אחת. כדי להריץ כמה runners על מארח אחד, תן לכל אחד מכולה.
כשמכולה או unit מגיעים למגבלת הזיכרון שלהם, הליבה הורגת תהליכים רק בתוכם. אם תגדיר --capacity מעל אחד, הסשנים חולקים מגבלה אחת. כשהם מגיעים אליה, הליבה לא בוחרת לפי סשן אילו תהליכים להרוג, כך ששימוש בזיכרון של סשן אחד יכול לסיים את האחרים.
ב-Kubernetes, אי אפשר להפריד את הסשנים עם אף אחד מהערכים של ההגדרה singleProcessOOMKill של kubelet. אי אפשר גם להגביל את סך הזיכרון של סשן עם ulimit של זיכרון בסקריפט ה-wrapper שלך. הליבה מחילה ערך כמו ulimit -v על כל תהליך בנפרד.
#השאר מרווח מתחת למגבלות תהליכים ו-threads
כדי לגודל מגבלה של תהליכים ו-threads, מדוד מה סשן אחד צריך והכפל במספר הסשנים שחולקים את המגבלה. כשהסשנים האלה מגיעים למגבלה, הליבה מסרבת לתהליכים ו-threads חדשים, כך שפקודות, בניות ובדיקות בכל אחד מהם יכולות להיכשל. הקריאה שנדחתה מחזירה EAGAIN, שההודעה הסטנדרטית שלה היא Resource temporarily unavailable.
נניח שסשן אחד מגיע לשיא של 300 תהליכים ו-threads, וארבעה סשנים חולקים את המגבלה. יחד הם צריכים 1,200, ועם מגבלה של 2,400, אתה משאיר מרווח לבניות שצריכות פי שניים.
#איפה להגדיר כל מגבלה
כל מגבלה בטבלה הזו סופרת threads וגם תהליכים, ו-pids.max הוא הקובץ שמחזיק את המגבלה של cgroup:
| מגבלה | איפה מגדירים אותה | מה חולק אותה |
|---|---|---|
pids.max ב-Kubernetes | podPidsLimit בתצורת kubelet של הצמתים שמריצים runners. אי אפשר להגדיר אותו במפרט Pod. | המכולות של Pod אחד |
pids.max ב-Docker | --pids-limit, או pids_limit בקובץ Compose | מכולה אחת |
pids.max תחת systemd | TasksMax על ה-unit של ה-runner | unit אחד |
ulimit -u | המעטפת או השירות שמפעילים את ה-runner | כל מה על המארח שרץ תחת מזהה המשתמש של ה-runner. לא נאכף עבור root. |
כשאתה לא מגדיר TasksMax, systemd מחיל DefaultTasksMax, שברירת המחדל שלו היא 15% מהנמוך מבין kernel.pid_max, kernel.threads-max, ו-pids.max של ה-cgroup של root.
#מדוד את השיא של סשן אחד
כדי לקבל את המספרים ל-runners שלך, מדוד סשן אחד על המארח שלו, שב-Kubernetes הוא הצומת:
1. הפעל runner אחד לבד
הפעל runner אחד ב---capacity 1 בלי שום דבר אחר במכולה שלו או ב-unit של systemd, לבד על המארח שלו ובסביבה שלו:
- המארח שלו: השתמש במארח שלא מריץ runner אחר. אחר כך תוכל לזהות את התהליך שלו בשלב הבא.
- הסביבה שלו: הפוך אותו ל-runner היחיד שם, לדוגמה בסביבה שאתה יוצר לבדיקה. סשנים מנותבים לסביבה ולא ל-runner.
אל תתחיל סשן עדיין, כי אתה צריך את הספרייה מהשלב הבא לפני שהבנייה מתחילה.
2. מצא את ספריית ה-cgroup של ה-runner
על המארח, לא בתוך המכולה, מצא את מזהה התהליך של ה-runner, לדוגמה עם ps. קרא את /proc/<pid>/cgroup עבור המזהה הזה:
- שורה מכילה
:pids:: המארח משתמש ב-cgroup v1, והספרייה היא/sys/fs/cgroup/pidsואחריה הטקסט שאחרי:pids: - השורה היחידה מתחילה ב-
0::: המארח משתמש ב-cgroup v2, והספרייה היא/sys/fs/cgroupואחריה הטקסט שאחרי0::
3. הרץ בנייה מייצגת ורשום את השיא
הרץ סשן אחד דרך בנייה מייצגת, וקח את השיא מספריית ה-cgroup של ה-runner:
- לספרייה יש
pids.peak: קרא את הקובץ אחרי שהבנייה מסתיימת - לספרייה אין
pids.peak: קרא אתpids.currentפעם בשנייה בזמן שהבנייה רצה, ושמור את הערך הגבוה ביותר. שיא בין שתי קריאות לא נראה, אז התייחס לערך הזה כהערכה נמוכה.
4. הכפל בסשנים שחולקים את המגבלה
- Pod, מכולה או unit של runner אחד: הכפל את השיא ב-
--capacityשל ה-runner - מגבלה שכמה runners חולקים: הכפל את השיא בסכום הקיבולות שלהם
אחר כך הוסף את השיא של כל דבר אחר שחולק את המגבלה, כמו מכולה אחרת ב-Pod.
5. הגדר את המגבלה מעל התוצאה
עם מגבלה גבוהה יותר, אתה משאיר מרווח לבניות כבדות יותר, ועם מגבלה נמוכה יותר, אתה עוצר fork bomb מוקדם יותר.
#בדוק מגבלה של pids.max
- בדוק אם
pids.maxהושג: חפש ביומן הליבה של המארח, לדוגמה עםdmesg, שורה שמכילהcgroup: fork rejected by pids controller inונתיב של cgroup. הליבה רושמת את זה רק בפעם הראשונה שהיא מסרבת לתהליך או thread ב-cgroup הזה, אז חפש בכל היומן. - ראה את המגבלה בתוקף: קרא את
pids.maxבספריית ה-cgroup של ה-runner ובכל ספרייה מעליה. הערך הנמוך ביותר הוא המגבלה בתוקף, ו-maxאומר שאין מגבלה ברמה הזו.
#Kubernetes
ה-runner מגיש GET /healthz על פורט 8080 כברירת מחדל, שניתן להגדרה עם --health-port, כך שבדיקות Kubernetes עובדות בלי הגדרה נוספת. נקודת הקצה מחזירה 200 בכל פעם שהתהליך חי, כך שהבדיקות למטה מזהות תהליך מת ולא תהליך תקוע. כדי לתפוס runner שהפסיק לבצע polling, התרע על הסדרה last_poll_age_seconds מ-/metrics. ה-Deployment למטה מעגן את סוד הסביבה מ-Secret של Kubernetes, מפנה את בדיקות ה-liveness וה-readiness אל /healthz, ומגדיר termination grace period של 90 שניות. ראה תזמון כיבוי כדי להבין למה תקופת החסד חשובה.
המניפסט לא מגדיר resources של CPU או זיכרון על מכולת ה-runner. הוסף בלוק בגודל שמתאים לקיבולת שאתה מריץ, כפי שקביעת גודל CPU וזיכרון לסשנים מתאר.
apiVersion: apps/v1
kind: Deployment
metadata:
name: claude-runner
namespace: claude-runners
spec:
replicas: 3
selector:
matchLabels:
app: claude-runner
template:
metadata:
labels:
app: claude-runner
app.kubernetes.io/part-of: claude-code-self-hosted-runner
spec:
terminationGracePeriodSeconds: 90
containers:
- name: runner
image: <your-registry>/claude-runner:latest
args:
- self-hosted-runner
- --environment-secret-file
- /etc/claude/environment-secret
- --capacity
- "4"
- --base-dir
- /workspace
volumeMounts:
- name: environment-secret
mountPath: /etc/claude
readOnly: true
ports:
- name: health
containerPort: 8080
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 30
periodSeconds: 30
volumes:
- name: environment-secret
secret:
secretName: claude-runner-environment-secretה-Deployment למעלה חי ב-namespace בשם claude-runners. צור את ה-namespace קודם:
kubectl create namespace claude-runnersצור את ה-Secret התומך מקובץ מקומי שמחזיק את הערך שהעתקת בשלב Copy environment key בממשק הניהול, כדי שהסוד לעולם לא יופיע בהיסטוריית המעטפת שלך. הרץ (umask 077 && cat > ./environment-secret), הדבק את הסוד, לחץ Enter, ואז Ctrl-D. אחר כך צור את ה-Secret ומחק את הקובץ:
kubectl create secret generic claude-runner-environment-secret -n claude-runners --from-file=environment-secret=./environment-secret#Docker Compose
שירות ה-Compose למטה מפעיל מחדש את ה-runner בכל פעם שהוא יוצא, וזה מכסה גם קריסות וגם יציאה רגילה אחרי draining. מדיניות restart של Docker מפעילה מחדש את אותה מכולה עם שכבת הכתיבה שלה שלמה, כך שה-runner חוזר על מערכת קבצים בשימוש חוזר במקום על מערכת הקבצים החדשה שעמדת ההקשחה ממליצה עליה. השתמש במתכון הזה להערכה, ולייצור או צור מחדש את המכולה בכל הרצה או השתמש ב-orchestrator שעושה זאת.
Docker מחכה יותר לפני כל הפעלה מחדש של מכולה שממשיכה לצאת, עד לתקרה, כך ש-runner שלא מצליח להתחיל לא ממשיך להתחיל מחדש בלולאה צפופה תחת המתכון הזה. כשה-runner יוצא מתאר מה לבדוק כשזה קורה.
services:
claude-runner:
image: <your-registry>/claude-runner:latest
command:
- self-hosted-runner
- --environment-secret-file
- /run/secrets/environment-secret
- --capacity
- "4"
- --base-dir
- /workspace
secrets:
- environment-secret
restart: always
stop_grace_period: 90s
secrets:
environment-secret:
file: ./environment-secret#תזמון כיבוי
[!NOTE]
התזמונים האלה חלים על runners. לתהליך
claude self-hosted-runner orchestratorיש תזמון משלו: ראו Size--shutdown-drain-secand the orchestrator's stop timeout.
בהפעלה, כל runner רושם כמה זמן הוא יכול להזדקק אחרי SIGTERM כדי לכבות את הסשנים שלו בצורה נקייה, למשל This runner needs up to 80s בהגדרות ברירת המחדל. ה-stop timeout הוא כמה זמן מפקח התהליך מחכה אחרי SIGTERM לפני שהוא הורג את ה-runner. הגדירו אותו לפחות לזמן שנרשם:
- Kubernetes:
terminationGracePeriodSeconds. ברירת המחדל של Kubernetes היא 30 שניות, ולכן בלי ההגדרה הזו הוא יכול לעצור את ה-pod לפני שה-runner מסיים. - Docker Compose:
stop_grace_period
ב-SIGTERM, ה-runner מפסיק לקבל סשנים חדשים. אחר כך הוא מתחיל כיבוי מסודר, שנקרא drain, אלא אם הגדרתם --defer-shutdown-max-min כדי לדחות אותו. ל-drain יש שלושה שלבים:
- ה-runner מחכה עד
--drain-wait-secשניות,0כברירת מחדל, שתורות שעדיין רצות יסתיימו. - הוא מסיים את עץ התהליכים של כל סשן, כולל כל פקודה ש-Claude עדיין הריץ, אבל לא תהליך שעדיין רץ אחרי שפקודת ה-shell שלו יצאה.
- הוא מריץ את ה-lifecycle hook של
post-session.
ה-runner ממשיך לבצע polling מול Anthropic לאורך כל ה-drain. זה משאיר את הסשנים שלו משויכים אליו, כך ש-runner אחר לא יאסוף אחד מהם בזמן שה-hook של post-session עדיין שומר עבודה שלא בוצעה לה commit.
הזמן שנרשם הוא הסכום של הערכים האלה:
--drain-wait-secלשלב 1, 0 שניות כברירת מחדל--session-stop-grace-secלשלב 2, 5 שניות כברירת מחדל--post-session-hook-timeout-secלשלב 3, 60 שניות כברירת מחדל- 15 שניות קבועות של מרווח
- עוד 30 שניות כש-
--push-outcome-on-releaseמוגדר
בהגדרות ברירת המחדל זה מסתכם ב-0 + 5 + 60 + 15 = 80 שניות. --capacity גבוה יותר לא מוסיף לזה, כי ה-runner מבצע drain לכל הסשנים שלו באותו זמן.
הקצו יותר זמן אם הגדרתם אחד מהדגלים האלה:
- עם
--retire-at: השאירו מספיק זמן בין זמן ה-retire לבין הזמן שהמארח נעצר, כדי שתורות טיפוסיות יסתיימו, ועוד ההמתנה למשימות רקע ש-Runner lifecycle מתאר, ועוד הזמן שנרשם. חשבו את זמן ה-retire בכל הפעלה, למשלdate +%sועוד משך החיים המיועד של ה-runner. - עם
--defer-shutdown-max-min: ה-stop timeout חייב לכסות גם את הדקות שהגדרתם וגם המתנה נוספת לפני שה-drain מתחיל, 75 שניות בהגדרות ברירת המחדל. Defer the drain past the first signal מסביר את שניהם. ה-runner רושם גם את הזמן הארוך יותר הזה בהפעלה.
#לאפשר לתורות רצות להסתיים לפני הפעלה מחדש
כדי לאפשר לתורות רצות להסתיים לפני rolling restart, הגדירו --drain-wait-sec, והעלו את ה-stop timeout לפחות להמתנה הזו ועוד 80 שניות. עם המתנת ברירת המחדל של 0, הפעלה מחדש קוטעת כל תור שעדיין רץ, והסשן מתחדש על runner אחר בלי העבודה שלא נדחפה.
--drain-wait-sec: בערך משך התורות ומשימות הרקע שאתם רוצים לאפשר להן להסתיים. זה מקסימום: ההמתנה מסתיימת מוקדם אם הן מסתיימות קודם.- ה-stop timeout שלכם:
terminationGracePeriodSecondsב-Kubernetes,stop_grace_periodב-Docker Compose. לדוגמה, עם--drain-wait-sec 120, החליפו את ה-90במתכון Kubernetes או Docker Compose ב-200. - אם שיניתם הגדרות כיבוי אחרות: 80 השניות הן להגדרות ברירת המחדל. הפעילו runner אחד עם הדגלים שלכם, והשתמשו לפחות במספר בשורת ההפעלה
This runner needs up to, שכולל את ההמתנה. כש---defer-shutdown-max-minמוגדר, השתמשו במספר הגדול יותר בשורת ההפעלה הבאה, אחריSize the stop timeout to at least. Shutdown timing מפרט מה מתווסף למספרים האלה.
[!WARNING]
אם ה-stop timeout נגמר במהלך ההמתנה, המארח הורג את ה-runner בזמן שעבודה עדיין רצה. הסשנים שלו לא מקבלים hook של
post-session. ה-runner גם לא מבצע deregister, ולכן מישור הבקרה יכול לקחת כמה דקות ולפעמים יותר כדי להכניס אותם מחדש לתור.
#לפרסם הודעת סטטוס לפני הפעלה מחדש
לפני הפעלה מחדש שתקטע סשנים, בקשו מ-Owner לפרסם הודעת סטטוס על הסביבה מדף הניהול Cloud environments. ב-claude.ai/code, חברים רואים את ההודעה בסשנים שלהם בסביבה הזו. בקשו מה-Owner להגדיר תפוגה עם הפרסום, או להסיר את ההודעה ברגע שה-runners חזרו.
#לדחות את ה-drain מעבר לאות הראשון
הגדירו --defer-shutdown-max-min <n> אם אתם רוצים ש-runner שאתם מפעילים מחדש ימשיך לשרת את הסשנים שהוא מחזיק עד n דקות, במקום לבצע להם drain באות הראשון. ב-SIGTERM או SIGINT הראשון, ה-runner מפסיק לקבל עבודה חדשה וממשיך לשרת את הסשנים שהוא מחזיק. הוא ממשיך לבצע polling כדי שמישור הבקרה לא יכניס מחדש לתור את הסשנים האלה. דורש Claude Code גרסה v2.1.238 או חדשה יותר.
#מה קורה לסשנים שה-runner מחזיק אחרי האות הראשון
בשני השלבים הראשונים שאחרי האות, ה-runner משחרר סשנים, וסשן ששוחרר מתחדש על runner חדש כשהמשתמש שולח את ההודעה הבאה שלו. בספירה מהאות הראשון, ה-runner עובר בשלושה שלבים:
- ב-
nהדקות הראשונות: ה-runner משרת את הסשנים שלו כרגיל וממשיך לאכוף את--startup-timeout-minואת--kill-session-after-min. אם הגדרתם גם--release-idle-session-min, ה-runner משחרר כל סשן שהמשתמש שלו היה לא פעיל במשך הזמן הזה, אלא אם החזקתם את הסשן הזה. בלי הדגל, סשנים לא פעילים נשארים על ה-runner. - כש-
nהדקות נגמרות: ה-runner משחרר כל סשן שהוא עדיין מחזיק, לא פעיל או לא. ה-runner מחכה שתור של סשן באמצע תור יסתיים, ועד 60 שניות נוספות למשימות הרקע של תור, לפני שהוא משחרר את הסשן הזה. - כש-post-release grace נגמר: ה-runner מבצע drain לכל סשן שהוא עדיין מחזיק, ומישור הבקרה מכניס מחדש לתור כל סשן שעבר drain ל-runner אחר מיד. ה-post-release grace מתחיל כש-
nהדקות נגמרות והוא 75 שניות בברירות המחדל. אם הגדרתם--drain-wait-secמעל 60 שניות, ה-post-release grace הוא--drain-wait-secועוד 15 שניות במקום זאת.
בכל שלב, ה-runner יוצא עם קוד 0 ברגע שהוא לא מחזיק סשנים. אות שני מקצר את השלבים: ה-runner מבצע drain מיד, כמו שהוא עושה באות הראשון בלי --defer-shutdown-max-min. ברגע ש-drain מתבצע, האות הבא מאלץ יציאה של ה-runner. זה נכון בין אם אות שני או סיום ה-post-release grace התחילו את ה-drain.
#לקבוע את גודל ה-stop timeout
תנו ל-stop timeout של המארח לפחות את הסכום של שלושה חלקים: n הדקות שהגדרתם, ה-post-release grace, וה-drain ש-Shutdown timing מתאר. בהגדרות ברירת המחדל ה-post-release grace הוא 75 שניות וה-drain לוקח עד 80 שניות, אז הקצו n דקות ועוד 155 שניות. ה-runner מדפיס את הסכום הזה בהפעלה בכל פעם ש---defer-shutdown-max-min מוגדר.
אם ה-stop timeout נגמר לפני שה-runner מסיים, הסשנים שהוא עדיין מחזיק לא מקבלים hook של post-session. ה-runner לא מבצע deregister, ולכן מישור הבקרה יכול לקחת כמה דקות ולפעמים יותר כדי להכניס מחדש לתור את הסשנים האלה. אם אי אפשר לתת ל-stop timeout את הסכום הזה, השאירו את --defer-shutdown-max-min לא מוגדר כדי שה-runner יבצע drain באות הראשון במקום זאת.
#מה מגיע ל-hook של post-session שרץ
ה-hook של post-session ותהליך הילד של סשן Claude רצים כל אחד בקבוצת תהליכי POSIX משלו, נפרדת מזו של ה-runner, ולכן מנגנוני עצירה מגיעים אליהם בצורה שונה:
SIGTERMבזמן שה-runner כבר מבצע drain: מאלץ יציאה מיידית של ה-runner, ודילוג על מה שנשאר מה-drain. בלי--defer-shutdown-max-min, זה ה-SIGTERMהשני שה-runner מקבל. שום דבר לא מסמן hook שלpost-sessionבאמצע ריצה, ולכן על מארח חשוף שבו תהליך init מאמץ יתומים, הוא מסיים בכוחות עצמו, אבל בלי פיקוח: תקציב ה-timeout שלו כבר לא חל, וכתיבה לצינור הלוג שנסגר יכולה להרוג אותו עםSIGPIPE, ולכן hook שצריך לשרוד יציאה מאולצת שם צריך להפנות את הפלט שלו לקובץ. במתכוני הקונטיינר בדף הזה ה-runner הוא PID 1 של הקונטיינר והיציאה שלו מסיימת את הקונטיינר, ותחת ברירת המחדלKillMode=control-groupשל systemd ההרג ברמת ה-cgroup מגיע גם ל-hook, כפי שהרשומה Cgroup-wide kills מתארת. בשניהם, התייחסו ליציאה מאולצת כקטלנית ל-hook והסתמכו על תקופת החסד במקום זאת.- אותות ברמת קבוצת התהליכים, כמו
kill -- -<pid>בסקריפט עטיפה, בקרת jobs של shell, או watchdog ברמת הקבוצה: מגיעים ל-runner ולתהליך משנה של hook שלcheckoutבאמצע ריצה, שנשאר מחובר לקבוצה בכוונה, אבל לא ל-hook שלpost-sessionבאמצע ריצה או לתהליך הילד של הסשן. - הרגות ברמת cgroup, כמו ברירת המחדל
KillMode=control-groupשל systemd או ה-SIGKILLש-Kubernetes שולח לכל הקונטיינר כש-terminationGracePeriodSecondsפג: מגיעים לכל דבר, כולל ה-hook. בידוד קבוצת תהליכים לא מגן מפני אלה, ולכן תקופת החסד חייבת לכסות את כל ה-drain. - ה-timeout של ה-hook עצמו: כש-hook חורג מ-
--post-session-hook-timeout-sec, ה-runner שולחSIGTERMלכל קבוצת התהליכים של ה-hook, ואזSIGKILLשתי שניות אחר כך, כך ש-worker שה-hook עשה לו fork, כמו tar, rsync או git, מסתיים עם ה-shell העוטף במקום לשרוד כיתום. הפיקוח של ה-runner מסתיים ברגע שה-stdio של ה-hook נסגר: worker שהפנה את הפלט שלו לקובץ וממשיך מעבר לשלב ה-SIGTERMנמצא מעבר להישג ידו של ה-runner.
כשה-drain מתחיל, ושוב ביציאה מאולצת, ה-runner רושם כמה hooks של post-session עדיין רצים, כדי שאפשר יהיה להבחין בין drain שקט לבין אחד שנמצא באמצע snapshot.
#שמרו על ספריית הבסיס ועל הקיבולת זהות בכל ה-runners
אם runner מת באמצע סשן, השרת מכניס מחדש לתור את הסשן ו-runner אחר בסביבה אוסף אותו. ה-runner הזה גוזר את נתיב ה-checkout מ---base-dir ומ---capacity שלו: --capacity 1 מבצע checkout ישירות תחת --base-dir, ו---capacity מעל 1 משתמש ב-worktrees לכל סשן במקום זאת. כש-runners באותה סביבה משתמשים בערכים שונים לאחד מהדגלים, ספריית העבודה של הסשן שהתחדש משתנה, ונתיבים מוחלטים שהסוכן רשם קודם, בעריכות, בקריאות כלים, או בהערות שלו, מצביעים על מיקום שכבר לא קיים.
השתמשו באותם --base-dir ו---capacity בכל runner בסביבה, ואל תשתמשו בערך לכל מארח כמו מזהה instance או hostname. העבירו את שני הדגלים במפורש, כמו שמתכוני Kubernetes ו-Docker Compose עושים, כך שאף ערך לא תלוי בברירת מחדל.
ספריית הבסיס ברירת המחדל שלה היא /workspace, עם החריג ש-שורת הייחוס של --base-dir מתעדת. ה-runner צריך גישת כתיבה אליה. בהפעלה, לפני הרישום, ה-runner יוצר את הספרייה ומאשר שהוא יכול לכתוב אליה, ויוצא עם cannot create or write to base directory כשהוא לא יכול. runner שהופעל כ-root יוצר את /workspace ברירת המחדל בעצמו. עבור runner שאינו root, צרו את הספרייה ותנו למשתמש של ה-runner בעלות לפני הפעלת ה-runner, או הצביעו את --base-dir לספרייה שהמשתמש הזה כבר מחזיק.
#שימוש חוזר ב-checkout שחומם מראש
עבור מאגרים גדולים, ה-clone יכול לשלוט בהפעלת הסשן. כדי לדלג על clone קר, ספקו clone בעצמכם בנתיב שבו ה-runner שומר את שלו. בלי hook של checkout, ה-runner שומר clone קנוני אחד לכל מאגר ב-<base-dir>/<repo-owner>/<repo> ומשתמש בו מחדש בין סשנים:
- ב-
--capacity 1: ה-runner מושך את ה-ref המבוקש, מנתק אתHEAD, ומבצע reset hard אליו, וזה כמעט מיידי כשמעט השתנה. - ב-
--capacityמעל אחד: ה-runner מושך אל ה-clone הזה, ואז מבצע checkout ל-worktree נפרד ממנו לכל סשן. clone שחומם מראש חוסך את ההורדה אבל לא את ה-checkout.
ספקו את ה-clone בתמונה או על אמצעי אחסון קבוע:
- Clone בתמונה: בנו את ה-clone לתוך תמונת ה-runner בנתיב הזה. כל קונטיינר חדש מתחיל אז עם ה-clone החם בלי שימוש חוזר בדיסק.
- Clone על אמצעי אחסון קבוע: על runners שאתם נועלים מראש לחשבון של משתמש אחד עם
--lock-to-account, הצביעו את--base-dirלאמצעי אחסון קבוע, כך שהדיסק משרת תמיד רק את החשבון הזה. runner שננעל מראש אף פעם לא אוסף סשנים של ערוץ Claude Tag, ולכן האפשרות הזו לא חלה על runners שמשרתים אותם.
מה שנתיב השימוש החוזר מבטיח ומה שהוא לא מבטיח:
עומק clone: בנו את ה-clone בלי
--depth, עם או בלי--single-branch. ה-runner אף פעם לא מעביר--depthכשהוא מושך אל clone עם remote בשםorigin, גם אם הגדרתםCLAUDE_RUNNER_FETCH_DEPTH. עם clone מלא, כל סשן מוריד רק את ה-commits שה-clone חסר. עם clone רדוד, סשן על ענף שהתפצל לפני ה-commit הישן ביותר של ה-clone מוריד את כל ההיסטוריה של הענף הזה.שינויים במעקב מאופסים, קבצים לא במעקב נשארים: ב-
--capacity 1, כל סשן מתחיל מ-hard reset שמוחק את השינויים במעקב של הסשן הקודם, אבל ה-runner אף פעם לא מריץgit clean, ולכן קבצים לא במעקב מסשנים קודמים של הבעלים הנעול נשארים בעץ.גם ספריות לכל סשן נשארות: לצד ה-checkout, ה-runner יוצר רשומות לכל סשן תחת
<base-dir>/_sessions/לכל סשן שהוא מריץ. ספריית התצורה של Claude של הסשן מחזיקה עותק מקומי של תמליל השיחה. לידה יושבים הקבצים שהועלו של הסשן, כשיש לסשן כאלה. גם ספריית הסשן יושבת שם: היא מחזיקה כל worktrees לכל סשן ו-checkouts של hook שלcheckoutבזמן שהסשן רץ, והיא שומרת כל דבר אחר ש-Claude כתב בה.כברירת מחדל ה-runner משאיר את אלה במקום כשהסשן מסתיים, ולכן על דיסק שממשיך אחרי תהליך ה-runner הם מצטברים. כל סשן רץ כמשתמש של ה-runner עצמו, ולכן כל סשן מאוחר יותר שהדיסק הזה משרת יכול לקרוא אותם. אם אתם שומרים
--base-dirקבוע, גדלו את אמצעי האחסון לצמיחה הזו. אותו דבר חל על כל הגדרה שמפעילה מחדש את ה-runner על אותה מערכת קבצים, כולל מתכון Docker Compose.עם
--remove-session-state, ספריות לכל סשן לא נשארות: הפעילו את ה-runner עם--remove-session-stateכדי שהוא ימחק את הספריות לכל סשן של כל סשן כשהסשן מסתיים. המחיקה היא best-effort: הספריות נשארות כשה-runner נהרג לפני שהניקוי שלו רץ. ה-clone הקנוני וקבצים שסשן כתב במקום אחר על המארח, כמו ספריית הקבצים הזמניים, נשארים בכל מקרה.עם ה-git proxy, ה-reset הופך ל-checkout: עם
--use-anthropic-git-proxy, ה-runner מנקה את.git/של ה-clone לפני כל סשן. הוא שומר את מאגר האובייקטים ואת מצב ה-shallow, ומוחק את ה-index. בלי ה-index, כל סשן מתחיל עם checkout מלא של עץ העבודה במקום reset כמעט מיידי. ה-runner עדיין מושך אל ה-clone הקיים במקום לשכפל את המאגר שוב. חימום מראש של submodules לא נתמך תחת ה-proxy.עם ה-git proxy, חימום מראש ב-
reftableמאבד את ה-refs שלו: כשה-runner מנקה את.git/לפני כל סשן, הוא שומר את.git/refs/ומסיר את הספרייה.git/reftable/. clone שחומם מראש בפורמט ה-refreftableשל git מאבד את ה-refs שלו כך, ולכן חממו מראש את ה-clone בפורמט ה-reffilesשל git במקום זאת.clones ארוכים לא צריכים מעקף: ה-runner מגביל כל פעולת git עם watchdog של 120 שניות בלי התקדמות ותקרה קשיחה של 30 דקות, לא timeout שטוח, ולכן clone קר איטי שממשיך לדווח על התקדמות מסתיים.
#לאשר שה-runner משתמש ב-clone שלכם
כדי לאשר שה-runner משתמש ב-clone שלכם, הפעילו אותו עם --log-level debug והפעילו סשן על המאגר. ה-runner רושם Prefetched repo found at ואת נתיב ה-clone. אם הוא רושם No prefetched repo, fresh init at במקום זאת, הוא לא מצא clone שמיש בנתיב בשורה הזו. בדקו שה-clone שלכם נמצא בנתיב הזה, בבעלות המשתמש של ה-runner, עם remote בשם origin.
אם ה-runner מוצא את ה-clone שלכם והסשנים עדיין מתחילים לאט, בדקו אם ה-clone רדוד.
#קבע את הגרסה
תהליך הבן של כל סשן מריץ כברירת מחדל את הבינארי של ה-runner עצמו, וה-runner מכבה עדכון אוטומטי בתוך הסשנים שהוא מפעיל. עם ברירת המחדל הזו, כל סשן מריץ את הגרסה שהתקנת על המארח או שבנית לתוך האימג'. עדכון ברמת המארח נכנס לתוקף בפעם הבאה שה-runner מופעל.
בחר איזו גרסה הסשנים שלך מריצים ומתי היא משתנה:
- לפני שאתה מקבע גרסה: בדוק את גרסאות Claude Code שהמודלים דורשים לכל מודל שהסשנים שלך משתמשים בו. אם מודל דורש גרסה חדשה יותר מזו שהסשנים שלך מריצים, השרת דוחה בקשות למודל הזה עם Claude Code does not support this model.
- כדי להחזיק צי על גרסה אחת: בנה את האימג' עם גרסה מקובעת, או על מארח חשוף התקן גרסה מסוימת והשבת עדכונים אוטומטיים
- כדי לשדרג צי קבוע: קרא את רשומות יומן השינויים בין הגרסה שלך לזו שאתה מתקין, ואז התקן את הגרסה החדשה יותר או בנה מחדש את האימג' והפעל מחדש את ה-runners
- כדי לשדרג runners לפי דרישה: קרא את רשומות יומן השינויים בין הגרסה שלך לזו שאתה מתקין, ואז שנה את האימג' ש-hook
spawn-runnerמפעיל. כל runner חדש מקבל את הגרסה החדשה. runner שכבר פועל, כולל runner בהמתנה ש---min-idleהפעיל, שומר על הגרסה שלו עד שהוא יוצא. אל תפעיל אותו מחדש, כי הזמנת העבודה שלו היא לשימוש יחיד. - אם סשנים מפעילים בינארי אחר: אם אתה משתמש ב-
--exec-pathאו ב-hook commandכדי להפעיל בינארי של Claude Code שאינו הבינארי של ה-runner עצמו, הסשנים מריצים את הגרסה של הבינארי הזה. עדכן את הבינארי הזה כשאתה מעדכן את ה-runner. - תוספים: גם שוקי תוספים לא מתעדכנים אוטומטית. הגדר
FORCE_AUTOUPDATE_PLUGINS=1בסביבה של ה-runner כדי לאפשר לתוספים להתעדכן אוטומטית בזמן שהבינארי נשאר מקובע
#הרחב את הצי
ה-orchestrator שלך מחליט מתי להוסיף או להסיר runners. בגלל נעילת בעלים אחד לכל runner, מספר העותקים המינימלי הוא מספר המשתמשים וסוכני Claude Tag שאתה מצפה שיהיו פעילים בו זמנית. --capacity שולט במקביליות בתוך הסשנים של בעלים אחד, ולא בין בעלים.
שתי גישות להרחבה זמינות:
- צי קבוע: הרץ קבוצה סטטית של עותקי runner והתאם את הגודל לפי מדדי Prometheus שכל runner מגיש
- runners לפי דרישה: הרץ את פקודת המשנה
claude self-hosted-runner orchestrator, שבודקת מול Anthropic סשנים שנמצאים בתור בלי runner זמין ומפעילה את ה-hook spawn-runnerשלך כדי להעלות אחד לכל סשן. ראה runners לפי דרישה.
#שחרר סשנים לא פעילים בצי קבוע
אם סשנים נשארים בתור בצי קבוע בזמן שכל runner מקוון, ייתכן שהסיבה היא סשנים לא פעילים. כברירת מחדל, סשן שומר על המשבצת שלו אחרי שהסבב שלו מסתיים, וה-runner שלו נשאר נעול לבעלים של הסשן הזה. סשנים של בעלים אחרים לא יכולים להשתמש במשבצות הפנויות של ה-runner הזה. אם משימה מתוזמנת בענן של הצוות שלך מתחילה סשן חדש בכל הרצה, הסשנים הלא פעילים שלה יכולים למלא כל משבצת.
כדי לבדוק, קרא את claude_code_self_hosted_runner_session_idle_seconds במדדי Prometheus של כל runner. לכל סשן ש-runner מחזיק יש סדרה משלו: 0 בזמן שהסשן עסוק, ואחרת מספר השניות מאז שהפך ללא פעיל. בחר את זמן חוסר הפעילות שתאפשר: ארוך יותר מההפסקות שאנשים עושים באמצע משימה, וקצר מספיק כדי שכל הרצה תשוחרר לפני שההרצה הבאה של המשימה המתוזמנת מתחילה. השווה את הסדרות לזמן הזה.
- סדרה מעל הזמן הזה, למשל
1800עבור 30 דקות: סשנים לא פעילים מחזיקים במשבצות האלה, אז הגדר--release-idle-session-min - כל סדרה מתחת לזמן הזה: אף סשן לא היה לא פעיל במשך הזמן הזה. הוסף runners, או הגדר
--kill-session-after-minעבור סשנים שלעולם לא הופכים ללא פעילים. - runner בלי סדרה: אף סשן לא מחזיק אותו. ראה סשנים נשארים בתור תחת פתרון בעיות.
- runner שהמדדים שלו לא עונים: ייתכן שהתהליך שלו יצא. ראה סביבה שנראית תקינה תחת כשה-runner יוצא.
כדי לשחרר סשנים לא פעילים, הפעל כל runner עם --release-idle-session-min והזמן הזה בדקות, למשל --release-idle-session-min 30. ה-runner אז משחרר כל סשן שנשאר לא פעיל כל כך הרבה זמן, בין אם משימה מתוזמנת או אדם התחילו אותו, אלא אם אתה מחזיק אותו עבור תהליכים שהוא השאיר רצים.
- עבודה שלא נדחפה: סשן ששוחרר ממשיך בהודעה הבאה שלו, בלי העבודה שהוא לא דחף. כדי לשמור על ה-commits שלו, ראה סשנים שמתחדשים מאבדים עבודה שלא נדחפה.
- ה-runner יוצא: runner שמשחרר את הסשן האחרון שלו מתחיל יציאה רגילה. בקש מה-supervisor שלך להפעיל אותו מחדש על מערכת קבצים חדשה, כפי שהקשח את הפריסה מתאר.
- סשנים שלעולם לא הופכים ללא פעילים: הגדר גם
--kill-session-after-min, כי חלק מהסשנים לא נספרים כלא פעילים
#שמור סשן לא פעיל בזמן שהתהליכים שלו רצים
כדי לשמור על סשן לא פעיל עד שתהליכים שהוא השאיר על המארח שלך מסתיימים, כמו משימות nohup, הפעל את ה-runner עם --hold-idle-file והמשך לבצע touch לקובץ הזה מהמארח. נדרש Claude Code v2.1.311 או חדש יותר.
השלבים האלה מניחים runner שאינו root על מארח. אם ה-runner שלך רץ כ-root, הסשנים שלו יכולים לבצע touch לקובץ בעצמם, אז הרץ אותו קודם כמשתמש ייעודי.
1. בחר נתיב לקובץ
בחר קובץ רגיל מקומי, כמו /var/lib/claude-hold/hold-idle, בתיקייה שסשנים לא יכולים לכתוב אליה, כולל תיקיות האב, כדי שסשן לא יוכל לכתוב אליו. ה-runner והסשנים שלו חולקים משתמש מערכת הפעלה אחד, אז צור קודם את התיקייה הזו, בבעלות משתמש אחר כמו root, עם מצב 755.
2. הפעל את ה-runner
מהמעטפת שלך, הפעל את ה-runner עם --hold-idle-file, --release-idle-session-min, ו---max-hold-min, התקרה על החזקה, שנמנית מהרגע שסשן הופך ללא פעיל. בלי הדגל הזה, התקרה היא יום אחד. הגדר אותו מעל --release-idle-session-min, אחרת שום דבר לא מוחזק. החלף את <writable-dir> כפי שההתחלה המהירה מתארת. הדוגמה הזו משחררת סשנים לא פעילים אחרי 30 דקות ומשחררת סשן מוחזק לכל היותר 4 שעות אחרי שהפך ללא פעיל:
claude self-hosted-runner \
--environment-secret-file /etc/claude/environment-secret \
--base-dir '<writable-dir>' \
--release-idle-session-min 30 \
--hold-idle-file /var/lib/claude-hold/hold-idle \
--max-hold-min 2403. בצע touch לקובץ בזמן שהתהליכים רצים
בזמן שהתהליכים רצים, בצע touch לקובץ מהמארח בתור הבעלים של התיקייה, בתדירות גבוהה יותר מכל שלוש דקות. ה-touch הראשון יוצר את הקובץ. לדוגמה, הוסף את השורה הזו ל-crontab של root:
* * * * * pgrep -x nightly-build > /dev/null && touch /var/lib/claude-hold/hold-idleהשורה רצה פעם בדקה ומבצעת touch לקובץ בזמן שכל תהליך בשם nightly-build רץ, כך שסשן שמתחיל תהליך כזה נשאר מוחזק עד התקרה. החלף את בדיקת ה-pgrep בדרך שבה המארח שלך מוצא מה הסשנים השאירו רץ.
הבדיקה חייבת להפסיק להתאים כשהם מסתיימים, אחרת כל סשן לא פעיל על ה-runner הזה נשאר מוחזק עד התקרה.
4. אשר את ההחזקה
בזמן שהתהליכים רצים, בדוק שזמן השינוי של הקובץ נשאר מתחת לשלוש דקות. כשזמן חוסר הפעילות של סשן נגמר, חפש idle release held בלוג של ה-runner. אם אין שורה כזו, קרא את leftover-hold= בשורת watchdog config שה-runner רושם בהפעלה, שמראה את הקובץ ואת התקרה, או למה שום דבר לא מוחזק.
שלוש דקות אחרי ה-touch האחרון, ההחזקה מסתיימת. בתוך כדקה נוספת, הלוג מציג idle release no longer held וה-runner משחרר את הסשן.
גם בזמן שהקובץ ממשיך להשתנות, החזקה יכולה להסתיים, במקרים כמו אלה:
- מעבר לתקרה:
--max-hold-minנגמר. כדי להחזיק יותר זמן, העלה אותו - שחרורים כפויים: הגדרת
--retire-at,--defer-shutdown-max-min, או--kill-session-after-min, והמגבלה הזו מגיעה. כדי להחזיק יותר זמן, הגדר מגבלה מאוחרת יותר
#בעיות ידועות ומגבלות
אלה המגבלות במהדורה הזו, עם דרכי עקיפה במקום שבו קיימת אחת.
#תעבורת מחברים יוצאת מהרשת שלך
Anthropic קוראת לכלי מחברים מהתשתית שלה, ולא מה-runner שלך. כלי מחברים הם המחברים של claude.ai, כמו GitHub, Slack ו-Linear. כש-Claude משתמש במחבר בסשן מתארח עצמית, התעבורה הזו עוברת דרך api.anthropic.com ולא מתחילה בתוך גבול הרשת שלך.
כדי להשאיר מחבר מחוץ לסשנים מתארחים עצמית, סנן אותו עם הגדרות המדיניות allowedMcpServers ו-deniedMcpServers. Claude Code מחיל את ההגדרות האלה על המחברים ש-Anthropic מספקת וגם על השרתים שאתה זורע ממארח ה-runner ועל השרתים שמשתמשים מוסיפים, כך שאם אתה פורס רשימת היתר לשרתים אחרים, Claude Code חוסם גם מחברים מסופקים. כדי להשאיר מחברים זמינים לצד רשימת היתר מבוססת URL, הוסף רשומות שתואמות לנתיבי ה-proxy של Anthropic למחברים מסופקים:
https://api.anthropic.com/v2/ccr-sessions/*https://api.anthropic.com/v1/code/sessions/*https://api.anthropic.com/v1/code/mcp/*
אם תעבורת הכלים חייבת להישאר בתוך הרשת שלך, הרץ את הכלים המקבילים כשרתי MCP מקומיים על אימג' ה-runner. ראה שרתי MCP.
#חלק מהסשנים לא נספרים כלא פעילים
סשן שמחזיק משימת רקע שממשיכה לרוץ לא נספר כלא פעיל, כך ש---release-idle-session-min לא ישחרר את המשבצת של הסשן הזה. סשן שממתין לאישור שהתבקש מתוך קריאת כלי שרצה גם לא נספר כלא פעיל. הגדר --kill-session-after-min לצדו כרשת ביטחון קשיחה.
--kill-session-after-min הוא רשת ביטחון לסשנים שרצים בלי שליטה. ב-runner בגרסה v2.1.260 או חדשה יותר, סשן שמגיע למגבלה לא מסתיים מיד. ה-runner נותן לו חלון חסד, 15 דקות כברירת מחדל, שאפשר לשנות עם SELF_HOSTED_RUNNER_MAX_LIFETIME_GRACE_MS:
- אם הסשן ממתין למשתמש שלו, ה-runner משחרר אותו. אם הסבב שלו הסתיים והוא מחזיק רק משימות רקע, ה-runner ממתין עד 60 שניות שהמשימות האלה יסתיימו ואז משחרר אותו. הסשן ממשיך כשהמשתמש שלו שולח את ההודעה הבאה.
- אם סבב עדיין רץ, ה-runner ממתין שהסבב יסתיים, או שהסשן ימתין בפעם הבאה למשתמש שלו, ואז משחרר אותו.
- אם הסשן עדיין על ה-runner כשחלון החסד נגמר, ה-runner מסיים אותו, והעבודה של כל סבב שרץ אובדת. סבב שממתין לאישור שהתבקש מתוך קריאת כלי שרצה הוא דרך אחת שבה סשן ממשיך מעבר לחלון.
סשן ששוחרר ממשיך משיבוט חדש, כך שעבודה שהוא לא דחף נעלמת בכל מקרה. ראה סשנים שמתחדשים מאבדים עבודה שלא נדחפה. לפני v2.1.260, ה-runner סיים כל סשן במגבלה, אחרי שהמתין לכל היותר את חלון החסד כדי שסבב שרץ יסתיים.
הגדר את הדגל מעל הסשן הארוך ביותר שאתה מצפה לו, למשל --kill-session-after-min 480 עבור 8 שעות. כדי לפנות משבצות משיחות שהופכות ללא פעילות, השתמש ב---release-idle-session-min.
#סשנים שמתחדשים מאבדים עבודה שלא נדחפה
סשן שמתחדש על runner חדש מאבד את העבודה שהוא לא דחף, כי ה-runner הזה משבט את המאגר שוב מענף ההתחלה שלו.
כדי לשמור על עבודה שבוצע לה commit, הגדר --push-outcome-on-release על כל runner בסביבה. runner עם הדגל מבצע דחיפה במאמץ מיטבי של ענפי התוצאה של הסשן לפני שהוא משחרר, והסשן שמתחדש מתחיל מה-commits האלה. runner בלי הדגל מחדש את הסשן מענף ההתחלה שלו. הדחיפה משתמשת בפרטי הגישה ל-git של מארח ה-runner עצמו, כולל ב-runner שמשתמש ב-git בניהול Anthropic.
[!WARNING]
לפני שאתה מגדיר את הדגל, הגבל מי יכול לדחוף ל-refs של
claude/*ב-remote של המקור. בחידוש, ה-runner מושך את הענף שנדחף קודם בלי לאמת מי דחף אותו.
גם כשהדגל מוגדר, שמור את העבודה הזו בדרך נוספת:
- שינויים שלא בוצע להם commit: אלה עדיין אובדים. ראה הנח לסשנים לדחוף את העבודה שלהם.
- מאגרים שמתבצע להם checkout דרך
checkoutlifecycle hook: אלה לא נדחפים. צלם אותם מ-hook post-session. - commits בסשן שמישהו מעביר לארכיון או מוחק, או שמשחררים את ה-runner שלו מתפריט הסשן: אל תסמוך על הדחיפה של הדגל. כדי לשמור אותם, דחוף את
HEADלענף הצלה מ-hook post-session. הדוגמה שם דוחפת רק אחרי commit של צילום, אז גרום לשלך לדחוף גם כשאין דבר שלא בוצע לו commit.
#מגבלות נוספות
- פקודה שנקטעה כי runner אבד יכולה לרוץ שוב: כשהסשן מתחדש על runner אחר, Claude רואה את קריאת הכלי שנקטעה עם תוצאה שאומרת שהתוצאה שלה לא ידועה.
- מאגר שנוסף באמצע סשן עלול להיכשל בשיבוט: Claude משבט אותו עם
git cloneדרך HTTPS. על runner בלי--use-anthropic-git-proxy, השיבוט נכשל עם שגיאת אימות git אם שום דבר על המארח לא יכול לקרוא את המאגר. איפה שאפשר, בחר כל מאגר שהסשן צריך כשאתה יוצר אותו.- כשהסשן מתחדש: hook
checkoutרץ גם עבור המאגר שנוסף, עםCLAUDE_RUNNER_REPO_REFריק, אז גרום ל-hook שלך לטפל בזה.
- כשהסשן מתחדש: hook
- חלק מהמחברים לא מופיעים בסשנים מתארחים עצמית: מחבר שעדיין לא חיברת בהגדרות claude.ai לא מופיע ברשימה בסשן מתארח עצמית, והסשן לא יבקש ממך לחבר אותו. חבר אותו קודם בהגדרות, ואז התחל סשן חדש. הוספת מחבר לסשן שכבר רץ גם לא הופכת את הכלים שלו לזמינים ל-Claude. התחל סשן חדש כדי לקלוט מחבר שנוסף זה עתה.
#דווח על בעיה
לבעיות בסביבות מתארחות עצמית, פנה לצוות החשבון שלך ב-Anthropic.
#פתרון בעיות
לאבחון מודרך, הרץ את פקודת המשנה doctor על מארח ה-runner. פקודת המשנה doctor מתחילה סשן Claude Code אינטראקטיבי עם הלוגים והמצב של ה-runner מצורפים. היכנס עם claude auth login על המארח הזה קודם כדי שהסשן יוכל לשאול את הסביבה שלך, את ה-runners שלה, ואת הסשנים שלה בתור. בלי הכניסה הזו, למשל כשהמארח מאמת עם מפתח API, הוא מוגבל לנקודת הקצה המקומית של תקינות, למדדים, וללוג של ה-runner, והוא קורא את הלוג רק אם הפעלת את ה-runner עם --log-file.
claude self-hosted-runner doctorבעיות נפוצות:
הרץ לא מופיע בסביבה: אשר שהמארח יכול להגיע אל
api.anthropic.comדרך HTTPS ושסוד הסביבה עדכני. הרץ רושם[runner:fatal]עם סיבת הדחייה בכשל אימות.הרץ יוצא בהפעלה עם
cannot create or write to base directory: הרץ לא יכול ליצור או לכתוב אל--base-dir, שברירת המחדל שלו היא/workspace. תקן את הבעלות על הספריה או כוון את--base-dirלנתיב שניתן לכתיבה, כמתואר בשמור על ספריית הבסיס והקיבולת זהות בין הרצים. אם הרץ רושם במקום זאת[runner:fatal]שאומר שבדיקת ספריית הבסיס חרגה מהזמן, הספריה נמצאת על עיגון NFS או CSI תקוע. בדוק את תקינות העיגון במקום את ההרשאות. הרץ מדפיס את שני כשלי ההפעלה האלה ל-stderr לפני שהוא פותח את--log-file, לכן חפש אותם במסוף או ביומני המכולות של הפלטפורמה שלך, במקום בקובץ היומן. לפני v2.1.225, הרץ לא בדק את ספריית הבסיס בהפעלה, וההגדרה השגויה הזו הכשילה סשנים אחרי איסוף.הרץ יוצא בהפעלה עם
RegisterRunner failed:ושגיאת אישור TLS: השגיאה יכולה להיותself signed certificate in certificate chain, לדוגמה. הרץ לא סומך על רשות האישורים (CA) שמאחורי האישור שקיבל, כפי שקורה מאחורי פרוקסי שבודק TLS. הגדר אתNODE_EXTRA_CA_CERTSבסביבה של הרץ לקובץ PEM עם CA השורש של הפרוקסי.לא מגדירים אותו שוב לסשנים, והם יורשים אותו. Git לא קורא אותו: לשכפולים דרך הפרוקסי הזה, הגדר גם את
GIT_SSL_CAINFO.סקריפט ההפעלה שלך מדפיס שגיאת כתיבה שמכילה
Resource temporarily unavailableויוצא: הסקריפט שלך חולק את stdout או stderr שלו עם תהליךclaude self-hosted-runnerאו תהליך ה-orchestrator שהוא מפעיל, שהעביר את הצינור או השקע המשותף למצב לא חוסם. כשהצינור מלא, כתיבה מהסקריפט נכשלת בשגיאת מערכת ההפעלה הזו במקום להמתין, ותחתset -eהסקריפט יוצא.כדי לעצור את השגיאה, שלח את הפלט של הרץ או של ה-orchestrator לקובץ שהמשתמש של הסקריפט יכול ליצור. הוסף
>> "$HOME/claude-runner.log" 2>&1לפקודה שמפעילה אותו, לפני כל&שמעביר אותו לרקע, והחלף את<writable-dir>כפי שההתחלה המהירה מתארת:claude self-hosted-runner --environment-secret-file /etc/claude/environment-secret --base-dir '<writable-dir>' >> "$HOME/claude-runner.log" 2>&1הפלט של הסקריפט עצמו עדיין הולך לאן שהלך קודם, ומה שהרץ כותב ל-stdout ול-stderr, כולל שגיאות ההפעלה שלו, הולך לקובץ הזה. השתמש בהפניית המעטפת הזו, ולא ב-
--log-fileלבדו, שמשקף את היומן לקובץ בזמן שהרץ ממשיך לכתוב ל-stdout ול-stderr. אם כבר מעבירים--log-file, השאר אותו גם, כי ההפניה לא משנה את מה שהקובץ הזה מקבל.ה-orchestrator רושם
poll failed:עם שגיאת אישור TLS וממשיך לנסות שוב: ה-orchestrator לא סומך על ה-CA שמאחורי האישור שקיבל, כפי שקורה מאחורי פרוקסי שבודק TLS. הגדר אתNODE_EXTRA_CA_CERTSבסביבה של ה-orchestrator לקובץ PEM עם CA השורש של הפרוקסי. הגדר אותו גם לרצים שה-hook שלspawn-runnerשלך מפעיל.לדוגמה, שורה יכולה להסתיים ב-
poll failed: self signed certificate in certificate chain , retrying in 1s, עם המילים של ספריית ה-TLS בין הנקודתיים למקף. כדי לאשר את התיקון, בדוק ש-connectedהואtrueבגוף/healthzשל ה-orchestrator.סשנים נשארים בתור: כל רץ מקוון עשוי להיות נעול לבעלים אחר. בדוק את המדד
claude_code_self_hosted_runner_locked_accountמדד של כל רץ, או את השדהlocked_accountבשורת היומן[runner:health]שלו, כדי לראות מי מחזיק בו. שניהם מציגים את האימייל של הבעלים רק אחרי שהונפק לרץ אסימון סשן שנושא claim שלact.email, דבר שסשנים של סוכן Claude Tag אף פעם לא עושים. בלי ה-claim, הרץ לא פולט סדרתlocked_accountורושםlocked_account=yes, מה שאומר שהרץ נעול אבל לא לאיזה בעלים. הוסף replicas, או המתן שרץ קיים יתרוקן ויופעל מחדש. אם הסביבה משתמשת ברצים לפי דרישה, בדוק את ה-orchestrator. ראה רצים לפי דרישה.סשנים נכשלים מיד אחרי איסוף: פתח את הסשן ב-claude.ai/code כדי לראות את השגיאה. אם השגיאה מראה ש-git לא הצליח לבצע אימות, בדוק את אישורי ה-git של הרץ. ברץ שהופעל עם
--use-anthropic-git-proxy, ראה כשסשנים נכשלים להתחיל ברץ עם פרוקסי ה-git.ספריית בסיס שאי אפשר לכתוב אליה עוצרת את הרץ בהפעלה במקום להכשיל סשנים. ראה את הרשומה הרץ יוצא בהפעלה עם
cannot create or write to base directoryברשימה הזו.סשן נאסף, אבל Claude אף פעם לא עונה להודעה הראשונה שלו: אם הרצים שלך מגיעים אל
api.anthropic.comדרך פרוקסי, הגדר אותו להעביר הלאה את תגובת/worker/events/streamשל הסשן כפי שהיא מגיעה. העלאת ה-timeouts של הרץ לא עוזרת. כדי לאשר את הסיבה ביומן של הרץ, ראה סשן נאסף, אבל Claude אף פעם לא עונה להודעה הראשונה שלו.סשנים נכשלים להתחיל ברץ שהגדיר
--use-anthropic-git-proxy: חפש ביומן של הרץ אתaccess denied by the git proxy, או שגיאת git שמציינת כתובתapi.anthropic.comשמכילה/git_proxy/. כדי לדעת אם Anthropic שירתה את הסשן ולתקן את הסיבה, ראה כשסשנים נכשלים להתחיל ברץ עם פרוקסי ה-git.סשנים לא מצליחים להגיע לרשת דרך פרוקסי יציאה עם אימות: כשהמקור שהגדרת עם
--proxy-authorization-commandאו--proxy-authorization-fileנכשל, חורג מהזמן אחרי 30 שניות, או מניב ערך ריק, הרץ עונה לחיבור הזה502 Bad Gatewayורושם למה. הרץ מסתיר את stderr של הפקודה ביומן הזה ואף פעם לא רושם את ערך הכותרת. עם--proxy-authorization-command, הרץ את הפקודה בעצמך על המארח כדי לאשר שהיא מדפיסה את כל ערך הכותרת ב-stdout. אם הרץ יוצא בהפעלה עםcould not start the proxy-authorization listener, הוא לא הצליח לפתוח את מאזין ה-loopback שלו.הרץ רושם שורות
Poll failedשמכילותrejecting the malformed poll response: הרץ קיבל תגובת סקר עבודה שגוף שלה אינו ה-JSON שהתור מצפה לו. סיבה אחת היא פרוקסי מיירט או פורטל שבוי שעונה בעמוד משלו. אם הרץ מתחבר דרך פרוקסי, הגדר את הפרוקסי להעביר תגובות מ-api.anthropic.comבלי שינוי.הרץ דוחה את התגובה הפגומה, סופר אותה תחת הסוג
transportשל המדדclaude_code_self_hosted_runner_poll_errors_totalמדד, ומנסה שוב לפי לוח הזמנים של סקר שנכשל המתואר במחזור חיי סשן. הרץ ממשיך לשרת את הסשנים החיים שלו. לפני v2.1.246, הרץ קרא תגובה כזו כתור עבודה ריק, מה שיכול היה לסיים את הסשנים החיים שלו או לגרום לו לצאת.סשן נכשל עם
no longer exists on the remoteאוcouldn't find remote ref: הרץ קורא אתcouldn't find remote refשל git כענף שנמחק. הענף עדיין יכול להתקיים. חפש אותו במארח ה-git שלך, בדיוק בשם שבהודעה, ואז בדוק כל סיבה שמתאימה:- הענף נעלם: שחזר אותו, כפי שההודעה מבקשת.
- הענף קיים ואינך מנהל את הרץ: שלח למנהל של הרץ את ההודעה המלאה ואמור לו שהענף קיים.
- הענף קיים ולרץ יש hook של
checkout: פקודת git ב-hook שלך יכולה להדפיסcouldn't find remote refל-stderr לפני שה-hook יוצא בקוד שאינו אפס. מצא את הפקודה הזו, ותקן את ה-ref שהיא מבקשת או את ה-remote שממנו היא מושכת. הרץ מעביר ל-hook שלך את הרוויזיה שהסשן ביקש כ-CLAUDE_RUNNER_REPO_REF. - הענף קיים ולרץ אין hook של
checkout: הרץ מושך את הענף בעצמו. אם--git-host-rewriteחל על המאגר, אשר שלמארח שאליו הוא משכתב יש את הענף באותו שם, ודחוף את הענף לשם אם הוא לא שם. - הענף קיים והרץ ישן מ-v2.1.301: שדרג את הרץ כמתואר בהצמד את הגרסה, כדי שכשל אחר יציג את השגיאה שלו עצמו. לפני v2.1.301, כשלים אחרים עם
couldn't find remote refבפלט השגיאה שלהם דווחו גם הם כענף שנמחק.
אחרי שמשדרגים את הרץ או שמתקנים את הסיבה, נסה שוב את הסשן או התחל סשן חדש. אם אף אחת מהבדיקות האלה לא מוצאת את הסיבה, דווח על בעיה.
עבור המקור שאליו הסשן דוחף תוצאות, ענף שנמחק מכשיל את הסשן. עבור מקור git שהסשן רק קורא ממנו, הרץ מדלג על המקור הזה וממשיך ביתר. הרץ מכשיל את הסשן כשדילוג היה משאיר אותו בלי שום מאגר. לפני v2.1.228, סשן כזה התחיל בספריה ריקה.
Claude Tag לפעמים לא מפרסם את התשובה שלו ב-Slack: תזכורת התשובה היא hook שמישור הבקרה מספק. אשר שהרץ הוא Claude Code v2.1.229 או חדש יותר עם
claude --version, כי רצים ישנים יותר מתעלמים מ-hooks שסופקו. אשר ש-python3 --versionמצליח בתוך תמונת הרץ, כי ה-hooks מריצים אתpython3. ואשר שאף קובץ הגדרות לא מכבה hooks, בין אםdisableAllHooksב-managed-settings.json, ה-~/.claude/settings.jsonשל משתמש הרץ, או.claude/settings.jsonאו.claude/settings.local.jsonשל המאגר, אוallowManagedHooksOnlyאוstrictPluginOnlyCustomizationשמכסה hooks בהגדרות מנוהלות.סשן מתחיל בלי אחד מהמאגרים שלו: ברץ בלי hook של
checkout, מארח ה-git יכול לסרב לבדיקת הגישה של הרץ למאגר שהסשן רק קורא ממנו. הרץ אז מדלג על המאגר הזה, רושם שורת[runner:warn] could not access context sourceשמציינת את הסירוב, ומתחיל את הסשן על הנותרים.עם אישורי git משלו, הרץ יכול לדלג על מאגר כשהמארח עונה שהמאגר לא נמצא, git לא מוצא אישורים למארח, או האימות נכשל.
עם
--use-anthropic-git-proxy, כשפרוקסי ה-git מסרב למאגר עםaccess denied by the git proxy, הרץ מדלג עליו. שורת היומן אז אומרתblocked by the git proxy. לסיבות שכדאי לבדוק, ראה את המקרהremote: access denied by the git proxyבכשסשנים נכשלים להתחיל ברץ עם פרוקסי ה-git.כשל רשת, timeout, או כל HTTP
403אחר עדיין מכשילים את התחלת הסשן, וכך גם סירוב למאגר שהסשן דוחף אליו תוצאות. הרץ עדיין מכשיל סשן שדילוג היה משאיר בלי שום מאגר.בדיקת הגישה רצה שוב בכל פעם שהסשן מתחיל על רץ, כך שברגע שלזהות ה-git של הרץ יש גישת קריאה, ההתחלה הבאה משכפלת את המאגר. לפני v2.1.274, כל אחד מהסירובים האלה הכשיל את התחלת הסשן.
היומן מציג
could not read context source: השורה הזו, בניגוד ל-could not access context source, אומרת שמארח ה-git המשיך לענות HTTP429, או ש-wrapper שהתקנת בתורgitהמשיך לצאת עם75. הרץ דילג על המאגר הזה. כדי לנסות שוב, התחל סשן חדש. הרץ גם מנסה את המאגר שוב בכל פעם שהסשן הזה מתחיל על רץ.בלי hook של
checkout, הרץ יכול לדלג על מאגר שהסשן רק קורא ממנו, אלא אם הוא הראשון בין מאגרי הסשן. לפני v2.1.311, התחלת הסשן נכשלה.סשן נכשל להתחיל עם
This runner couldn't read: הרץ ביקש ממארח ה-git מאגר עם אישורי ה-git שלו וקיבל תשובה שהמאגר לא נמצא. המאגר לא קיים, או שהאישורים האלה לא יכולים להגיע למאגר.- אם אתה מנהל את הרץ: תן לאישורי ה-git שלו גישת קריאה למאגר, ואז התחל סשן חדש.
- אם התחלת את הסשן: אם המאגר לא קיים, התחל סשן חדש עם המאגר הנכון. אם הרץ לא יכול להגיע למאגר, בקש ממנהל הרץ להעניק גישה, ואז התחל סשן חדש.
לסשנים לוקח דקות להתחיל או להתחדש: סיבה אחת היא שכפול טרי. בצי שמפעיל כל רץ על דיסק ריק, בלי hook של
checkout, הרץ משכפל את מאגרי הסשן מחדש לפני שהוא מתחיל את Claude Code. סשן שמתחדש על דיסק ריק ממתין לאותו שכפול שוב.כדי לדלג על השכפול הטרי, ספק checkout שחומם מראש. אם כבר סיפקת אחד, אשר שהרץ משתמש בו. במקום שבו הרץ יוצר את השכפול בעצמו ויוצא אחרי סשן אחד, הנמך את
CLAUDE_RUNNER_FETCH_DEPTHמברירת המחדל שלו של 50 קומיטים כדי למשוך פחות היסטוריה.אם אתה משתמש ב-hook, הוא רץ במקום השכפול של הרץ, לכן הסתכל על מה שה-hook שלך עושה. עקוב אחרי המדד
claude_code_self_hosted_runner_session_init_duration_secondsמדד כדי לראות כמה זמן לוקחות התחלות.תורות נכשלים עם 401: כשתור מסתיים ב-401 או ב-403 מ-API של Anthropic, הרץ מביא
CLAUDE_CODE_OAUTH_TOKENטרי מ-Anthropic ומעביר אותו לסשן. התור שנכשל לא מנוסה שוב. האסימון הזה קצר מועד, והרץ מסובב אותו דרך stdin של הסשן.כשאחזור נכשל, הרץ רושם שורת
inference_token refresh failedשאומרת מתי ינסה שוב, והוא ממשיך לנסות שוב כל עוד הסשן רץ.אם כל קריאה מתחילה להיכשל בערך 30 דקות מתחילת סשן, סקריפט wrapper כנראה ניתק את stdin של הסשן, כך שסיבובי האסימון לא יכולים להגיע אליו. ראה השאר את stdin ואת מתאר הקובץ 3 מחוברים.
לפני v2.1.274, הרץ הפסיק לנסות שוב אחזור שנכשל אחרי כמה ניסיונות והמתין למתוזמן הבא. תור שנכשל לא הפעיל אחזור, כך שכל תור נכשל עם 401 עד האחזור המתוזמן הבא.
תור נכשל ב-claude.ai/code עם
This model needs a newer version of Claude Code: גרסת Claude Code שבה הרצים שלך מתחילים סשנים ישנה יותר ממה שדורש המודל של הסשן, כך שה-API דוחה את הבקשה. שדרג את הרצים לגרסה שההודעה מבקשת, או לגרסה מאוחרת יותר, כמתואר בהצמד את הגרסה. עד אז, אנשים יכולים לבחור מודל אחר בסשן.Pod נהרג באמצע ריקון: העלה את
terminationGracePeriodSecondsלפחות עד הערך שהרץ רושם בהפעלה. ראה תזמון כיבוי.הרץ רושם שורת
[runner:log]שמסתיימת ב-was paused or too slow: הרץ המשיך לרוץ ואינו זקוק להפעלה מחדש. עד שהזרם stdout או stderr שנקרא בשורה נקרא שוב, הרץ השליך את כל השורות מלבד 256 KiB החדשים ביותר של הזרם הזה. אם העברת--log-file, חפש שם את השורות שהושלכו.הרץ רושם
Nobody can read this process's stdout anymore: מה שקרא את stdout של הרץ כבר לא שם, כך שהרץ מפסיק לכתוב לשם למשך שאר הריצה שלו וממשיך לרוץ. כל עוד stderr עדיין פתוח, השורה הזו ושורות הסטטוס המאוחרות של הרץ, כולל שורות[runner:fatal], הולכות ל-stderr, לכן קרא אותן שם.
אחרי שאתחול הרישום הושלם, הרץ כותב את יומן מחזור החיים שלו, כולל שורות [runner:fatal], ל-stdout, ופלט ניפוי ל-stderr, הכל כשורות טקסט פשוט ולא כ-JSON. כשלי ספריית הבסיס המתוארים ברשומות פתרון הבעיות שלמעלה מודפסים ל-stderr לפני הנקודה הזו. לכוד את שני הזרמים עם --log-file, שגם מאפשר ל-self-hosted-runner doctor לבצע להם tail, או עם איסוף היומנים של הפלטפורמה שלך.
כל תהליך בן של סשן כותב יומן ניפוי באגים נפרד. בכשל הראנר מציג את זנב היומן לצד הסשן ב claude.ai/code. אלא אם הפעלת את הראנר עם --remove-session-state, הוא גם שומר את יומן הסשן שנכשל על הדיסק ומדפיס את הנתיב שלו ביומן הראנר.
#כשהראנר יוצא
אל תפעיל מחדש ראנר לפי דרישה, כי הזמנת העבודה שלו היא לשימוש חד פעמי. ראנר שיוצא מיד אחרי שהוא מתחיל דורש טיפול שונה מראנר שיוצא מכל סיבה אחרת.
- יציאה רגילה: הראנר סיים את הסשנים שלו ורוקן, הגיע לזמן הפרישה שלו, או נאמר לו לעצור. הפעל אותו מחדש כדי שלסביבה תהיה שוב קיבולת. מחזור החיים של הראנר מתאר את היציאות האלה.
- התחלה שנכשלה: הראנר לא יכול להתחיל עם התצורה או המארח שניתנו לו, ולכן הוא יוצא שניות אחרי שהוא מתחיל, והוא יוצא באותו אופן בכל פעם שמפעילים אותו מחדש. הפעלה מחדש מהירה יותר לא עוזרת. מישהו צריך לקרוא את הפלט שלו ולתקן את הסיבה.
- אובדן קשר: ראנר שלא מצליח להגיע אל Anthropic לזמן ארוך יותר מהחכירה שלו, למשל בזמן שהמארח שלו ישן, יכול להיות מוסר מהסביבה. כשראנר שהוסר מתחבר מחדש, הוא יוצא. היומן שלו יכול להציג שורת
[runner:fatal]שמכילהrunner record gone server-sideאו, אחרי הפסקה ארוכה יותר,poll auth failed. הראנר לא נרשם שוב בעצמו, אז הפעל אותו מחדש.
הגדר את המפקח שלך להפעיל מחדש את הראנר בכל פעם שהוא יוצא, להמתין זמן ארוך יותר בין הפעלות מחדש כשהראנר ממשיך לצאת מיד אחרי שהוא מתחיל, ולהודיע למישהו כשזה ממשיך לקרות.
בפרודקשן, גרום למפקח שלך להפעיל מחדש את הראנר על דיסק נקי, בקונטיינר או במכונה וירטואלית חדשים, כפי שחיזוק הפריסה ממליץ.
#זיהוי התחלה שנכשלה
כשהראנר לא יכול להתחיל, הוא מדפיס שורה שאומרת למה, ואז הוא יוצא. ברוב הסיבות השורה מכילה [runner:fatal]. בחלק מהסיבות השורה מתחילה ב error: במקום, כולל כשהראנר לא יכול לנתח את הדגלים שלו, לא יכול לקרוא את סוד הסביבה, או לא יכול ליצור או לכתוב לספריית הבסיס. השורה הבאה מצביעה אז על --help.
רוב שורות היומן מתחילות בחותמת זמן וב [self-hosted-runner], שהדוגמה למטה משמיטה. לדוגמה, ראנר שהופעל עם פרוקסי הגיט של Anthropic ועם קיבולת מעל אחת מדפיס שורה כזאת:
[runner:fatal] --use-anthropic-git-proxy requires --capacity 1 (the proxy URL is per-session and linked worktrees share origin). Omit --use-anthropic-git-proxy or set --capacity 1.חפש את השורה בפלט הסטנדרטי ובשגיאה הסטנדרטית של הראנר, ביומני הקונטיינר של הפלטפורמה שלך, או בקובץ שהגדרת עם --log-file. הראנר מדפיס שורת error: לפני שהוא פותח את קובץ היומן, אז חפש אותה בטרמינל או ביומני הקונטיינר שלך, כפי שפתרון בעיות מציין.
אלה גם עוזרים כשקוראים התחלה שנכשלה:
- אין שורה בכלל: ראנר שהמארח הורג לא מדפיס אף אחת מהן. אם הפלט מסתיים בלי שורת
[runner:fatal]ובלי שורתerror:, בדוק אם המארח או המתזמר שלך עצרו את התהליך, למשל בגלל חריגה ממגבלת זיכרון. - קוד היציאה: הראנר לא מקצה קוד יציאה נפרד לשגיאות שחוזרות בכל התחלה. הוא יוצא עם אותו קוד עבור שגיאת תצורה, כמו שילוב דגלים לא נתמך, ועבור כשל שיכול להתפנות מעצמו, כמו API שנשאר לא נגיש לאורך הניסיונות החוזרים של הראנר עצמו. בסס את ההחלטה להמתין זמן ארוך יותר על כמה מהר הראנר יצא, וקרא את הפלט של הראנר כדי לדעת למה.
- סביבה שנראית תקינה: חלק משלבי ההפעלה רצים אחרי שהראנר נרשם בסביבה שלך, כמו
--configure-gitוהגדרת האישורים של פרוקסי הגיט של Anthropic. אם אחד מהשלבים האלה נכשל, הסביבה יכולה להמשיך לרשום את הראנר הזה כמה דקות אחרי שהתהליך יצא, ודף Cloud environments יכול להציג Healthy בזמן שאף ראנר לא קולט עבודה. אם סשנים נשארים בתור בסביבה שנראית תקינה, בדוק אם המפקח שלך מפעיל מחדש את הראנר.
#הפעלה מחדש עם המתנה שגדלה
איך מקבלים המתנה שגדלה תלוי במפקח שלך.
Kubernetes: הDeployment בדף הזה לא דורש שינוי. אחרי שקונטיינר יוצא, ה kubelet כברירת מחדל ממתין לפני שהוא מפעיל מחדש את הקונטיינר, וההמתנה גדלה בכל הפעלה מחדש עד לתקרה. ההמתנה מתחילה מחדש אחרי שהקונטיינר רץ זמן מה בלי לצאת.
ה kubelet מחיל את אותה המתנה אחרי יציאה רגילה כשהקונטיינר רץ רק זמן קצר. ראנר שמתרוקן לעתים קרובות יכול לכן להציג גם את הסטטוס
CrashLoopBackOff, אז קרא את הפלט לפני שתסיק שהראנר לא יכול להתחיל. הפקודה למטה קוראת את הפלט של הריצה האחרונה מפוד אחד של ה Deployment:kubectl logs --previous -n claude-runners deploy/claude-runnerכשהריצה האחרונה הייתה התחלה שנכשלה, שורת
[runner:fatal]אוerror:נמצאת בין השורות האחרונות של הפלט. כדי לקרוא את הריצה האחרונה של פוד אחר, ציין את שם הפוד הזה במקוםdeploy/claude-runner.Docker ו Docker Compose: מתכון ה Compose בדף הזה לא דורש שינוי. עם
restart: always, Docker ממתין זמן ארוך יותר לפני כל הפעלה מחדש של קונטיינר שממשיך לצאת, עד לתקרה. החלף את<container>בשם הקונטיינר בפקודה למטה, שקוראת כמה פעמים Docker הפעיל מחדש את הקונטיינר:docker inspect --format '{{.RestartCount}}' <container>הפקודה מדפיסה מספר. מספר שממשיך לעלות אומר ש Docker ממשיך להפעיל מחדש את הראנר.
יחידת systemd: כברירת מחדל systemd ממתין את אותו
RestartSecלפני כל הפעלה מחדש ולא מאריך אותו, כך שיחידה עםRestart=alwaysמפעילה מחדש ראנר שלא יכול להתחיל באותו מרווח בכל פעם. כשההתחלות מגיעות מהר מספיק כדי להגיע למגבלת קצב ההתחלה של היחידה, חמש התחלות ב 10 שניות כברירת מחדל, systemd מפסיק להפעיל מחדש את היחידה. היחידה נשארת עצורה עד שמישהו מפעיל אותה שוב, מה ש systemd מתיר אחרי שמרווח מגבלת הקצב עבר או אחריsystemctl reset-failed. מכיוון שRestartSecחל על כל הפעלה מחדש, ערך ארוך יותר גם מעכב את ההפעלה מחדש אחרי יציאה רגילה. בחר ערך שמאזן בין השניים, והתרע על ספירת ההפעלות מחדש של היחידה.לולאת מעטפת או המפקח שלך: החל את אותו כלל בעצמך. התחל עם המתנה של חמש שניות. אחרי כל ריצה שהסתיימה בתוך דקה, הכפל את ההמתנה להפעלה מחדש הבאה, עד חמש דקות. אחרי ריצה שנמשכה דקה או יותר, חזור לחמש שניות.
#בדיקה למה הראנר ממשיך לצאת
כשהראנר יצא מיד אחרי שהתחיל כמה פעמים ברצף, עצור ובדוק את אלה לפני שתפעיל אותו מחדש שוב.
- שורת
[runner:fatal]אוerror:האחרונה: היא אומרת למה הראנר עצר. פתרון בעיות מפרט את הסיבות הנפוצות. - שילוב הדגלים: פרוקסי הגיט של Anthropic דורש
--capacity 1. המתכונים בדף הזה משתמשים בקיבולת גבוהה יותר, אז הנמך אותה כשמוסיפים את הפרוקסי לאחד מהם. - מה סביבת השירות יכולה להגיע אליו: אם הראנר מתחיל ידנית ונכשל תחת המפקח שלך, השווה את המשתמש, את ספריית הבית, את
PATH, ואת מגבלת הזיכרון.--configure-gitופרוקסי הגיט של Anthropic צריכים git עלPATHו~/.gitconfigשניתן לכתיבה. - סוד הסביבה: אם ביטלת את הסוד או הקלדת אותו לא נכון, הראנר מדפיס שורה שמכילה
RegisterRunner auth failed. - לשונית Activity של הסביבה: פתח את הסביבה ובחר Activity. אם ראנרים חדשים ממשיכים להופיע שם ואף אחד לא קולט עבודה, המפקח שלך מפעיל מחדש את הראנר.
לאבחון מודרך על מארח הראנר, הרץ את פקודת המשנה doctor.
#סשן נקלט, אבל Claude אף פעם לא עונה להודעה הראשונה שלו
אם ראנר קולט סשן אבל Claude אף פעם לא עונה להודעה הראשונה, והראנרים שלך מגיעים אל api.anthropic.com דרך פרוקסי, הפרוקסי עשוי להיות הסיבה. Anthropic שולח לסשן את ההודעות שלו על פני תגובת סטרימינג אחת שנשארת פתוחה. אם הפרוקסי שלך אוגר אותה, או מסיים אותה לפני שהוא מעביר משהו הלאה, ההודעה הראשונה אף פעם לא מגיעה. בקשות אחרות מהסשן עדיין מצליחות.
כדי לתקן את זה, שנה את האופן שבו הפרוקסי מטפל בבקשה הזאת:
GET https://api.anthropic.com/v1/code/sessions/<session-id>/worker/events/stream- אל תאגר את התגובה: העבר אותה הלאה כשהיא מגיעה. סוג התוכן שלה הוא
text/event-stream. - תן לתגובה להישאר פתוחה: היא יכולה להישאר פתוחה עד שעה. אם לפרוקסי יש פסק זמן של חוסר פעילות, שמור אותו הרבה מעל 15 שניות, כי Anthropic שולח keep-alive לפחות בתדירות הזאת כברירת מחדל.
אחר כך התחל סשן חדש ובדוק ש Claude עונה. העלאת פסקי הזמן של הראנר לא עוזרת: רק ממתינים זמן ארוך יותר לפני שהראנר עוצר את הסשן.
#אישור הסיבה ביומן הראנר
השלבים האלה הם לראנרים על Claude Code v2.1.287 או חדש יותר, ברמת ברירת המחדל --log-level info. ראנרים ישנים יותר רושמים את השורות האלה מאוחר יותר, או לא רושמים אותן בכלל.
- המתן עד ש
--startup-timeout-minיעבור, 15 דקות כברירת מחדל. הראנר אז עוצר את הסשן ורושם שורה שמכילהno child output forומסתיימת ב, releasing. - חפש ביומן הראנר את
SDKStartup: SSE. השורות האלה באות אחרי שורהfinished: interrupted, כמה שורות אחרי זו שבשלב 1. לרובן אין חותמת זמן, אז אוסף יומנים עשוי להציג אותן במקום אחר. - מצא את השורה בטבלה הזאת:
| שורת היומן מכילה | מה לעשות |
|---|---|
SSE connect failed (no response in 30s) או SSE liveness timeout | בדוק שהפרוקסי מעביר את הבקשה, מעביר את הכותרות והנתונים של התגובה הלאה כשהם מגיעים, ולא מפיל את החיבור |
SSE connect failed (HTTP וקוד סטטוס | בדוק את היומן של הפרוקסי עצמו. אם הפרוקסי ענה לבקשה בעצמו, תן לבקשה לעבור דרכו. |
SSE connect failed ( וכל טקסט אחר | הטקסט בסוגריים הוא השגיאה. חפש את אותו חיבור ביומן של הפרוקסי עצמו. |
SSE stream ended by server, שוב ושוב | בדוק את מגבלת הזמן של הפרוקסי לתגובות ארוכות טווח. שורה אחת כזאת היא רגילה. |
SSE reconnected after | חפש אחת מהשורות האחרות אחריה. השורה הזאת לא אומרת שמשהו הגיע לסשן. |
אם ביומן אין אף אחת מהשורות האלה, או שיומן הפרוקסי לא מראה שום דבר שגוי, דווח על בעיה.
#עצירת הסשן מלנסות שוב
הסשן שומר את ההודעה בהמתנה, וראנר שיכול לשרת את בעל הסשן קולט את הסשן שוב. עד שתשנה את הפרוקסי, הסשן ממתין באותו אופן בכל פעם. כדי לעצור את זה, העבר את הסשן לארכיון ב claude.ai/code.
#מה הלאה
- התאמה אישית של סשנים: סקריפטי מעטפת, הוקי מחזור חיים, ראנרים לפי דרישה, שרתי MCP, והרשאות
- בדיקה מקצה לקצה: אמת תמונת ראנר חדשה מ CI לפני קידום שלה
- עזר: כל דגל CLI, משתנה סביבה, ומדד