מדריך קלוד קוד בעברית

תיעוד 141

התאמה אישית של סשנים בסביבות באירוח עצמי

התאימו סשנים של סביבה באירוח עצמי באמצעות סקריפטי עטיפה לאישורי גישה לכל סשן, ווי מחזור חיים, והפעלת runner לפי דרישה.

הערה:

סביבות באירוח עצמי נמצאות בבטא ציבורית בתוכניות Team ו-Enterprise. Owner מפעיל אותן על ידי הפעלת Allow self-hosted environments בדף הניהול Cloud environments. הדף הזה מניח runner שעובד. ראו את ה-quickstart להקמה ואת Deploy to production למתכוני הצי.

סביבה באירוח עצמי מריצה סשנים בענן של Claude Code על התשתית שלכם, שמבוצעים על ידי תהליך runner שאתם פורסים. בלי הגדרה, ה-runner משכפל את המאגר של הסשן, מפעיל את Claude Code, ומנקה. הדף הזה מיועד למהנדס הפלטפורמה שמפעיל את ה-runners: הוא מכסה את נקודות ההרחבה למקרים שבהם ברירות המחדל האלה לא מתאימות, מהקצאת אישורי גישה לכל סשן ועד החלפה מלאה של checkout. סקריפטי עטיפה וווים רצים כקבצי הרצה על מארח ה-runner, שהוא Linux או macOS, והדוגמאות בדף הזה מניחות מעטפת POSIX.

כמה משתני סביבה של ווים בדף הזה עדיין משתמשים ב-pool, למשל CLAUDE_RUNNER_POOL_ID. שמות דגלי ה-CLI ומשתני הסביבה משתמשים ב-environment, למשל --environment-secret-file.

#סקריפטי עטיפה

השתמשו בסקריפט עטיפה כשכל סשן צריך הגדרה שה-runner לא יכול לבצע בעצמו:

  • הקצאת אישורי גישה קצרי חיים שמוגבלים ליוצר הסשן
  • ייצוא סודות שתלויים בסביבה
  • הכנת שרשראות כלים לשפות
  • החלת מגבלות משאבים סביב תהליך הבן

ה-runner מפעיל את סקריפט העטיפה שלכם במקום הבינארי של Claude Code בכל פעם שהוא מתחיל סשן, כולל כשסשן מתחדש או מוחזר לתור. סיימו את סקריפט העטיפה ב-exec אל $CLAUDE_RUNNER_CLAUDE_BIN, הבינארי של ה-runner עצמו, כדי שאותות וקודי יציאה יועברו נכון.

הצביעו על --exec-path, או על SELF_HOSTED_RUNNER_EXEC_PATH, אל סקריפט העטיפה כשאתם מפעילים את ה-runner, והחליפו את <writable-dir> כפי שמתואר ב-quickstart:

claude self-hosted-runner --environment-secret-file /etc/claude/environment-secret --base-dir '<writable-dir>' --exec-path /etc/claude/session-wrapper.sh

ה-runner מגדיר את הדברים הבאים בסביבה של סקריפט העטיפה:

משתנהתיאור
CLAUDE_CODE_SESSION_ACCESS_TOKENה-JWT של הסשן, עם הקידומת sk-ant-cc-. טענת act שלו מזהה את יוצר הסשן, עם כתובת הדוא"ל של היוצר כשמשטח היצירה תיעד אותה. הערך הוא הטוקן בזמן ה-spawn. רענונים מגיעים דרך stdin של תהליך הבן, ולכן סקריפט עטיפה רואה רק את הערך הראשוני. ראו Verify session identity.
CCR_SESSION_ACCOUNT_EMAILכתובת הדוא"ל של יוצר הסשן, שחולצה מראש על ידי ה-runner מטענת act.email של הטוקן בלי אימות חתימה. מתאימה לסימון, למשל trailers של commit. כשהדוא"ל משמש שער להנפקת אישורי גישה, אמתו את הטוקן וקראו את הטענה ממנו. ראו הקצאת אישורי גישה שמוגבלים ליוצר הסשן. לא מוגדר כשהטוקן לא נושא דוא"ל של יוצר, למשל בסשנים שזהות השירות של הארגון יוצרת. התייחסו אליו כאל מידע מזהה אישית.
CLAUDE_RUNNER_CLIENT_PLATFORMמשטח הלקוח שיצר את הסשן, למשל web_claude_ai, desktop_app, ios, claude_code_cli, או scheduled_trigger. Anthropic רושמת את הערך פעם אחת ביצירת הסשן, ולכן סקריפט העטיפה וכל וו מחזור חיים רואים את אותו ערך. השתמשו בו לניתוח אימוץ ולסימון בלבד, לא כאות הרשאה. לא מוגדר כשלסשן אין משטח מתועד או מזוהה. דורש Claude Code v2.1.229 או חדש יותר.
CLAUDE_RUNNER_CLAUDE_BINנתיב מוחלט לבינארי Claude Code של ה-runner עצמו. סיימו את סקריפט העטיפה ב-exec "$CLAUDE_RUNNER_CLAUDE_BIN" "$@" כדי להעביר שליטה לבינארי המוצמד בלי לקבע נתיב התקנה בקוד.
CLAUDE_CODE_REMOTE_SESSION_IDמזהה סשן בצורה המתויגת cse_.... זה אותו סשן שווי מחזור החיים רואים כ-CLAUDE_RUNNER_SESSION_ID בצורה session_.... משתני ה-UUID תואמים בשניהם, והחלפת הקידומת cse_ ב-session_ מניבה את המזהה שמוצג בכתובת ה-URL של הסשן.
CLAUDE_CODE_REMOTE_SESSION_UUIDאותו מזהה סשן בצורת UUID קנונית, למערכות שמפתחות לפי UUID.
CLAUDE_CODE_REMOTE_SLACK_THREAD_URLבסשן Claude Tag ששייך לשרשור Slack אחד, הקישור לשרשור הזה. לא מוגדר לסשנים אחרים, ויכול להיות לא מוגדר גם לסשן שרשור.
CLAUDE_CODE_REMOTE_SLACK_THREAD_TSבסשן Claude Tag ששייך לשרשור Slack אחד, חותמת הזמן של Slack של השרשור הזה, למשל 1700000000.000100. יכול להיות לא מוגדר, ויכול להיות מוגדר כש-CLAUDE_CODE_REMOTE_SLACK_THREAD_URL לא מוגדר, אז בדקו כל משתנה בפני עצמו.
CLAUDE_CODE_REMOTE_SLACK_CHANNEL_IDבסשן Claude Tag, המזהה של ערוץ Slack או הודעה ישירה שהסשן משרת או שהשרשור שלו נמצא בו, למשל C0123ABCDE. חותמת זמן של Slack מזהה הודעה בתוך ערוץ אחד, אז רשמו את המזהה הזה יחד עם CLAUDE_CODE_REMOTE_SLACK_THREAD_TS. לא מוגדר לסשנים אחרים, ויכול להיות לא מוגדר גם לסשן Claude Tag.
CLAUDE_SESSION_INGRESS_TOKEN_FILEנתיב מוחלט לקובץ לכל סשן שמחזיק את ה-JWT הנוכחי של הסשן, ונשמר מעודכן לאורך רענוני טוקן. תהליכי משנה של מעטפת קוראים אותו עבור כותרת Authorization שלהם כשהם מורידים קבצים מצורפים שהמשתמש הוסיף לסשן. exec שומר את המשתנה אוטומטית. סקריפט עטיפה שבונה מחדש את סביבת תהליך הבן חייב להעביר את המשתנה, אחרת הורדות קבצים מצורפים מפסיקות לעבוד בשקט.
CLAUDE_CONFIG_DIRספריית הגדרות Claude לכל סשן, שנכתבת בתחילת הסשן מתמונת המצב של הגדרות מארח ה-runner שה-runner לוכד בהפעלה. ראו Permissions and tool approval. כתיבות כאן מבודדות לסשן הזה. הספרייה נשארת תחת <base-dir>/_sessions/ אחרי שהסשן מסתיים, אלא אם מפעילים את ה-runner עם --remove-session-state. ראו Reuse a pre-warmed checkout.
CLAUDE_CODE_REMOTE_MCP_CONFIGנתיב לקובץ ב-CLAUDE_CONFIG_DIR שמחזיק את שרתי ה-MCP שהועברו ממישור הבקרה. אם סקריפט העטיפה בונה מחדש את סביבת תהליך הבן, העבירו את המשתנה הזה, את CLAUDE_CODE_REMOTE, ואת CLAUDE_CODE_ENVIRONMENT_KIND בלי שינוי. ה-runner מגדיר את שלושתם. אם CLAUDE_CODE_REMOTE מגיע אבל המשתנה הזה או CLAUDE_CODE_ENVIRONMENT_KIND לא, תהליך הבן עוצר שרתי stdio, HTTP, ו-SSE שסקריפט העטיפה מוסיף עם --mcp-config ושעדכון של השרתים שהועברו משמיט. דורש Claude Code v2.1.299 או חדש יותר.
ANTHROPIC_BASE_URLכתובת הבסיס של ה-API שתהליך הבן ישתמש בה, שמועברת על ידי מישור הבקרה לכל סשן ובדרך כלל https://api.anthropic.com. אל תדרסו אותה: אישור ההסקה של הסשן הוא טוקן OAuth שהונפק על ידי Anthropic וספקים אחרים לא מקבלים אותו.
CLAUDE_CODE_OAUTH_TOKENטוקן הגישה OAuth קצר החיים שתהליך הבן משתמש בו להסקה של מודל, עם אורך חיים של כ-30 דקות. ה-runner מנפיק אותו מחדש לפני התפוגה ומעביר את הרוטציה דרך stdin של תהליך הבן, ולכן סקריפט עטיפה שלא שומר את stdin מחובר רואה רק את הערך הראשוני. אל תסתמכו על רשימת ההיתר של כתובות IP של הארגון כדי לתחום את השימוש בטוקן הזה: התייחסו אליו כאל אישור bearer שנשאר שמיש בערך 30 דקות אם הוא דולף, ואל תרשמו אותו ללוג, אל תכתבו אותו לדיסק, ואל תעבירו אותו מחוץ לקונטיינר של הסשן.
CLAUDE_RUNNER_ALIVE_EVERY_MSה-runner מגדיר כל כמה זמן, במילישניות, תהליך הבן מדווח שהוא חי כשתור מתחיל לפני שתהליך הבן מאותחל. אם סקריפט העטיפה בונה מחדש את סביבת תהליך הבן, העבירו את המשתנה הזה, אחרת ה-runner יכול לשחרר את הסשן במהלך התור הזה ולרשום ללוג no child output for <n> , releasing. דורש v2.1.309 או חדש יותר על ה-runner ועל תהליך הבן.

סקריפט העטיפה גם יורש את שאר הסביבה המנוהלת של תהליך הבן, כולל משתני סביבה שהשרת מספק. exec מעביר את כולם אוטומטית. אם סקריפט העטיפה מפעיל את תהליך הבן בדרך אחרת, העבירו את הסביבה המלאה.

אפשר לקרוא את משתני CLAUDE_CODE_REMOTE_SLACK_* בסקריפט העטיפה או בוו command, ובמה שהסשן מריץ, למשל פקודות מעטפת, ווי git, וווי Claude Code. ווי checkout, post-session, ו-spawn-runner לא מקבלים אותם.

#תנו ברירת מחדל למשתנים שיכולים להיות לא מוגדרים

אם הסקריפט משתמש ב-set -u, הרחיבו את CCR_SESSION_ACCOUNT_EMAIL, CLAUDE_RUNNER_CLIENT_PLATFORM, CLAUDE_CODE_REMOTE_SLACK_THREAD_URL, CLAUDE_CODE_REMOTE_SLACK_THREAD_TS, ו-CLAUDE_CODE_REMOTE_SLACK_CHANNEL_ID עם ברירת מחדל, למשל "${CCR_SESSION_ACCOUNT_EMAIL:-}". כל אחד מהם יכול להיות לא מוגדר, ותחת set -u Bash נעצר עם unbound variable כשהוא מרחיב משתנה לא מוגדר.

#צטטו את הארגומנטים והמשתנים שהסקריפט מרחיב

צטטו כל ארגומנט ומשתנה שה-runner מעביר לסקריפט העטיפה או לוו command. חלק מהערכים מכילים תווים שהמעטפת פועלת עליהם, כי הם מחזיקים JSON, משפטים שלמים, או כלל הרשאה כמו Bash(npm run test *).

  • ארגומנטים: העבירו אותם הלאה כ-"$@"
  • משתנים: צטטו כל אחד, כמו ב-"${CLAUDE_CODE_REMOTE_SLACK_THREAD_URL:-}". קישור שרשור Slack, למשל, יכול להכיל ? ו-&.
  • מחרוזות eval ו-sh -c: אל תציבו ערך בתוך מחרוזת ש-eval או sh -c מריצים, גם בתוך מרכאות. תנו למחרוזת הזו להפנות למשתנה.

#שמרו את stdin ואת מתאר קובץ 3 מחוברים

stdin של תהליך הבן הוא ערוץ הבקרה של ה-runner. רוטציות טוקן ואותות סיום סשן מגיעים עליו. ה-runner גם פותח צינור על מתאר קובץ 3 וקורא ממנו את אותות הפעילות של תהליך הבן כדי להניע פסקי זמן של חוסר פעילות ושל הפעלה. exec "$CLAUDE_RUNNER_CLAUDE_BIN" "$@" רגיל שומר את שניהם אוטומטית.

אם סקריפט העטיפה מעביר את תהליך הבן לרקע עם & חשוף, הוא מנתק את stdin של תהליך הבן. הסשן נראה תקין עד שאורך החיים של כ-30 דקות של טוקן ה-OAuth הראשוני פג, ואז כל קריאת API שמשתמשת בטוקן נכשלת עם 401 authentication_error. אם סקריפט העטיפה חייב להעביר את תהליך הבן לרקע, למשל כדי לשמור מלכודת teardown חיה, שמרו את stdin על מתאר קובץ 9 וחברו אותו מחדש במפורש:

exec 9<&0   # the runner uses 3, and from v2.1.309 reserves 4 and 5
"$CLAUDE_RUNNER_CLAUDE_BIN" "$@" <&9 9<&- &
CHILD=$!
trap 'teardown' EXIT
wait "$CHILD"

אפשר להפנות את stdout של תהליך הבן. שמרו את מתאר קובץ 3 ואת stderr מחוברים ל-runner:

  • מתאר קובץ 3: נושא את אותות הפעילות של תהליך הבן אל ה-runner. אל תסגרו אותו ואל תשתמשו בו מחדש בסקריפט העטיפה.
  • מתארי קובץ 4 ו-5: runners על Claude Code v2.1.309 או חדש יותר שומרים אותם לתהליך הבן. אל תסגרו אותם ואל תשתמשו בהם מחדש בסקריפט העטיפה. אם סקריפט העטיפה משתמש באחד מהם, למשל כדי לשמור stdin, העבירו את השימוש הזה למתאר מ-6 עד 9, שגם גרסאות קודמות משאירות פנוי.
  • stderr: כשסקריפט העטיפה או תהליך הבן יוצאים עם קוד שאינו אפס, ה-runner מפרסם את השורות האחרונות של stderr לסשן ומדפיס אותן בלוג שלו. המשתמש של הסשן רואה את השורות האלה, אז אל תדפיסו סודות ל-stderr, והסירו את set -x לפני שפורסים את סקריפט העטיפה. אם מפנים את stderr, הסשנים עדיין רצים, אבל ה-runner מדווח על כשל עם קוד היציאה בלבד.

#העבירו את דגלי הנחיית המערכת

הנחיית המערכת והנחיית המערכת המצורפת שמישור הבקרה של Anthropic שולח לסשן מגיעות לסקריפט העטיפה כנתיבי קבצים, לא כטקסט מוטמע. ה-runner כותב כל הנחיה לקובץ בספריית ההגדרות של הסשן, CLAUDE_CONFIG_DIR, ומעביר את הנתיב שלה בארגומנטים שסקריפט העטיפה מקבל, כ---system-prompt-file <path> או --append-system-prompt-file <path>.

runners על Claude Code v2.1.281 או חדש יותר מעבירים את ההנחיות כקבצים. לפני v2.1.281, ה-runner העביר אותן כ---system-prompt <text> ו---append-system-prompt <text>.

בסקריפט העטיפה או בוו command, טפלו בדגלים האלה כך:

  • העבירו אותם הלאה: סיימו את סקריפט העטיפה ב-exec "$CLAUDE_RUNNER_CLAUDE_BIN" "$@", שמעביר את דגלי הקובץ יחד עם כל ארגומנט אחר. אל תשמיטו אותם ואל תכתבו אותם מחדש. אם סשן מאבד דגל של קובץ הנחיה, הוא רץ בלי ההוראות שמישור הבקרה שלח עבורו.
  • על runner בגרסה v2.1.281 או חדשה יותר, דגל קובץ שמוסיפים מחליף את זה של השרת, ולעולם לא מוסיף אליו: כל דגל של קובץ הנחיה מקבל ערך יחיד ו-Claude Code שומר את המופע האחרון, אז אם מוסיפים --append-system-prompt-file <path> אחרי "$@", התוכן של הקובץ מחליף את ההוראות המצורפות של השרת. כדי להוסיף הוראות מעל אלה של השרת, שימו אותן ב-CLAUDE.md של תמונת ה-runner, שה-runner זורע להגדרת רמת המשתמש של כל סשן.

#תיקון Argument list too long בתחילת סשן

ב-Linux, אם מחברים את ההנחיה של מישור הבקרה ואת הטקסט שלכם לארגומנט --append-system-prompt אחד, ה-runner רושם שורה כזו ללוג ברגע שהארגומנט מגיע ל-128 KB:

Session <id> finished: failed , reason: child exited 126: /opt/wrapper.sh: line 9: /usr/local/bin/claude: Argument list too long

Linux מגביל כל ארגומנט יחיד ל-32 דפי זיכרון, או 128 KB עם הדפים הרגילים של 4 KB. הנתון מ-getconf ARG_MAX, לעתים קרובות 2 MB, הוא מגבלה נפרדת על כל הארגומנטים והסביבה יחד.

ב-Claude Code v2.1.283 או חדש יותר, השאירו את דגלי הקובץ ב-"$@" והעבירו רק את הטקסט שלכם כ---append-system-prompt. Claude Code משתמש בשניהם, קודם ההנחיה של מישור הבקרה ואחר כך הטקסט שלכם:

exec "$CLAUDE_RUNNER_CLAUDE_BIN" "$@" --append-system-prompt "$(cat /opt/team-instructions.md)"

ב-Linux, אם הטקסט שלכם מגיע ל-128 KB, העבירו אותו ל-CLAUDE.md של תמונת ה-runner, כי הוא עדיין ארגומנט אחד.

ב-v2.1.281 או v2.1.282, הפקודה הזו יוצאת עם Cannot use both --append-system-prompt and --append-system-prompt-file. בגרסאות קודמות, הטקסט שלכם מחליף את ההנחיה של מישור הבקרה בלי שגיאה. בכל אחת מהן, עדכנו קודם, או העבירו את הטקסט שלכם ל-CLAUDE.md.

#הקצאת אישורי גישה שמוגבלים ליוצר הסשן

השתמשו בפקודת המשנה decode-token כדי לקרוא טענות מ-JWT של הסשן. היא קוראת את הטוקן מארגומנט, מ-CLAUDE_CODE_SESSION_ACCESS_TOKEN, או מ-stdin, בסדר הזה. ראו Verify the token inside the session לגבי מה שהיא בודקת. הדוגמה למטה מפענחת את זהות היוצר, מחליפה אותה באישורי AWS קצרי חיים, ומבצעת exec אל Claude Code:

#!/bin/bash
# Key on the stable Anthropic user ID and require a human creator.
CREATOR_SUB=$("$CLAUDE_RUNNER_CLAUDE_BIN" self-hosted-runner decode-token \
  | jq -re '.act.sub // "" | select(startswith("user:"))') \
  || { echo "decode-token: verification failed or no human creator" >&2; exit 1; }

creds=$(your-sts-helper assume-role --subject "$CREATOR_SUB") \
  || { echo "credential exchange failed" >&2; exit 1; }
eval "$creds"

exec "$CLAUDE_RUNNER_CLAUDE_BIN" "$@"

השתמשו ב-jq -re במקום ב-jq -r כשהטענה שחולצה משמשת שער להחלטת אימות, כדי שטענה חסרה תצא עם קוד שאינו אפס במקום להעביר את המחרוזת המילולית null הלאה. סשנים שנוצרים על ידי זהות שירות של ארגון, כמו סשני בוט וסוכן, נושאים נושא agent: ולא user:, ולכן הדוגמה הזו מסרבת להם. אם הסביבה שלכם משרתת את הסשנים האלה, החליטו במפורש אם סקריפט העטיפה נופל חזרה לאישור ברירת מחדל עבורם במקום לצאת. כשחילוף אישורי הגישה צריך את הדוא"ל, קראו את .act.email וטפלו בהיעדר שלו: הטוקן נושא אותו רק כשמשטח היצירה תיעד אותו, וסשן שנשלח מ-CLI יכול להיות בלי אותו. למדריך המלא של הטענות ולאימות משירותים מחוץ ל-runner, ראו Verify session identity.

#hooks של מחזור חיים

hooks של מחזור חיים מחליפים שלבים בצינור של ה-runner לכל סשן בסקריפטים שלך. הפנה את ה-runner לתיקייה של hooks באמצעות --hooks-dir <path>, או SELF_HOSTED_RUNNER_HOOKS_DIR. ה-runner מחפש קבצים ברי הרצה עם שמות מוכרים. כל hook שאינו קיים עובר להתנהגות המובנית, כך שאתה כותב רק את אלה שאתה צריך. ה-hooks רצים עם ההרשאות של ה-runner עצמו, ותהליכי הבנים של הסשן חולקים את אותו UID, לכן עגן את תיקיית ה-hooks לקריאה בלבד, או אפה אותה לתוך התמונה, כדי שקוד הסשן לא יוכל לשנות אותה. ראה את סעיף ההקשחה.

ה-hooks האלה נפרדים מ-hooks של Claude Code, שרצים בתוך הסשן. hooks של מחזור חיים רצים על ה-runner, סביב הסשן.

#checkout

רץ לכל מאגר בכל פעם שה-runner מתחיל סשן, כולל כשהסשן מתחדש או מוחזר לתור, במקום ה-clone וה-fetch המובנים של ה-runner. השתמש ב-hook כדי לשכפל ממראת read-through שאליה אתה מגיע דרך HTTPS או SSH, לאכלס עץ עבודה מארכיון, או להחיל אימות git לכל סשן.

ה-runner מגדיר את המשתנים האלה, ויכול להגדיר משתני CLAUDE_RUNNER_ נוספים שהטבלה לא מפרטת:

משתנהתיאור
CLAUDE_RUNNER_REPO_URLכתובת URL של המאגר לשכפול, אחרי שהוחלו --git-host-rewrite ו---git-ssh-rewrite כלשהם
CLAUDE_RUNNER_REPO_REFרוויזיה ל-checkout, כפי שהסשן ביקש אותה: ענף, תג, SHA של commit, או שם reference מלא כגון refs/heads/<branch> או refs/pull/<number>/head. ריק פירושו ענף ברירת המחדל של המאגר.
CLAUDE_RUNNER_CHECKOUT_PATHנתיב מוחלט שבו עץ העבודה חייב להישאר
CLAUDE_RUNNER_SESSION_IDמזהה סשן בצורה המתויגת session_..., לרישום ולקישור
CLAUDE_RUNNER_SESSION_UUIDאותו מזהה סשן בצורת UUID קנונית
CLAUDE_RUNNER_API_BASE_URLכתובת הבסיס של Anthropic API לקריאות בהיקף הסשן
CLAUDE_RUNNER_CLIENT_PLATFORMמשטח הלקוח שיצר את הסשן, כגון web_claude_ai, desktop_app או ios. לא מוגדר כשאין לסשן משטח מתועד או מזוהה, לכן התייחס אליו כ-"${CLAUDE_RUNNER_CLIENT_PLATFORM:-}" תחת set -u. דורש Claude Code v2.1.229 או חדש יותר.
CLAUDE_CODE_SESSION_ACCESS_TOKENאסימון הגישה של הסשן, לקריאות API בהיקף הסשן
GIT_CONFIG_COUNT, GIT_CONFIG_KEY_n, GIT_CONFIG_VALUE_nהגדרות Git שה-runner מקבע עבור ה-git שה-hook שלך מריץ. הגדרת Git בתוך hooks של מחזור חיים מתארת אותן. דורש Claude Code v2.1.280 או חדש יותר.

הסקריפט חייב להשאיר עץ עבודה ב-CLAUDE_RUNNER_CHECKOUT_PATH שעבר checkout ברוויזיה המבוקשת. HEAD מנותק עובד, כי ה-runner יוצר את ענף העבודה של הסשן מעליו.

אחרי שה-hook שלך חוזר, ה-runner מוודא ש-CLAUDE_RUNNER_CHECKOUT_PATH מכיל .git. אם ה-hook שלך מממש מקור שאינו git כגון Perforce או tarball שנפרק, הגדר CLAUDE_RUNNER_SKIP_GIT_VERIFY=1 בסביבה של ה-runner כדי לדלג על הבדיקה הזו. זרימות מבוססות Git כגון יצירת ענף עבודה ודחיפת תוצאות דורשות checkout של git, לכן ייצא תוצרים מעצים שאינם git באמצעות hook של post-session.

#קבלת פרטי גישה של git ב-hook

ה-runner לא מעביר ל-hook פרטי גישה של git. גם פקודת המשנה decode-token אינה זמינה כאן, כי CLAUDE_RUNNER_CLAUDE_BIN אינו מוגדר בסביבת ה-checkout hook. הנפק פרטי גישה לשכפול לכל סשן מזהות הסשן, או חזור לאימות ה-git של המארח עצמו:

  • פרטי גישה לשכפול לכל סשן: אמת את CLAUDE_CODE_SESSION_ACCESS_TOKEN בספריית JWT רגילה מול נקודת הקצה JWKS תחת CLAUDE_RUNNER_API_BASE_URL, כמתואר ב-אימות האסימון מהשירות שלך. אחר כך בקש משירות פרטי הגישה שלך להנפיק פרטי גישה לשכפול קצרי חיים עבור הזהות ב-claim act של האסימון. בסס את פרטי הגישה על act.sub, ואל תדרוש act.email.
  • אימות git של המארח: השתמש באימות git שכבר יש למארח, כגון סוכן SSH, credential helper או .netrc.

#כשה-hook נכשל

ה-hook נכשל כשהוא יוצא עם קוד שאינו אפס, או יוצא עם 0 בלי להשאיר checkout שמיש:

  • מאגר שהסשן דוחף אליו תוצאות: ה-runner מכשיל את הסשן, וביציאה עם קוד שאינו אפס מציג למשתמש את זנב ה-stderr של הסקריפט. כתוב את הסיבה ל-stderr כשורה קצרה אחת, כתוב אותה אחרונה, ואז צא עם קוד שאינו אפס. אל תדפיס סודות ל-stderr, והסר את set -x לפני שאתה פורס את ה-hook.

    אם couldn't find remote ref של git מופיע בערך ב-4 KB האחרונים של ה-stderr של ה-hook, ה-runner מדווח במקום זאת על ענף שנמחק. הודעת השגיאה של הסשן אומרת אז שהענף כבר לא קיים ב-remote ומבקשת מהמשתמש לשחזר אותו. אם הענף הזה קיים, ראה סשן נכשל עם no longer exists on the remote או couldn't find remote ref.

  • מאגר שהסשן רק קורא ממנו, כגון מאגר שנוסף לסשן רץ: ה-runner מדלג עליו וממשיך עם שאר המאגרים. ה-runner מפרסם שלב Skipped לסשן ומסיר את מה שה-hook השאיר בנתיב ה-checkout.

    כששלב Skipped מזכיר את ה-checkout hook, מצא את פרטי הכשל בשורה [runner:warn] checkout hook failed בלוג של ה-runner. כשהשלב אומר ש-ref של מקור no longer exists on remote, ראה סשן נכשל עם no longer exists on the remote או couldn't find remote ref. אם הדילוג משאיר את הסשן בלי מאגר כלל, ה-runner מכשיל את הסשן בכל מקרה.

כשה-hook מצליח, ה-runner מסיר את נתיב ה-checkout אחרי שהסשן מסתיים.

#post-session

רץ אחרי שתהליך הבן של Claude Code יצא ולפני שה-runner מפרק את סביבת העבודה. זה הסיכוי היחיד שלך לשמור עבודה שלא עברה commit, כי שינויים במעקב שלא עברו commit לא שורדים באף אחד מהמסלולים:

  • ב---capacity מעל אחד: ה-runner מוחק worktrees לכל סשן מיד אחרי שה-hook חוזר
  • ב---capacity 1: ה-clone הקנוני שנעשה בו שימוש חוזר עובר hard-reset כשהסשן הבא מתחיל

שימושים אופייניים הם דחיפת ענף snapshot של שינויים שלא עברו commit, אחסון לוגים בארכיון, או פליטת אירוע של סיום סשן למערכות שלך.

ה-hook מופעל בכל סיום סשן שבו נוצר תהליך בן, יהיה הגורם אשר יהיה. הערכים של CLAUDE_RUNNER_EXIT_REASON למטה מונים את המקרים. הוא לא יכול להיות מופעל כשה-runner מסתיים בפתאומיות, כגון preemption של VM או הפסקת חשמל. אם אתה צריך הבטחות מפני סיום פתאומי, צלם snapshot מעת לעת מתוך הסשן עם hook PostToolUse של Claude Code. ה-runner מגדיר:

משתנהתיאור
CLAUDE_RUNNER_SESSION_IDמזהה סשן בצורה המתויגת session_...
CLAUDE_RUNNER_SESSION_UUIDאותו מזהה סשן בצורת UUID קנונית
CLAUDE_RUNNER_EXIT_REASONאיך הסשן הסתיים. ראה את הערכים מתחת לטבלה
CLAUDE_RUNNER_WORKSPACE_PATHSנתיבים מוחלטים מופרדים בנקודתיים של עצי העבודה של הסשן. ריק לסשנים בלי מאגר.
CLAUDE_RUNNER_DEBUG_LOG_PATHנתיב ללוג הדיבוג של הסשן, שעדיין על הדיסק בזמן שה-hook רץ
CLAUDE_RUNNER_API_BASE_URLכתובת הבסיס של Anthropic API לקריאות בהיקף הסשן
CLAUDE_RUNNER_CLIENT_PLATFORMמשטח הלקוח שיצר את הסשן, כגון web_claude_ai, desktop_app או ios. לא מוגדר כשאין לסשן משטח מתועד או מזוהה, לכן התייחס אליו כ-"${CLAUDE_RUNNER_CLIENT_PLATFORM:-}" תחת set -u. דורש Claude Code v2.1.229 או חדש יותר.
CLAUDE_CODE_SESSION_ACCESS_TOKENאסימון הגישה של הסשן, לקריאות API בהיקף הסשן
GIT_CONFIG_COUNT, GIT_CONFIG_KEY_n, GIT_CONFIG_VALUE_nהגדרות Git שה-runner מקבע עבור ה-git שה-hook שלך מריץ. הגדרת Git בתוך hooks של מחזור חיים מתארת אותן. דורש Claude Code v2.1.280 או חדש יותר.

CLAUDE_RUNNER_EXIT_REASON מקבל אחד מארבעה ערכים:

  • completed: הסשן הסתיים בצורה נקייה. תהליך Claude Code יצא כרגיל, או יצא מעצמו אחרי שהסשן אורכב או נמחק.
  • failed: תהליך Claude Code קרס, או שההקמה נכשלה אחרי שהתחיל.
  • interrupted: ה-runner עצר את הסשן, באחד מהמקרים האלה:
    • ה-runner שחרר את הסשן כדי לפנות את ה-slot.
    • פג הזמן של הסשן בהפעלה.
    • השרת העביר את הסשן מה-runner הזה.
    • ה-poll של ה-runner הבחין בארכוב או במחיקה לפני שהתהליך יצא.
    • ה-runner היה ב-draining.
    • הסשן חרג ממגבלת --kill-session-after-min שלו.
  • abandoned: שמור לסשן ש-runner אחר תבע. ה-hook כרגע לא מופעל במקרה הזה.

אם אתה משווה קבלות hook עם מוני מחזור החיים של הסשן, צפה שחלק מקבלות interrupted ייספרו שם כ-completed. המונים סופרים שחרור, פקיעת זמן בהפעלה, העברת שרת, וארכוב או מחיקה שה-poll של ה-runner הבחין בהם ראשון כ-completed, כי ה-runner החזיר את ה-slot בצורה נקייה.

סטטוס היציאה של ה-hook אף פעם לא משפיע על תוצאת הסשן. כשל נרשם בלוג ומתעלמים ממנו. ה-runner ממתין עד --post-session-hook-timeout-sec, 60 שניות כברירת מחדל, בכל סיום סשן כולל כיבוי ה-runner. הדוגמה הזו שומרת עבודה שלא עברה commit לענף הצלה:

#!/usr/bin/env bash
set -u
export GIT_ALLOW_PROTOCOL=${GIT_ALLOW_PROTOCOL:-https:http:ssh}
IFS=':'
# -c overrides beat repo-local settings, blocking session-written fsmonitor,
# hook-path, and gpg-program config from executing code with the hook's
# privileges. -c commit.gpgsign=false also leaves these rescue commits
# unsigned under --configure-git.
# Repo-local credential.helper and pushurl still apply, and on a runner
# before v2.1.280 so does core.sshCommand; see the note below the script
# before you give this push a credential.
g() { git -c core.fsmonitor=false -c core.hooksPath=/dev/null \
        -c commit.gpgsign=false "$@"; }
for ws in $CLAUDE_RUNNER_WORKSPACE_PATHS; do
  cd "$ws" 2>/dev/null || continue
  [ -z "$(g status --porcelain 2>/dev/null)" ] && continue
  g add -A
  g commit -q -m "runner snapshot: $CLAUDE_RUNNER_SESSION_ID ($CLAUDE_RUNNER_EXIT_REASON)" || continue
  g push -q origin "HEAD:refs/heads/rescue/$CLAUDE_RUNNER_SESSION_ID" || true
done

שורת GIT_ALLOW_PROTOCOL בסקריפט מגבילה את git ל-remotes של HTTPS, HTTP ו-SSH. אם הסביבה של ה-runner כבר מגדירה רשימת GIT_ALLOW_PROTOCOL לא ריקה משלה, הסקריפט שומר את הרשימה הזו.

ה-hook דוחף עם פרטי הגישה של git שזמינים בסביבה שלו על מארח ה-runner. תחת העמדה של בלי פרטי גישה בתמונה, כולל כשה-clone המובנה עובר דרך פרוקסי ה-git של Anthropic, אין כאלה, לכן הנפק פרטי גישה לדחיפה קצרי חיים בתוך ה-hook לפני הדחיפה: החלף את אסימון הסשן שה-hook מקבל ב-CLAUDE_CODE_SESSION_ACCESS_TOKEN עם שירות האסימונים שלך, ואמת אותו כפי שמתאר אימות זהות הסשן.

התייחס לכל פרטי גישה שה-hook שלך נותן ל-git כאל כאלה שסשן יכול להשיג, והנפק אותם כך שיוכלו לעשות לא יותר מהדחיפה הזו. Git ב-hook שלך קורא קובצי הגדרה שסשן יכול לכתוב, ו-credential helper או filter driver ששמו מופיע באחד מהם רץ עם ההרשאות של ה-hook שלך. הגדרות בקבצים האלה יכולות גם לשנות לאן דחיפה הולכת, יהיה ה-remote שאתה נוקב אשר יהיה. להגדרות ה-git שה-runner מקבע ב-hook שלך ולאלה שהוא משאיר לקבצים האלה, ראה הגדרת Git בתוך hooks של מחזור חיים.

#תזמון ה-hook כשה-runner משחרר סשן

סשן ששוחרר יכול להתחדש על runner אחר. ב-runner בגרסה v2.1.236 או חדשה יותר, מה שהסשן עשה בזמן השחרור קובע אם הוא יכול להתחדש לפני שה-hook הזה מסתיים:

  • לא פעיל אחרי תור, או שפג הזמן בהפעלה: ה-runner עוצר את תהליך הבן ומריץ את ה-hook הזה עד הסוף. רק אז הוא משחרר את הסשן. הודעת משתמש שנשלחת בזמן שה-hook רץ לא יכולה לחדש את הסשן על runner אחר לפני שה-hook מסתיים.
  • ממתין שהמשתמש יענה להנחיה, כגון הנחיית הרשאה: ה-runner משחרר את הסשן קודם, ואז מריץ את ה-hook הזה. הודעת משתמש שנשלחת בזמן שה-hook רץ יכולה לחדש את הסשן על runner אחר לפני שה-hook מסתיים.

זה חל בכל פעם שה-runner משחרר סשן: בפקיעת זמן חוסר הפעילות, בזמן --retire-at, וב-runner בגרסה v2.1.260 או חדשה יותר, במגבלת --kill-session-after-min של סשן. סשן שהתור שלו הסתיים ושמחזיק רק משימות רקע נחשב כאן ללא פעיל. לפני v2.1.236, ה-runner שחרר את הסשן קודם ואז הריץ את ה-hook הזה בשני המקרים.

במהלך drain של SIGTERM, ה-runner מחזיק את ה-lease של הסשן עד שה-hook מסתיים. ראה תזמון כיבוי.

#הגדרת Git בתוך lifecycle hooks

הוקי checkout ו-post-session רצים עם אסימון הגישה של הסשן בסביבה שלהם, וה-git שהם מריצים קורא קובצי הגדרות שסשנים יכולים לכתוב, כמו ~/.gitconfig ו-.git/config של checkout. לפני שאחד משני ההוקים רץ, ה-runner מגדיר בסביבה של ההוק הגדרות git, כולל אלה שלמטה, כזוגות GIT_CONFIG_COUNT/GIT_CONFIG_KEY_n/GIT_CONFIG_VALUE_n וכמשתני סביבה של git. Git מדרג אותם מעל כל קובץ הגדרות, והם חלים רק על ה-git שההוקים שלך מריצים, לא על ה-git של הסשן עצמו. בהפעלה, ה-runner מדפיס שורה [runner:git] lifecycle hooks: שמציגה את נתיב ההוקים, הפרוטוקולים המותרים, תוכניות gpg, ומצב החתימה שבתוקף. נדרש Claude Code v2.1.280 או חדש יותר.

  • הוקי Git: אלא אם אתה מספק ערך, core.hooksPath הוא /dev/null, כך ש-git מדלג על ההוקים ב-.git/hooks של מאגר ועל כל ספריית הוקים ש-~/.gitconfig מציינת. כדי לספק ערך, ייצא את core.hooksPath כזוג GIT_CONFIG_KEY_n/GIT_CONFIG_VALUE_n בסביבה של ה-runner. ה-runner גם קורא את core.hooksPath מהגדרות git של המערכת, ומשתמש בו רק כאשר המשתמש של ה-runner לא יכול לכתוב לקובץ הזה, לספרייה שהוא מציין, או לקובצי ההוק שבתוכה. כאשר ה-runner מתעלם מערך, שורת [runner:warn] בהפעלה מציינת את הערך ואת הסיבה.
  • מנטר מערכת קבצים: core.fsmonitor ריק, כך ש-git בהוק שלך לא מריץ תוכנית מנטר שקובץ הגדרות מציין.
  • פרוטוקולי remote: GIT_ALLOW_PROTOCOL הוא https:http:ssh. clone, fetch, או push שמשתמשים בנתיב מקומי, בכתובת file://, או בכתובת git:// נכשלים עם fatal: transport 'file' not allowed או fatal: transport 'git' not allowed.
  • פקודת SSH ובקשת credential: git בהוק שלך מתעלם מ-core.sshCommand ומ-core.askPass מקובצי הגדרות. כדי להשתמש בפקודת SSH משלך, הגדר GIT_SSH_COMMAND בסביבה של ה-runner. כדי להשתמש בתוכנית לבקשת credential, הגדר שם את GIT_ASKPASS. סשנים יורשים את הסביבה של ה-runner, כך ששני המשתנים מגיעים גם ל-git של הסשן עצמו. אל תשים credential באף אחד מהם.
  • תוכניות gpg: gpg.program, gpg.openpgp.program, gpg.x509.program, ו-gpg.ssh.program הם נתיבים שה-runner מגדיר, אף פעם לא ערכים מקובץ הגדרות.
  • חתימת commit: עם --configure-git, commits שאתה יוצר מהוק נחתמים כסשן. בלי הדגל, commit.gpgsign ו-tag.gpgsign הם false.

כדי לשנות אחת מההגדרות האלה, השתמש בסביבה של ה-runner או באפשרות git -c בתוך ההוק:

  • זוגות הגדרות: זוג GIT_CONFIG_KEY_n/GIT_CONFIG_VALUE_n שאתה מייצא בסביבה של ה-runner מחליף את הערך של ה-runner לאותו מפתח. מספר את הזוגות מ-0 והגדר את GIT_CONFIG_COUNT לכמה יש. כאשר הזוג האחרון ש-GIT_CONFIG_COUNT מכריז עליו חסר, ה-runner מתעלם מכל הזוגות שלך ורושם שורת [runner:warn] בהפעלה.
  • משתני סביבה של Git: ה-runner משאיר את GIT_ALLOW_PROTOCOL, GIT_SSH_COMMAND, ו-GIT_ASKPASS כפי שהגדרת אותם בסביבה שלו.
  • אפשרויות git -c: אפשרות git -c בתוך ההוק דורסת זוג GIT_CONFIG_KEY_n, של ה-runner או שלך. היא לא משנה את GIT_ALLOW_PROTOCOL, GIT_SSH_COMMAND, או GIT_ASKPASS, ש-git קורא לפני כל הגדרה.

git בהוק שלך עדיין קורא כל הגדרה שה-runner לא מגדיר, כמו credential helpers, שכתובים url.*.insteadOf, ו-filter drivers, מכל קובץ הגדרות, כולל אלה שסשנים יכולים לכתוב. credential helper או filter driver שמצוין באחד מהקבצים האלה רץ כתוכנית עם ההרשאות של ההוק שלך, והגדרות בקבצים האלה עדיין יכולות לשנות לאן push מההוק שלך הולך, כולל push לכתובת שאתה מעביר בשורת הפקודה.

לפני v2.1.280, ה-runner לא הגדיר אף אחת מההגדרות האלה, ותחת --configure-git commit שנוצר מהוק נכשל אלא אם ההוק העביר -c commit.gpgsign=false.

#command

רץ אחרי checkout בכל פעם שסשן מתחיל או מתחדש, במקום ה-child spawn המובנה. ההוק מקבל את אותה סביבה כמו סקריפט wrapper וצריך לבצע exec אל "$CLAUDE_RUNNER_CLAUDE_BIN" באותו אופן. השתמש בהוק command כדי לשמור את כל ההתאמה בספריית הוקים אחת. השתמש ב---exec-path כאשר ה-wrapper נמצא במקום אחר. אם גם --exec-path מוגדר, הדגל גובר והוק command לא מופעל.

תמיד בצע exec לבינארי של ה-runner עצמו ולא ל-claude שנפתר דרך PATH. אחרת אתה מבטל את נעיצת הגרסה.

#runners לפי דרישה

במקום להריץ צי קבוע, אפשר להפעיל runner אחד לכל סשן. ה-orchestrator הוא תת פקודה נפרדת וחסרת מצב שעושה poll מול Anthropic לבקשות spawn, אחת לכל סשן שנמצא בתור ואין לו runner זמין, ומריץ את הוק spawn-runner שלך לכל אחת. ההוק שלך שולח עומס עבודה לפלטפורמה שלך: Kubernetes Job, instance של EC2, Nomad dispatch.

runners לפי דרישה משפרים את היגיינת ה-credentials. בצי קבוע, סוד הסביבה נמצא על כל מארח runner, וזה אותו מארח שמריץ סשנים של משתמשים. עם ה-orchestrator, סוד הסביבה נשאר רק על מארח ה-orchestrator, שלעולם לא מריץ קוד של משתמש. כל runner שנוצר מקבל הוראת עבודה לשימוש חד פעמי שרושמת בדיוק runner אחד ואז פגה.

כדי להפעיל את ה-orchestrator, העבר את סוד הסביבה וספריית הוקים שמכילה סקריפט spawn-runner בר הרצה:

claude self-hosted-runner orchestrator \
  --environment-secret-file /etc/claude/environment-secret \
  --hooks-dir /etc/claude/hooks

ה-orchestrator לא שומר מצב בין סבבי poll, כך שאפשר להריץ שני replicas או יותר מול אותה סביבה לזמינות. כל בקשת spawn נתפסת בצד השרת על ידי replica אחד בדיוק. כל ה-replicas חייבים להשתמש באותו ערך --expected-spawn-seconds. ראה את חוזה ההוק.

#הוק spawn-runner

ה-orchestrator מריץ את ${hooks-dir}/spawn-runner פעם אחת לכל בקשת spawn. ההוק חייב לשלוח עבודה באופן אסינכרוני, בלי להמתין שה-runner יעלה, ולחזור בתוך --hook-timeout, 60 שניות כברירת מחדל. ההוק מקבל:

משתנהתיאור
CLAUDE_RUNNER_WORK_ORDER_FILEנתיב לקובץ זמני שמכיל את ה-JWT החתום של הוראת העבודה, שאיתו ה-runner החדש נרשם. נמחק אחרי שההוק יוצא. אל תרשום בלוג את תוכן הקובץ.
CLAUDE_RUNNER_ORDER_IDמפתח idempotency אטום, ייחודי לכל בקשת spawn ובטוח לשמות משאבים ב-Kubernetes. השתמש רק במזהה ההזמנה כמפתח ה-dedup של ה-provisioner שלך.
CLAUDE_RUNNER_SESSION_IDהסשן שבקשה זו מיועדת לו, כמזהה session_... בכתובת של הסשן. הוא חוזר על עצמו בכל בקשה חוזרת לסשן, אז השתמש בו לרישום ולניתוב, לא כמפתח dedup. ריק לבקשות pre-warming, שמפעילות runner standby לפני סשן מסוים כאשר --min-idle מוגדר, אז אל תניח שהמשתנה מוגדר.
CLAUDE_RUNNER_SESSION_UUIDאותו מזהה סשן בצורת UUID קנונית. ריק לבקשות pre-warming.
CLAUDE_RUNNER_ATTEMPTמונה לכל סשן לשימוש ברישום. הוא אינו ספירת retry ואינו ספירת בקשות. 0 לבקשות pre-warming, אם כי בקשה לסשן יכולה לשאת 0 גם כן.
CLAUDE_RUNNER_ORDER_SERVER_TIMEזמן שרת מכותרת ה-HTTP Date של תשובת ה-poll. כאשר ההוק מאמת את exp של JWT הוראת העבודה, השווה לערך הזה במקום לשעון המקומי כדי לסבול סטייה. ריק כאשר ה-gateway השמיט את הכותרת.
CLAUDE_RUNNER_POOL_IDהמזהה של הסביבה שה-runner החדש צריך להצטרף אליה, בצורה ccpool_...
CLAUDE_RUNNER_ACCOUNT_IDמזהה מתויג של החשבון שהכניס את הסשן לתור. השתמש בו לניתוב לפי חשבון, מכסה, או chargeback, לא כמפתח dedup. ריק כאשר אינו זמין.
CLAUDE_RUNNER_ACCOUNT_EMAILכתובת הדוא"ל של החשבון שהכניס את הסשן לתור. ריקה כאשר אינה זמינה. התייחס לדוא"ל כמידע מזהה אישית ואל תרשום אותו בלוג.
CLAUDE_RUNNER_PRIMARY_REPO_URLכתובת מקור ה-git הראשון של הסשן, לניתוב אל runner שבו המאגר הזה כבר חומם מראש. ריקה כאשר לסשן אין מקורות git.
CLAUDE_RUNNER_PRIMARY_REPO_REVISIONrevision של מקור ה-git הראשון של הסשן: branch, SHA, tag, או שם reference מלא. ריקה כאשר לא צוין.
CLAUDE_RUNNER_REPO_SOURCESמערך JSON של {url, revision} לכל מקורות ה-git של הסשן, להוקים שמנתבים לפי מאגר משני. ריק כאשר אין מקורות.
CLAUDE_RUNNER_CORRELATION_IDמזהה ה-correlation שסופק ביצירת הסשן, ומוחזר כדי שההוק יוכל למפות את הוראת העבודה הזו לבקשה שיצרה את הסשן. ריק כאשר לסשן אין כזה.
CLAUDE_RUNNER_CLIENT_PLATFORMמשטח הלקוח שיצר את הסשן, כמו web_claude_ai, desktop_app, ios, או scheduled_trigger, לניתוח אימוץ. לא מוגדר כאשר לסשן אין משטח שנרשם או שזוהה, ולבקשות pre-warming. בדוק אותו עם [ -n "${CLAUDE_RUNNER_CLIENT_PLATFORM:-}" ], שנשאר בטוח תחת set -u.

ה-runner שנוצר נרשם עם הוראת העבודה במקום סוד הסביבה:

  • הפעל אותו עם הוראת העבודה: כוון את --environment-secret-file לקובץ שמכיל את JWT הוראת העבודה, או הגדר את SELF_HOSTED_RUNNER_ENVIRONMENT_SECRET לערך ה-JWT.
  • העתק את ה-JWT לפני שההוק יוצא: ה-orchestrator מוחק את קובץ הוראת העבודה אחרי שההוק יוצא, אז העתק את ה-JWT לתוך עומס העבודה שאתה שולח, כמו Kubernetes Secret על ה-Job שנוצר, במקום להעביר את נתיב הקובץ הלאה.
  • השתמש ב---capacity 1 על runners שנוצרו: הוראת עבודה הקשורה לסשן רושמת בדיוק runner אחד הקשור לסשן הזה, כך שקיבולת גבוהה יותר מוסיפה משבצות שלעולם לא מקבלות עבודה, וה-runner רושם אזהרה בהפעלה.
  • הוראות עבודה של pre-warming נרשמות בלי קישור: ה-runner standby אינו קשור לסשן ותופס עבודה מהתור כמו runner בצי קבוע.

לחוזה יש ארבעה כללים, בכל פלטפורמה שההוק שלך מקצה עליה:

  1. היה idempotent על CLAUDE_RUNNER_ORDER_ID. מסירה חוזרת של אותה בקשה חייבת ליצור לכל היותר runner אחד. גזור שם משאב דטרמיניסטי ממזהה ההזמנה ותן לפלטפורמה שלך לדחות את הכפילות. אל תבסס מפתח על CLAUDE_RUNNER_SESSION_ID במקום זאת. כל בקשה חוזרת לסשן נושאת את אותו מזהה סשן עם מזהה הזמנה חדש. עומס עבודה שנקרא או שעובר dedup לפי מזהה הסשן לא נוצר שוב כל עוד הראשון קיים.

  2. אל תנסה שוב את עומס העבודה. מזהה הזמנה אחד אומר לכל היותר עומס עבודה אחד שנוצר. אם ה-runner אף פעם לא נרשם, Anthropic מבקשת שוב עם מזהה הזמנה חדש אחרי --expected-spawn-seconds.

  3. השתמש בחוזה קודי היציאה. צא עם הסטטוס שמתאים לתוצאה:

    • יציאה 0: נשלח.
    • יציאה 1, או עדיין רץ כאשר --hook-timeout מסתיים: כשל שניתן לנסות שוב. הסשן מבצע backoff ומוצע שוב. אחרי 10 בקשות שנכשלו ברצף, הוא נחסם כפי שהוא נחסם אחרי יציאה 2 או גבוהה יותר.
    • יציאה 2 או גבוהה יותר: כשל שלא ניתן לנסות שוב. הסשן נחסם מ-spawn נוסף עד שמשתמש שולח לו הודעה חדשה או שOwner בוחר Retry עליו בלשונית Activity של הסביבה.

    בזמן שסשן מבצע backoff, הוא מציג Queued בלשונית Activity בלי שגיאת spawn, אז קרא את הפלט של ההוק בלוג של ה-orchestrator. אחרי חסימה, הסשן מציג Failed. הרחב אותו כדי לקרוא את שגיאת ה-spawn, שנלקחת מ-512 התווים האחרונים של stderr של ההוק. כתוב ל-stderr שגיאה שאפשר לפעול לפיה, ובלי סודות. בהוק shell, שמור כשלים חולפים כניתנים לניסיון חוזר.

    לבקשת pre-warming אין סשן שיכול להיכשל: ה-orchestrator רושם יציאה שאינה אפס רק מקומית, והשרת מבקש את ה-spawn שוב אחרי שחכירת --expected-spawn-seconds פגה.

  4. הגדר את --expected-spawn-seconds לפחות לזמן ה-p99 שלך מבקשת spawn עד רישום ה-runner. מדוד מהרגע שה-orchestrator מקבל את בקשת ה-spawn, וכלול גם כל המתנה לקיבולת בפלטפורמה שלך וגם את זמן העלייה. הערך הזה הוא החכירה בצד השרת, והוראת העבודה פגה איתו, כך ש-runner שעומס העבודה שלו נמשך יותר זמן לא יכול להירשם. כל replicas של ה-orchestrator חייבים להשתמש באותו ערך.

כל מה שההוק כותב ל-stdout או ל-stderr מופיע בלוג של ה-orchestrator כש-credentials מצונזרים אוטומטית. אם סשנים נשארים בתור, בדוק את גוף /healthz של ה-orchestrator לספירות תור, ואז פתח את לשונית Activity של הסביבה שלך בדף הניהול Cloud environments: הרחב שם סשן שנכשל כדי לראות את שגיאת ה-spawn שלו, ובחר Retry כדי לבקש אותו שוב.

#תיקון סשנים שנפתחו מחדש שנשארים Queued או מציגים Failed

אם סשנים שנפתחו מחדש נשארים Queued או מציגים Failed, או שההוק שלך מגיע ל-timeout עליהם, סיבה אחת היא הוק שנותן שמות לעומסי עבודה או עושה להם dedup לפי מזהה הסשן. סשן נפתח מחדש כאשר המשתמש שלו שולח לו הודעה אחרי שה-runner שלו יצא. לדוגמה, קח הוק שבונה את השם של כל עומס עבודה כך:

name="runner-${CLAUDE_RUNNER_SESSION_ID}"
  1. בבקשת ה-spawn הראשונה של הסשן, ההוק יוצר עומס עבודה בשם הזה, ו-runner מתחיל
  2. ה-runner יוצא, לדוגמה אחרי --release-idle-session-min, והמשתמש פותח מחדש את הסשן מאוחר יותר
  3. בקשת ה-spawn הבאה של הסשן נושאת את אותו מזהה סשן ומזהה הזמנה חדש, כך שההוק בונה את אותו שם
  4. אם הפלטפורמה שלך עדיין מחזיקה את עומס העבודה מהבקשה הראשונה, כי שום דבר לא הסיר אותו כשה-runner שלו יצא, ההוק לא יוצר עומס עבודה חדש ואף runner לא מתחיל
  5. כל בקשת spawn מאוחרת יותר לסשן נגמרת באותו אופן

פתח את ההוק שלך וקרא מה הוא עושה כאשר עומס העבודה כבר קיים, לדוגמה כאשר הפלטפורמה שלך דוחה את השם הכפול. מצא את ההתנהגות הזו בעמודה הראשונה כדי לראות מה הסשן שנפתח מחדש מציג בלשונית Activity:

כאשר עומס העבודה קיים, ההוק שלךQueued משתנה ל-Failedשגיאת spawn
יוצא 0 בלי ליצור דבראחרי 10 בקשות כאלה ברצףrunner did not register within ETA after max attempts
יוצא 1אחרי 10 בקשות כאלה ברצףזנב stderr של ההוק שלך, או exit 1 אם לא כתב דבר ל-stderr
יוצא 2 או גבוה יותרמידזנב stderr של ההוק שלך, או exit והקוד, כמו exit 2, אם לא כתב דבר ל-stderr
ממתין עד ש---hook-timeout מסתייםאחרי 10 בקשות כאלה ברצףזנב stderr של ההוק שלך, או spawn-runner hook timed out after 60000ms אם לא כתב דבר ל-stderr

בשגיאת spawn של השורה האחרונה, 60000 הוא ה---hook-timeout שלך באלפיות שנייה. כאשר הוק ממתין עד ש---hook-timeout מסתיים, לוג ה-orchestrator מקבל גם שורה timed out after.

כדי לתקן את ההוק, בנה כל שם או מפתח מ-CLAUDE_RUNNER_ORDER_ID, כפי שכלל 1 של חוזה ההוק דורש. בדוגמה, השם הופך ל:

name="runner-${CLAUDE_RUNNER_ORDER_ID}"

אם ההוק שלך ממתין, גם גרום לו לצאת ברגע שהוא שלח את עומס העבודה, כי הוק שממתין מעכב את ההוקים הבאים של ה-orchestrator שלו. אחרי שאתה משנה את ההוק, בדוק את הסטטוס של כל סשן תקוע בלשונית Activity:

  • Queued: הסשן מקבל את בקשת ה-spawn הבאה שלו מעצמו
  • Failed: בחר Retry עליו כ-Owner, או שמשתמש שלו ישלח לו הודעה חדשה

#גרום ל-runners לפי דרישה לצאת כשהם בטלים

כברירת מחדל, runner שההוק שלך מפעיל לא יוצא כאשר ה-turn של הסשן שלו מסתיים, כך שעומס העבודה ששלחת ממשיך לרוץ. כדי לגרום ל-runners לצאת, ההוק שלך צריך להעביר את דגלי ה-runner האלה:

דגל, עם ערך לדוגמההעבר אותו אלמה הוא עושה
--release-idle-session-min 30כל runner שההוק שלך מפעילה-runner משחרר את הסשן שלו אחרי אותו מספר דקות בטלות ואז יוצא 0. הסשן מתחדש כאשר המשתמש שלו שולח את ההודעה הבאה, אבל עבודה שהוא לא דחף אבדה. החזק סשן לתהליכים שהוא השאיר רצים.
--kill-session-after-min 480כל runner שההוק שלך מפעילמגביל כל סשן לאותו מספר דקות, כגיבוי לסשנים שלא נחשבים בטלים, אז הגדר אותו מעל הסשן הארוך ביותר שאתה מצפה לו
--exit-if-unused-min 60runners standby, שההוק שלך מפעיל לבקשות pre-warmingstandby שלעולם לא לוקח סשן יוצא אחרי אותו מספר דקות. אם אתה מגדיר --min-idle, אתה מקבל תחליף אלא אם ה-standby היה עודף, אז השתמש בערך גבוה בהרבה מזמן העלייה שלך.

הסר את עומס העבודה כאשר ה-runner יוצא, לדוגמה על ידי הגדרת ttlSecondsAfterFinished על Kubernetes Job או על ידי כך שסקריפט ההפעלה של ה-VM מוחק את ה-VM אחר כך.

#שליחת הפלט של תהליך רקע לקובץ

אם ה-hook spawn-runner משאיר תהליך רץ ברקע, שלחו את הפלט של התהליך לקובץ שהמשתמש של ה-hook יכול ליצור. לדוגמה, הוסיפו >> "$HOME/claude-runner.log" 2>&1 לפקודה שמפעילה את התהליך, לפני כל &. שורת ה-hook הזו מראה לאן ההפניה הולכת, כאשר your-background-command מייצג את הפקודה שה-hook שולח לרקע:

your-background-command >> "$HOME/claude-runner.log" 2>&1 &

בלי ההפניה, התהליך כותב ל-stdout ול-stderr של ה-hook:

  • בערך שנייה אחרי שה-hook יוצא, ה-orchestrator רושם בלוג exited, but a process it started still has the hook's stdout or stderr open. מהשורה הזו ואילך, הוא רושם עד 64 KB נוספים מהפלט הזה תחת [runner:hook:spawn-runner:after-exit jti=<order id>] ואז משמיט את השאר.
  • ברגע שה-orchestrator יוצא, הכתיבות של התהליך לשם נכשלות.

runner שה-hook מפעיל בעצמו מריץ סשנים על המארח של ה-orchestrator, שמחזיק את סוד הסביבה.

לפני v2.1.305, hook שכבר יצא עדיין הגיע ל---hook-timeout בזמן שתהליך שהוא הפעיל החזיק את stdout או stderr של ה-hook פתוחים, כפי שמוצג:

  • לשונית Activity, בשגיאת spawn של סשן שנכשל: spawn-runner hook timed out after 60000ms, או השורות האחרונות ב-stderr של ה-hook.
  • לוג ה-orchestrator: timed out after 60000ms, sending SIGTERM to process tree.

#יציאה מה-hook בלי להמתין ל-runner

אם כל runner לפי דרישה נעצר בערך 60 שניות אחרי שה-hook שלו מתחיל, וה-hook רץ שוב לאותו סשן, בדקו אם ה-hook ממתין ל-runner. 60 השניות הן ברירת המחדל של --hook-timeout, כך שאם הגדרתם ערך משלכם, runners נעצרים זמן זה אחרי שה-hook מתחיל.

hook שמפעיל claude self-hosted-runner על המארח של ה-orchestrator וממתין לו, בחזית או עם wait, עדיין רץ כש---hook-timeout מסתיים. ה-orchestrator אז עוצר את ה-hook ואת התהליכים שרצים תחתיו, כולל ה-runner הזה ותהליך Claude של הסשן, כך שתור שעדיין רץ נקטע.

  • כדי לאשר שה-timeout הוא מה שעוצר את ה-runners: חפשו בלוג של ה-orchestrator את timed out after 60000ms, sending SIGTERM to process tree בכל בקשת spawn. ה-60000 הוא ה---hook-timeout שלכם באלפיות שנייה.
  • כדי לתקן: גרמו ל-hook לשלוח את ה-runner לפלטפורמה שלכם ולצאת עם 0. אם במקום זה תגדילו את --hook-timeout, ה-runner נעצר רק מאוחר יותר.

#שמירה על כשלים חולפים כניתנים לניסיון חוזר ב-hook של shell

ב-hook של shell שמשתמש ב-set -e, כשל שניסיון חוזר היה יכול לנקות יכול לחסום את הסשן. ה-hook נעצר בפקודה שנכשלת ויוצא עם הסטטוס של הפקודה עצמה, וה-orchestrator מחיל את חוזה קוד היציאה על הסטטוס הזה. כשלים רבים מחזירים סטטוס של 2 או יותר, כמו 127 כשפקודה לא מותקנת ו-22 מ-curl --fail על שגיאת HTTP, כך שהם חוסמים את הסשן בכשל הראשון שלו.

סשן שה-hook כבר חסם נשאר חסום עד שמשתמש שולח לו הודעה חדשה או ש-Owner בוחר Retry עליו בלשונית Activity של הסביבה.

כדי להפוך כשל כזה ליציאה 1, שימו את השורות האלה ישירות מתחת לשורת #! של ה-hook, מעל כל דבר שיכול להיכשל:

set -e
PERMANENT=; permanent() { printf '%s\n' "$*" >&2; PERMANENT=1; exit 2; }
trap 'rc=$?; [ "$rc" -eq 0 ] || [ -n "${PERMANENT:-}" ] || exit 1' EXIT

השורות האלה משנות את אופן ההתנהגות של שאר ה-hook, אז בדקו אותו לכל אחד מהדפוסים האלה אחרי שתוסיפו אותן:

  • exit 2 חשוף או גבוה יותר: כשה-trap מוגדר, הוא הופך ליציאה 1. לשגיאה ששום ניסיון חוזר לא יכול לתקן, קראו ל-permanent עם הסיבה במקום, כמו permanent "namespace claude-runners does not exist". קראו לו ב-shell הראשי, לא בתוך $( ), ( ), או pipe.

  • exec: אל תפעילו את הפקודה האחרונה של ה-hook עם exec, כי exec מחליף את ה-shell וה-trap לא רץ.

  • trap שני של EXIT: trap ... EXIT שני מחליף את הראשון, אז מזגו את השניים ל-trap יחיד. שימו את פקודות הניקוי שלכם ישירות אחרי rc=$?; וסיימו כל אחת ב-|| true;. הניקוי אז רץ גם בכשל וגם בהצלחה, ופקודת ניקוי שנכשלת לא קובעת את סטטוס היציאה של ה-hook. ה-trap הממוזג הזה מראה את הצורה, כאשר your-cleanup-command מייצג את הפקודה שלכם:

    trap 'rc=$?; your-cleanup-command || true; [ "$rc" -eq 0 ] || [ -n "${PERMANENT:-}" ] || exit 1' EXIT
  • פקודות שמותר להן להיכשל: אם ה-hook לא השתמש ב-set -e קודם, הוא עכשיו נעצר בפקודה הראשונה שמחזירה ערך שאינו אפס, כמו חיפוש שלא מוצא כלום. אם ה-hook פועל לפי התוצאה, הפכו את הפקודה הזו לתנאי של if. אם הוא מתעלם מהתוצאה, הוסיפו אחרי הפקודה || true.

  • הפקודה ששולחת את עומס העבודה: אל תוסיפו אחריה || true. עם || true, ה-hook יוצא 0 גם כשהפלטפורמה שלכם מסרבת לשליחה, למשל בגלל חריגה ממכסה, והסשן ממתין ל-runner שמעולם לא הופעל.

    תנו שם לעומס העבודה לפי CLAUDE_RUNNER_ORDER_ID, כמו שהכלל הראשון של ה-hook אומר. תשובת already-exists היא אז מסירה חוזרת של אותה בקשה, אז התייחסו לתשובה הזו לבדה כהצלחה. לכל דבר אחר, הדפיסו את התשובה ל-stderr וצאו עם 1, או קראו ל-permanent אם שום ניסיון חוזר לא יכול לתקן. הדוגמה הזו עושה זאת עבור kubectl create, עם job.yaml שמחזיק רק את עומס העבודה:

    if ! out=$(kubectl create -f job.yaml 2>&1); then
      case "$out" in
        *AlreadyExists*) ;;
        *) printf '%s\n' "${out:-kubectl create failed}" >&2; exit 1 ;;
      esac
    fi

    הלוג של ה-orchestrator אז מציג את ההודעה הזו. אם ה-hook יוצר אובייקטים אחרים, תנו לכל אחד שם לפי מזהה ההזמנה, תנו לו פקודה משלו, והתייחסו לתשובת already-exists שלו כהצלחה באותו אופן. לאובייקט שמחזיק סוד, הדפיסו הודעה קבועה במקום הפלט של הפקודה.

כדי לאשר שה-trap עובד, הוסיפו שורה ישירות מתחת לשורת ה-trap שקוראת לפקודה שלא קיימת, כמו no-such-command. הריצו את קובץ ה-hook מה-shell שלכם ובדקו ש-echo $? מדפיס 1, ואז הסירו את השורה.

#בירור למה נשלחה בקשת spawn

ה-hook מקבל את הסיבה לכל בקשת spawn ב-CLAUDE_RUNNER_SPAWN_REASON, כך שאפשר לרשום למה ה-hook רץ, יחד עם CLAUDE_RUNNER_SESSION_UUID. דורש Claude Code v2.1.318 או מאוחר יותר על ה-orchestrator. קראו אותו כ-"${CLAUDE_RUNNER_SPAWN_REASON:-}", כי הוא יכול להיות לא מוגדר, והפעילו את ה-runner יהיה הערך אשר יהיה. אם ה-hook מסתעף לפי הערך, שמרו על ענף ברירת מחדל, כי Anthropic יכולה להוסיף ערכים.

כדי למצוא את הסיבה לבקשות קודמות, חפשו בלוג של כל replica של ה-orchestrator את session= ואת CLAUDE_RUNNER_SESSION_UUID של הסשן, לא את מזהה session_... שלו. שורות ok session= ו-nack session= מציגות את הערך של CLAUDE_RUNNER_SPAWN_REASON אחרי reason=, או reason=- אם הוא לא היה מוגדר:

  • [runner:orchestrator] ok session=: ה-hook יצא עם 0
  • [runner:orchestrator] nack session=: ה-orchestrator מדווח ל-Anthropic על בקשת spawn שנכשלה

כל ערך נותן רק את הסיבה האחרונה ש-Anthropic רשמה לסשן, לא את ההיסטוריה שלו:

ערךמה קרהמה לבדוק
session_createdזו בקשת ה-spawn הראשונה לסשן חדשכלום. אל תסתמכו עליו כדי לזהות את הבקשה הראשונה של סשן, כי הבקשה הראשונה שה-hook רואה יכולה לשאת runner_lost אחרי ש-runner מסוג fixed-fleet או standby החזיק את הסשן.
no_runner_registeredאף runner לא נרשם לבקשה הקודמת בתוך --expected-spawn-seconds, ו-Anthropic לא רשמה דוח כשל עבורהstop: no nack for session= או nack failed session= בין השורות הקודמות של הסשן אומר שאף דוח כשל לא נרשם. קראו כל שגיאת hook שלפניו, ואם ה-orchestrator היה נעצר, ראו עצירה או הפעלה מחדש של ה-orchestrator. אחרי שורת ok session=, בדקו שה-runner שלו הופעל ונרשם בתוך --expected-spawn-seconds. אם אף replica לא רשמה את הבקשה, בדקו את ה-orchestrators שלכם להפעלות מחדש ולשורות poll failed.
hook_failedה-orchestrator שלכם דיווח שהבקשה הקודמת נכשלה ואפשר לנסות אותה שוב, למשל כי ה-hook יצא עם 1 או עדיין רץ כש---hook-timeout הסתיים. הערך זהה כשה-orchestrator מחזיר בקשה ל-Anthropic בלי להריץ את ה-hook, למשל בזמן שה-orchestrator נעצר.מצאו את שורת [runner:orchestrator] nack session= או stop: handing back session= האחרונה של הסשן לפני הבקשה הזו. שגיאת ה-spawn שה-orchestrator שלח באה אחרי ערך reason= ויכולה להימשך לשורות הבאות. שורת handing back session= אומרת שה-orchestrator היה נעצר ולא הריץ את ה-hook, כך של-hook אין מה לתקן.
runner_lostה-runner שהחזיק את הסשן הפסיק לדווח ל-Anthropic, ביטל את הרישום, או נמחקמצאו מה עצר את ה-runner או ניתק אותו מ-Anthropic, כמו פינוי node, הריגה בגלל חוסר זיכרון, spot reclaim, או rollout או scale-down משלכם
runner_reported_failureה-runner הקודם דיווח שהסשן נכשל עליו, למשל במהלך checkoutקראו את הלוג של ה-runner הקודם. אם סשנים רבים מקבלים את הערך הזה, בדקו את ה-image ואת הגדרת המאגר.
resumedהסשן הושהה ועכשיו צריך runner שוב. לדוגמה, ה-runner שלו שחרר אותו תחת --release-idle-session-min, ומאוחר יותר הוא קיבל הודעה חדשה.כלום
requeuedהסשן הוחזר לתור, למשל כש-Owner בחר Retry או שסשן חסום קיבל הודעה חדשהאם אותו סשן ממשיך לקבל את הערך הזה, קראו את השגיאה בשורות nack session= שלו. אם ה-hook שלו יצא עם 0, קראו את הלוג של ה-runner.
server_retryAnthropic ויתרה על הבקשה הקודמת או על הפעלת הסשן על ה-runner שלו, ומנסה שוב. ה-hook שלכם לא נכשל.כלום לבקשה יחידה. אם הערך ממשיך להופיע, על סשן אחד או על פני רבים, דווחו על כך ל-צוות החשבון שלכם ב-Anthropic.
prewarmזו בקשת pre-warming, ועדיין אין סשןכלום

#עצירה או הפעלה מחדש של ה-orchestrator

כדי לעצור או להפעיל מחדש את ה-orchestrator בלי לקטוע hooks של spawn-runner שרצים, הפעילו אותו עם --shutdown-drain-sec. ב-SIGTERM הוא אז נותן ל-hooks שרצים עד למספר השניות שהגדרתם כדי לסיים. כדי לבחור את הערך ולהתאים את מפקח התהליכים, ראו גודל --shutdown-drain-sec וזמן העצירה של ה-orchestrator.

פקודת ההפעלה הזו מאפשרת 70 שניות:

claude self-hosted-runner orchestrator \
  --environment-secret-file /etc/claude/environment-secret \
  --hooks-dir /etc/claude/hooks \
  --shutdown-drain-sec 70

ה-orchestrator יוצא ברגע שה-poll שכבר נשלח וכל ה-hooks הסתיימו. כששניות --shutdown-drain-sec נגמרות, hooks שעדיין רצים מקבלים SIGTERM וה-orchestrator יוצא. SIGTERM שני או כל SIGINT עושים אותו דבר בלי להמתין. שורת הלוג שמכילה [runner:orchestrator] stop summary: סופרת hooks ובקשות spawn לפי האופן שבו כל אחד הסתיים.

בלי --shutdown-drain-sec, ה-orchestrator נעצר מיד ב-SIGTERM או ב-SIGINT. כל hook שרץ מקבל SIGTERM, ול-Anthropic לא נאמר דבר. סשן שה-runner שלו מעולם לא הופעל יכול אז להישאר בתור עד ש-Anthropic מבקשת אותו מחדש, וזה קורה אחרי ש---expected-spawn-seconds חלף, 120 שניות כברירת מחדל.

[!NOTE]

--shutdown-drain-sec דורש Claude Code v2.1.299 או מאוחר יותר. גרסה ישנה יותר יוצאת בהפעלה עם error: unknown flag --shutdown-drain-sec.

#גודל --shutdown-drain-sec וזמן העצירה של ה-orchestrator

בהגדרות ברירת המחדל, השתמשו ב---shutdown-drain-sec 70 עם זמן עצירה של 85 שניות, כלומר terminationGracePeriodSeconds: 85 ב-Kubernetes או TimeoutStopSec=85 תחת systemd. אם שיניתם את --hook-timeout, הגדילו את שניהם:

  • --shutdown-drain-sec: לפחות --hook-timeout ועוד 10 שניות, כדי ש-hook שהתחיל ממש לפני SIGTERM עדיין יוכל לסיים
  • זמן עצירה: לפחות --shutdown-drain-sec ועוד 15 שניות. ה-orchestrator רושם את המספר הזה בהפעלה: חפשו את השורה שמכילה shutdown drain:.

גרמו למפקח התהליכים לשלוח SIGTERM יחיד, לתהליך ה-orchestrator בלבד, כי SIGTERM שני מקצר את ההמתנה של --shutdown-drain-sec. אם משאירים את ברירת המחדל של systemd KillMode=control-group, גם ה-hooks מקבלים SIGTERM, ו-hook יכול למות לפני שהוא שולח את עומס העבודה שלו. לפני שמשנים את KillMode, בדקו איך ה-hook spawn-runner מפעיל runners:

  • אם ה-hook שולח את עומס העבודה לפלטפורמה ויוצא: הריצו את ה-orchestrator כתהליך הראשי של ה-unit והגדירו KillMode=mixed, ששולח SIGTERM לתהליך הזה בלבד.
  • אם ה-hook מפעיל runners כתהליכים ב-unit של ה-orchestrator עצמו: KillMode=mixed לא שולח ל-runners האלה SIGTERM. במקום זה systemd שולח להם מאוחר יותר SIGKILL, כך שהם יוצאים בלי drain של הסשנים שלהם ובלי להריץ את ה-hook post-session שלהם.

#שליחת בקשות מודל ל-Bedrock או ל-Agent Platform

אם הארגון שלך צריך שבקשות המודל יעברו דרך חשבון AWS או Google Cloud שלו, הגדר את ה-runner עבור Amazon Bedrock או עבור Agent Platform של Google Cloud, לשעבר Vertex AI. כל סשן שה-runner הזה מתחיל קורא אז למודל בחשבון הענן שלך, עם אישורי הענן שלך. בלי ההגדרה הזו, סשנים שולחים בקשות מודל אל Anthropic API.

ה-runner עדיין פונה ל-Anthropic בסבב בדיקה כדי לקבל סשנים, וכל סשן עדיין שולח את זרם האירועים שלו אל api.anthropic.com. זרם האירועים נושא הנחיות, תשובות ותוצאות כלים. דרישת התוכנית וההחרגה של Zero Data Retention בזמינות ומגבלות עדיין חלות.

סשנים מנותבים לסביבה ולא ל-runner, וסשן שהוחזר לתור או שחודש יכול לרוץ על runner אחר. הגדר כל runner בסביבה באותו אופן. לפני שאתה מתחיל, קרא מה שונה אצל ספקים אלה.

1. הכן את חשבון הענן ואת כללי ה-egress

הגדר גישה למודל, מדיניות או תפקיד בהיקף צר, וגישת רשת:

  • Amazon Bedrock: שלח פרטי תרחיש שימוש, ואז צור את המדיניות בהגדרת IAM, תוך הגבלת bedrock:InvokeModel ו-bedrock:InvokeModelWithResponseStream לפרופילי ההסקה שהסשנים שלך משתמשים בהם ולמודלי היסוד שמאחוריהם
  • Agent Platform: הפעל את ה-API ובקש גישה למודל, ואז צור את התפקיד המותאם שהגדרת IAM מתארת, עם aiplatform.endpoints.predict בלבד
  • Egress: אפשר את נקודות הקצה של הספק שלך דרך כללי ה-egress. ראה דרישות רשת. אם סשנים לא מצליחים להגיע אליהן, Claude Code יכול להמשיך לנסות שוב במשך שעות לפני שהסשן מציג שגיאה.

2. תן לסשנים אישורים בהיקף צר

צרף את המדיניות או התפקיד משלב 1 לזהות שלא יכולה לעשות שום דבר אחר. לשיטות ש-Claude Code מקבל, ראה הגדרת אישורי AWS והגדרת אישורי GCP.

אזהרה

כל מי שיכול להביא להרצת קוד בסשן, כולל באמצעות הזרקת הנחיות, יכול להשתמש באישורים האלה על חשבונך כל עוד האישורים תקפים. Claude Code רץ בתוך הסשן, ולכן האישור שבאמצעותו הוא קורא למודל חייב להיות קריא שם.

פקודות המעטפת ש-Claude מריץ, ה-hooks של Claude Code שלך, ושרתי MCP מסוג stdio יורשים את סביבת הסשן ורצים כאותו משתמש כמו Claude Code. כתוצאה מכך, הם יכולים לקרוא משתני אישורים וקבצי אישורים.

כשאתה מקשיח את הפריסה, אתה שומר אישורי מארח מחוץ לסשנים, אבל את האישור הזה אי אפשר להשאיר בחוץ. תן לזהות שמאחוריו רק את המדיניות או התפקיד משלב 1, בלי שום דבר מעבר לזה.

בדוק את השיטה שאתה בוחר מול התנהגויות ה-runner האלה:

  • נקודת קצה של מטא-נתונים: אם אתה חוסם מסשנים את נקודת הקצה של מטא-נתוני הענן לחלוטין, אישורים שמגיעים ממנה, כמו instance profile, לא מגיעים ל-Claude Code. זהות web מבוססת קובץ, כמו IAM Roles for Service Accounts (IRSA) ב-Amazon EKS או קובץ אישור של Workload Identity Federation, לא תלויה בה.
  • חידוש: סשן יכול להימשך מעבר לתוקף של אישור, לכן השתמש בשיטה שמחדשת את עצמה, כמו זהות web מבוססת קובץ
  • סקריפט עטיפה: ערכי אישורים שסקריפט העטיפה שלך מייצא לא מתחדשים בזמן ש-Claude Code רץ, ולכן לסשן ארוך השתמש בשיטה שמחדשת את עצמה. Claude Code קורא אישורי AWS מהסביבה שלו, כך שאם סקריפט העטיפה שלך כבר מייצא אישורי AWS לעבודה אחרת, Claude Code יכול לחתום באמצעותם על בקשות מודל.

3. הגדר את המשתנים של ספק אחד בסביבת ה-runner

הגדר בדיוק את המשתנים של ספק אחד במקום שבו אתה מגדיר את משתני הסביבה האחרים של ה-runner, כמו מפרט הקונטיינר או יחידת השירות, ואז הפעל מחדש את ה-runner. הדוגמאות מציגות אותם כייצוא של מעטפת. עם runners לפי דרישה, הגדר אותם על עומס העבודה שה-hook מסוג spawn-runner מתחיל.

התחל את ה-runners האלה עם --confine-repo-settings enforce. הוא מסרב לסשנים במאגרים שההגדרות השמורות בהם (committed settings) מסומנות על ידו, לכן הרץ קודם במצב ברירת המחדל warn ונקה את מה שהוא רושם.

Amazon Bedrock

החלף את האזור בשלך:

export CLAUDE_CODE_USE_BEDROCK=1
export AWS_REGION=us-east-1

לאופן שבו Claude Code פותר את האזור, ראה הגדרת Claude Code. לקידומת פרופיל ההסקה חוצה האזורים ש-Claude Code משתמש בה לאזור שלך, ראה קידומות של פרופילי הסקה חוצי אזורים.

Agent Platform של Google Cloud

החלף את האזור ואת מזהה הפרויקט בשלך:

export CLAUDE_CODE_USE_VERTEX=1
export CLOUD_ML_REGION=global
export ANTHROPIC_VERTEX_PROJECT_ID=YOUR-PROJECT-ID

כדי לבחור אזור, ראה הגדרת אזור.

4. בדוק שהמשתנים הגיעו לסשן

המעטפת שלך על המארח היא תהליך אחר, לכן בדוק מתוך סשן. התחל סשן בסביבה ובקש מ-Claude להריץ את הפקודה הזו:

env | grep -E 'CLAUDE_CODE_USE_(BEDROCK|VERTEX)'

שורה שמגדירה את CLAUDE_CODE_USE_BEDROCK או את CLAUDE_CODE_USE_VERTEX ל-1 פירושה שהמשתנה הגיע לסשן. אם שניהם מופיעים, Claude Code משתמש ב-Amazon Bedrock. אין פלט פירושו שאף אחד מהם לא הגיע.

הפקודה מציגה הגדרה ולא תעבורה. כדי לאשר את הבקשות עצמן, חפש אותן במדדים או ביומני הבקשות של חשבון הענן שלך. אם ההודעה הראשונה נכשלת במקום זאת, ראה פתרון בעיות עבור Amazon Bedrock או Agent Platform.

#מה שונה מסשנים על Anthropic API

סשן ששולח בקשות מודל אל Amazon Bedrock או אל Agent Platform של Google Cloud שונה מסשן על Anthropic API באופנים האלה:

  • מדיניות מ-claude.ai: הגדרות שמנוהלות בשרת לא מגיעות לסשנים האלה. גם המדיניות הארגונית ש-Owner מגדיר בהגדרות הניהול של Claude Code לא מגיעה, ולכן Claude Code לא אוכף אותן בתוך הסשן. שים את הכללים שאתה מסתמך עליהם בקובץ ההגדרות המנוהלות של תמונת ה-runner.
  • skills של חשבון: הסשנים האלה לא מורידים את ה-skills שמופעלים לחשבון claude.ai של אדם. ראה איך מורכבת ההגדרה של כל סשן.
  • קבצים: קבצים שאנשים מצרפים לסשן ב-claude.ai או באפליקציה לנייד או לשולחן העבודה לא מגיעים אליו, ו-Claude לא יכול לשלוח קבצים בחזרה עם הכלי SendUserFile. שים קבצי קלט במאגר או על ה-runner.
  • בחירת מודל: מישור הבקרה של Anthropic שולח את המודל של כל סשן, וכשסשן מתחיל בלי מודל, Claude Code משתמש בברירת המחדל שלו לספק. אי אפשר לבחור את המודל עם ANTHROPIC_MODEL או ANTHROPIC_DEFAULT_MODEL בסביבת ה-runner, אבל אפשר לקבע למה כינוי נפתר:
    • ANTHROPIC_MODEL ו-ANTHROPIC_DEFAULT_MODEL: ה-runner מסיר אותם מהסביבה שהוא מעביר לסשנים, אף על פי שהדוגמאות בדפי הספק מגדירות את ANTHROPIC_MODEL.
    • משתני קיבוע לפי משפחה: המשתנים ב-Pin model versions עבור Amazon Bedrock וAgent Platform מגיעים לסשנים. הם קובעים למה כינוי כמו opus נפתר, והם לא קובעים למה מזהה מודל מלא נפתר.
  • מודלים שהחשבון שלך לא מספק: סשן יכול להיכשל על הודעה עם שגיאה שמציינת את המודל. הפעל את המודלים שהמפתחים שלך יכולים לבחור, את מודל הרקע שמתואר ב-Pin model versions, ואת מודל המסווג שמצב auto משתמש בו. ב-Amazon Bedrock, אפשר כל אחד מהם במדיניות שלך.
  • חיפוש באינטרנט ומצב מהיר: חיפוש באינטרנט לא זמין ב-Amazon Bedrock, ומצב מהיר לא זמין בשני הספקים. ליכולות אחרות ששונות לפי ספק, ראה יכולות CLI שמשתנות לפי ספק.

#שרתי MCP

כדי ששרתי MCP יהיו זמינים בכל סשן, הוסיפו אותם בזמן בניית התמונה עם אותה פקודת claude mcp add שבה משתמשים בהתקנה על שולחן העבודה. אם ה-runner שלכם הוא תהליך חשוף ולא קונטיינר, הריצו את אותה פקודה בתור המשתמש של ה-runner על המארח, ואז הפעילו מחדש את ה-runner: הוא קורא את הגדרות המארח פעם אחת בהפעלה. הדגל --scope user נדרש. היקף local, שהוא ברירת המחדל, כותב תחת מפתח לפי תיקייה, שה-runner לא זורע לסשנים. לדוגמה, ב-Dockerfile שלכם:

RUN claude mcp add --scope user sidecar -- /usr/local/bin/mcp-sidecar
RUN claude mcp add --scope user --transport http internal http://mcp-gateway.svc.cluster.local:8080

ה-runner מצלם את הגדרות המארח פעם אחת בהפעלה. הצילום לוכד את המפתח mcpServers מקובץ .claude.json של המארח, שנמצא ליד ~/.claude/ ולא בתוכו, וה-runner זורע רק את המפתח הזה להגדרות המבודדות של כל סשן. מצב החשבון והיסטוריית הפרויקט נשמטים. כדי לאשר שהשרתים הגיעו לסשנים, התחילו סשן בסביבה ובקשו מ-Claude למנות את כלי ה-MCP שלו. ה-runner גם רושם אזהרת הפעלה לכל רשומה שנלכדה שה-type שלה לא מוכר לו, ומשמיט את הרשומה, כך שאפשר לראות למה השרת הזה חסר בסשנים. כאשר SELF_HOSTED_RUNNER_HOST_CONFIG_DIR מוגדר, ה-runner קורא את .claude.json מהתיקייה הזו במקום, ולכן הפניית המשתנה לתיקייה ריקה משביתה גם את זריעת ה-MCP.

Claude Code גם טוען שרתי MCP ממקורות אחרים:

  • קובץ MCP מנוהל בהיקף enterprise, בנתיב המערכת הסטנדרטי שלו: /etc/claude-code/managed-mcp.json על מארחי runner של Linux, ו-/Library/Application Support/ClaudeCode/managed-mcp.json על מארחי macOS. השתמשו בו לציים נעולים, שבהם מותר לטעון רק שרתים שמנהל המערכת רשם. ראו שליטה בלעדית עם managed-mcp.json לכללי הקדימות. כאשר הקובץ הזה נמצא על מארח ה-runner, Claude Code מדלג על שרתי ה-MCP שמישור הבקרה של Anthropic מעביר לסשן, כולל מחברי claude.ai, ומציין את שמותיהם באזהרה ב-stderr של תהליך הילד של הסשן, שה-runner רושם ברמת הלוג debug. לפני v2.1.229, הסשנים האלה יצאו בהפעלה עם You cannot dynamically configure MCP servers when an enterprise MCP config is present.
  • המפתח managedMcpServers בהגדרות מנוהלות על מארח ה-runner: מספק שרתי HTTP ו-SSE בלי לקחת שליטה בלעדית, כך ששרתים מהמקורות האחרים עדיין נטענים. דורש Claude Code v2.1.259 או גרסה חדשה יותר.
  • <repo>/.mcp.json: היקף פרויקט. מחוץ לשרשור פרויקט, בצעו commit של הקובץ למאגר, והשרתים שלו מאושרים אוטומטית בסשני ענן. בסשן עם כמה מאגרים, נטען לכל היותר הקובץ של מאגר אחד.
  • קובץ --mcp-config מסקריפט ה-wrapper שלכם: הוסיפו את הדגל אחרי "$@", כמו ב-exec "$CLAUDE_RUNNER_CLAUDE_BIN" "$@" --mcp-config /etc/claude/mcp.json. כדי ששרתי הקובץ הזה ימשיכו לרוץ כאשר השרתים שנמסרו לסשן מתעדכנים באמצע הסשן, גרמו ל-wrapper להעביר את הסביבה המלאה לתהליך הילד, כפי ש-exec עושה. לפני v2.1.299, שרתי stdio, HTTP ו-SSE בקובץ הזה נסגרו בעדכון הראשון מסוג זה שלא ציין אותם.

כאשר מסירת מחברים מופעלת לארגון שלכם, מישור הבקרה של Anthropic מעביר את המחברים שהגדרתם ב-claude.ai לסשנים שנוצרו באופן אינטראקטיבי, דרך הגדרת MCP שהשרת מספק, שמנותבת דרך api.anthropic.com. סשנים שנוצרים באופן תכנותי, כמו שיגורי CLI, לא מקבלים מסירת מחברים. תנו להם שרתי MCP דרך אחד מהמקורות האחרים שהקטע הזה מונה. אסימון ה-OAuth של תהליך הילד לא נושא scope לשליפת מחברים ישירות, ולכן תהליך הילד לא מנסה את השליפה הזו בעצמו. המסירה מונעת על ידי השרת.

[!NOTE]

בסשן שקיבל מחבר, שרת באותו שם מקובץ .claude.json של המארח או מקובץ .mcp.json של מאגר לא מחובר. כדי להשתמש בשניהם, תנו לשרת שלכם שם שונה.

settings.json לא נושא הגדרות של שרתי MCP, ואין שדה mcpServers ברמה העליונה בסכמת ההגדרות. בהגדרות מנוהלות, ספקו שרתים עם המפתח managedMcpServers.

סשנים יורשים את סביבת ה-runner, לכן הגדירו שם את ENABLE_TOOL_SEARCH כדי לשלוט בחיפוש כלי MCP לכל סשן שה-runner מפעיל. דף ה-MCP מכסה את הערכים.

#המתנה לשרתי MCP לפני התור הראשון

סשן self-hosted ממתין לזמן קצר לשרתי MCP שעדיין מתחברים, בשתי נקודות נפרדות. לשרת שמחמיץ המתנה, הכלים חסרים כשהתור הראשון מתחיל, והם נעשים זמינים מאוחר יותר בלי פעולה מצדכם. שתי ההמתנות הן:

  • הפעלת הסשן: לפני שרשימת הכלים נלקחת בפעם הראשונה, הסשן ממתין עד 5 שניות כברירת מחדל לשרת HTTP או SSE שהרשומה שלו מגדירה alwaysLoad: true, או לכל השרתים כאשר מגדירים MCP_CONNECTION_NONBLOCKING=0 בסביבת ה-runner. אחרת, שרתי HTTP ו-SSE מתחברים ברקע. בזמן שהסשן ממתין כאן, האתחול שלו איטי יותר. MCP_CONNECT_TIMEOUT_MS משנה את ברירת המחדל של 5 שניות.
  • התור הראשון: אחרי שההודעה מגיעה, התור הראשון ממתין עד 2 שניות לשרתי stdio שעדיין מתחברים. בזמן שהסשן ממתין כאן, התשובה הראשונה איטית יותר. כדי לשנות כמה זמן ההמתנה הזו נמשכת, הגדירו CLAUDE_CODE_MCP_STARTUP_WAIT_MS בסביבת ה-runner. זה לא משנה אילו שרתים ההמתנה מכסה. דורש Claude Code v2.1.274 או גרסה חדשה יותר.

ל-claude mcp add אין דגל alwaysLoad. כדי להגדיר את המפתח, הוסיפו את השרת עם claude mcp add-json, שמקבל אותו ב-JSON של השרת וכותב אותו אל .claude.json. ב-Dockerfile שלכם:

RUN claude mcp add-json core '{"type":"http","url":"https://mcp.example.com/mcp","alwaysLoad":true}' --scope user

אם הכלים של שרת לא מופיעים גם בתורות מאוחרים יותר, בדקו אם השרת הגיע לסשן בכלל, כפי ששרתי MCP מתאר.

#כיבוי כלי סשן מובנים

מישור הבקרה של Anthropic מחבר שרת MCP משלו, בשם Claude Code Remote, לסשני ענן. Claude משתמש בכלים של השרת כדי ליצור משימות ענן מתוזמנות, להתחיל ולכוון סשני ענן אחרים, לצרף מאגרים נוספים, ולעקוב אחרי פעילות של pull request.

כדי לכבות את השרת כולו, הוסיפו כלל דחייה ברמת שרת להגדרות שלכם. מישור הבקרה רושם את השרת תחת אחד משלושה שמות, בהתאם לאופן שבו הסשן נוצר. Claude Code מתאים את השם בכלל באופן מדויק, כולל רישיות, לכן כתבו את הכלל פעם אחת לכל שם, כפי שמוצג:

{
  "permissions": {
    "deny": [
      "mcp__Claude_Code_Remote",
      "mcp__claude-code-remote",
      "mcp__bf7c680d-5fdc-5ef4-b4a0-abadb619bf0a"
    ]
  }
}

כלל שמציין את השרת כולו מכסה גם כלים שהשרת מקבל מאוחר יותר. כדי לכבות כלי אחד ולהשאיר את השאר, הוסיפו שני קווים תחתונים נוספים ואת שם הכלי לכל כלל, כמו ב-mcp__Claude_Code_Remote__add_repo. כדי לחסום את חיבור השרת לגמרי, במקום להסיר את הכלים שלו, הוסיפו את שלושת השמות בלי הקידומת mcp__ כרשומות serverName תחת deniedMcpServers.

שימו את הכללים בהגדרות שמנוהלות בשרת כדי להגיע לסשנים בלי שינוי ב-runner, או ב-~/.claude/settings.json על ה-runner. על runner ששולח בקשות מודל ל-Bedrock או ל-Agent Platform, השתמשו בקובץ הזה, כי הגדרות שמנוהלות בשרת לא מגיעות לסשנים האלה. הרשאות ואישור כלים מסביר איך הגדרות על ה-runner מגיעות לסשנים.

כדי לאשר שהכללים נכנסו לתוקף, התחילו סשן בסביבה ובקשו מ-Claude למנות את כלי ה-MCP שלו. Claude Code מסיר כלי שנדחה מההקשר של Claude, ולכן הכלים שנדחו נעדרים מהתשובה שלו.

#הנחיה לסשנים לדחוף את העבודה שלהם

סשנים שמתארחים אצל Anthropic מריצים hook מסוג Stop, ה-hook של Claude Code שרץ כאשר Claude מסיים להגיב, שמנחה את Claude לבצע commit ולדחוף את העבודה שלו. ה-runner לא מתקין hook כזה. בלעדיו, סשן שמסתיים עם שינויים שלא בוצע להם commit משאיר את העבודה הזו רק על הדיסק של ה-runner, וכפתור Create PR ב-claude.ai/code נשאר לא פעיל עד שהענף קיים ב-remote.

מימוש הייחוס למטה כולל שני חלקים. מזגו את בלוק ההגדרות אל ~/.claude/settings.json על מארח ה-runner, שה-runner זורע לכל סשן, ושמרו את הסקריפט כ-~/.claude/hooks/stop-hook-nudge.sh על מארח ה-runner והפכו אותו לבר הרצה:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "timeout": 10,
            "command": "\"$CLAUDE_CONFIG_DIR/hooks/stop-hook-nudge.sh\""
          }
        ]
      }
    ]
  }
}
#!/bin/sh
# Stop-hook reference implementation for self-hosted runners.
#
# Nudges Claude once per turn if the project directory has uncommitted
# changes OR unpushed commits, so work isn't lost when an idle session
# is released and so the "Create PR" button on claude.ai/code lights up.
#
# Runner-level (no repo changes): drop this file at ~/.claude/hooks/ on
# the runner host and merge the accompanying Stop-hook settings block
# into ~/.claude/settings.json , the runner seeds both into every session.
# Repo-level alternative: commit to <repo>/.claude/hooks/ and change the
# settings.json command path to $CLAUDE_PROJECT_DIR/.claude/hooks/.
#
# stdin: hook JSON payload (see https://code.claude.com/docs/en/hooks)
# stdout: {"decision":"block","reason":"..."} to nudge, or nothing to allow stop.

# Re-entry guard: the harness sets stop_hook_active=true when re-invoking
# the Stop hook after a block. Bail so we only nudge once per turn. The
# harness emits compact JSON (no space after the colon), which this
# pattern relies on; use jq if you need a whitespace-tolerant check.
in=$(cat)
case "$in" in *'"stop_hook_active":true'*) exit 0 ;; esac

d="$CLAUDE_PROJECT_DIR"

# Hardening: this script runs outside Claude Code's Bash sandbox, so its git
# calls skip some features that can start a program named in the checkout's
# git settings. They don't skip them all, so don't count on the sandbox alone.
GIT_OPTIONAL_LOCKS=0
export GIT_OPTIONAL_LOCKS
git() { command git -c core.fsmonitor= -c core.hooksPath=/dev/null "$@"; }

# Not a git repo → nothing to nudge.
git -C "$d" rev-parse --git-dir >/dev/null 2>&1 || exit 0

# No remote → "push to the remote" is unsatisfiable; bail.
[ -z "$(git -C "$d" remote 2>/dev/null)" ] && exit 0

# Uncommitted changes (staged, unstaged, or untracked). Exclude .claude/
# entirely , operator-seeded settings and CLI-written runtime state
# (scheduler lock, worktrees, routine state) live there and neither is
# "uncommitted work" the model needs to push.
s=$(git -C "$d" status --porcelain --ignore-submodules=dirty -- . ':(exclude).claude/' 2>/dev/null)
if [ -n "$s" ]; then
  printf '{"decision":"block","reason":"There are uncommitted changes in the repository. Please commit and push these changes to the remote branch."}'
  exit 0
fi

# Unpushed commits. Count commits on HEAD not reachable from any
# remote-tracking ref or FETCH_HEAD. This works uniformly for:
#   - init+fetch checkouts (runner default: only FETCH_HEAD exists)
#   - clone-based checkouts (origin/* exist)
#   - the runner default: the child starts on the session's outcome
#     branch, which the runner creates after checkout
#   - detached HEAD, when a custom setup skips that branch creation
# With no reference point at all (never fetched), stay silent rather
# than false-positive on a read-only turn.
base=""
git -C "$d" rev-parse --verify -q FETCH_HEAD >/dev/null && base="FETCH_HEAD"
if [ -z "$base" ] && [ -z "$(git -C "$d" for-each-ref --count=1 refs/remotes/origin 2>/dev/null)" ]; then
  exit 0
fi
# shellcheck disable=SC2086  # $base is either "" or "FETCH_HEAD", intentional word-split
unpushed=$(git -C "$d" rev-list HEAD --not $base --remotes=origin --count 2>/dev/null) || unpushed=0
if [ "$unpushed" -gt 0 ]; then
  branch=$(git -C "$d" symbolic-ref --short -q HEAD)
  if [ -n "$branch" ]; then
    # $branch is attacker-influenced , git-check-ref-format(1) allows `"`
    # in ref names. `\` is forbidden (rule 10) but escaped anyway as cheap
    # defense-in-depth.
    # Escape JSON metacharacters before interpolating into the hand-built
    # payload so a branch like x","continue":false can't inject keys into
    # the hook-output JSON the harness parses. $unpushed is safe , the
    # -gt guard above rejects anything that isn't a plain integer.
    branch_esc=$(printf '%s' "$branch" | sed 's/\\/\\\\/g; s/"/\\"/g')
    printf '{"decision":"block","reason":"There are %s unpushed commit(s) on branch '\''%s'\''. Please push these changes to the remote repository."}' "$unpushed" "$branch_esc"
  else
    printf '{"decision":"block","reason":"There are %s unpushed commit(s) on a detached HEAD. Please create a branch and push it to the remote repository."}' "$unpushed"
  fi
  exit 0
fi

exit 0

ה-hook מנחה את Claude לבצע commit ולדחוף לפני שהסשן מסתיים, ונשאר שקט כאשר התיקייה אינה מאגר git או שאין לה remote. לסשן עם כמה מאגרים, ראו מה $CLAUDE_PROJECT_DIR מציין.

ה-hook מסוג Stop רק מנחה את Claude. כדי שה-commit והדחיפה יעבדו, הגדירו:

ה-hook מסוג Stop מנחה לכל היותר פעם אחת בכל תור. אם ה-commit או הדחיפה נכשלים, התור עדיין מסתיים. חפשו את שגיאת git בסשן ותקנו את מה שהיא מציינת.

ב-checkout גדול, העלו את timeout בהגדרות ה-hook מסוג Stop והפעילו מחדש את ה-runners. ה-git status של הסקריפט מדלג על core.fsmonitor, ולכן הוא יכול לקחת יותר מ-10 השניות שמוגדרות שם. hook שפג הזמן שלו מסיים את התור בלי הנחיה.

#תת-מודולים וה-hook מסוג Stop

ה-hook מסוג Stop בודק על איזה commit כל submodule נמצא, לא את הקבצים שבתוכו:

  • commit חדש ב-submodule: Claude מקבל הנחיה עד שמאגר האב רושם אותו, גם אם .gitmodules מגדיר ignore = all ל-submodule הזה
  • קבצים שלא בוצע להם commit ב-submodule: Claude לא מקבל הנחיה, ולכן העבודה הזו נשארת רק על הדיסק של ה-runner
  • commits שלא נדחפו ב-submodule: ה-hook מסוג Stop לא בודק אם הם נדחפו, ולכן האב יכול להצביע על commit שאף אחד אחר לא יכול לבצע לו fetch

#החלפת עותק מוקדם יותר של סקריפט ה-hook מסוג Stop

חפשו בעותק שלכם של stop-hook-nudge.sh את GIT_OPTIONAL_LOCKS. אם הוא חסר, יש לכם עותק מוקדם יותר. החליפו אותו בסקריפט הנוכחי בכל מקום שהתקנתם אותו, ואז הפעילו מחדש את ה-runners. שמרו את הגדרות ה-hook מסוג Stop, חוץ מהעלאת timeout ב-checkout גדול.

  • מתי: אם הסשנים שלכם רצים עם ארגז החול של Bash מופעל, החליפו אותו עכשיו. אחרת, החליפו אותו בבניית התמונה הבאה או בהפעלה מחדש של ה-runner.
  • מה משתנה: קבצים שלא בוצע להם commit ב-submodule כבר לא מנחים את Claude. commit חדש ב-submodule ש-.gitmodules מסמן לו ignore = all כן מנחה עכשיו.

[!WARNING]

hooks רצים מחוץ לארגז החול של Bash, ולכן קריאות ה-git של הסקריפט הנוכחי מדלגות על חלק מתכונות git שיכולות להפעיל תוכנית ששמה מופיע בהגדרות git של ה-checkout. הן לא מדלגות על כולן. אל תסמכו על ארגז החול לבדו, ובודדו סשנים כפי שחיזוק הפריסה שלכם מתאר.

#הרשאות ואישור כלים

לסשן באירוח עצמי אין טרמינל מחובר, ולכן בקשת הרשאה שלא נענתה עוצרת את התור עד שהמשתמש מגיב בממשק. מישור הבקרה של Anthropic שולח את רשימת הכלים ואת כללי ההרשאות של כל סשן יחד עם מטען העבודה. תצורת ברירת המחדל מאשרת מראש קריאות כלים שגרתיות, כולל Bash, וסשני ענן מאשרים מראש עריכות קבצים בלי קשר למצב. קריאה שדבר לא מאשר מראש מעלה בקשה דרך ממשק הסשן.

#קיבוע מצב הרשאות מתוך העטיפה

[!NOTE]

קבעו מצב auto רק בסביבה שמיכלי הסשן שלה רצים עם יציאת רשת בחסימה כברירת מחדל ושאר סעיף ההקשחה כבר במקום. קריאות כלים שגרתיות, כולל בקשות רשת של Bash, רצות בלי אדם בלולאה גם בערכת הכלים שמאושרת מראש כברירת מחדל וגם במצב auto, כך שגבול הרשת הוא מה שמגביל לאן הקריאות האלה יכולות להגיע.

כדי להתחיל סשנים במצב auto עד ש claude.ai/code או לקוח אחר קובע להם מצב הרשאות, קבעו אותו מסקריפט העטיפה שלכם או מהוק command. ה runner מפעיל את העטיפה עם הדגלים שלו כארגומנטים, ו Claude Code מחיל את --permission-mode האחרון בשורת הפקודה, לכן הוסיפו את שלכם אחרי "$@":

#!/bin/bash
exec "$CLAUDE_RUNNER_CLAUDE_BIN" "$@" --permission-mode auto

אחרי שלקוח קובע מצב, המצב שהוספתם כבר לא חל, גם אחרי שהסשן מופעל מחדש על ה runner. claude.ai/code, למשל, קובע מצב עם ההודעה הראשונה של הסשן כשתפריט המצב שלו מציג Auto, או כשמישהו בחר שם מצב.

במצב auto, מודל מסווג נפרד בודק פעולות לפני שהן רצות וחוסם את אלה שהוא דוחה. כלל ask מפורש עדיין כופה בקשה. קריאה שכלל deny חוסם נשארת חסומה בכל מצב.

#אישור מראש של כלים מסוימים

כדי לאשר מראש כלים מסוימים, הוסיפו --allowed-tools עם הכללים שלכם אחרי "$@" בסקריפט העטיפה, למשל --allowed-tools "Bash(bazel *) Bash(yarn *) mcp__internal__*". דגלים שהם רשימות, כמו --allowed-tools ו --disallowed-tools, מצטברים בין מופעים במקום לדרוס, כך שהכללים שלכם חלים מעל כללים שמישור הבקרה שולח. כדי לצמצם, הוסיפו --disallowed-tools, שדוחה כלים גם אם כלל אחר מתיר אותם.

#איך מורכבת התצורה של כל סשן

ה runner נותן לכל סשן ספריית תצורה משלו, שנזרעת מתצלום של ~/.claude/ של המארח שה runner לוכד פעם אחת בהפעלה: settings.json, CLAUDE.md, hooks, agents, commands ו skills בתמונת ה runner שלכם חלים על כל סשן כבסיס ברמת המשתמש. אם משנים תצורה על מארח שרץ, השינוי נכנס לתוקף רק אחרי שמפעילים מחדש את ה runner.

הגדירו את SELF_HOSTED_RUNNER_HOST_CONFIG_DIR כדי לזרוע מנתיב אחר, או כוונו אותו לספרייה ריקה כדי לבטל את הזריעה.

סשנים קוראים גם את קבצי ההגדרות האלה:

לסדר שבו המקורות האלה חלים, ראו קדימות הגדרות.

כשמישור הבקרה של Anthropic מספק לסשן hooks של Claude Code, ה runner מתקין אותם לצד התצורה שלכם, לא מעליה. נדרש Claude Code v2.1.229 או חדש יותר. runners ישנים יותר מתעלמים מ hooks שסופקו. ה hooks שסופקו מריצים את python3, אז התקינו את python3 בתמונת ה runner שלכם.

  • איפה הם נוחתים: ה runner כותב כל סקריפט hook שסופק לתת ספרייה שמורה hooks/.ccr-launcher/ בספריית התצורה של הסשן, ורושם את הסקריפטים בקובץ הגדרות נפרד שהוא מעביר לסשן עם --settings, ומשאיר את settings.json שנזרע ואת הסקריפטים שלכם ב hooks/<name> ללא שינוי. ה runner יוצר מחדש את תת הספרייה השמורה לכל סשן, ולא זורע תוכן מארח שנמצא ב ~/.claude/hooks/.ccr-launcher/ לתוך סשנים.
  • מי מחבר אותם: מישור הבקרה ממלא את הסקריפטים מקבועים קבועים בפריסה שלו, אף פעם לא מקלט פר סשן או מצד שלישי.
  • מה עדיין שולט בהם: hooks שנמסרים דרך --settings נכנסים לתצורת ה hooks הממוזגת הרגילה, לא לשכבה המנוהלת, כך שההגדרות המנוהלות שלכם עדיין חלות. disableAllHooks משבית אותם, והם לא בין הקטגוריות ש allowManagedHooksOnly משאיר טעונות.

כשאדם מתחיל סשן משלו, Claude Code מוריד את ה skills שמופעלים עבור חשבון claude.ai שלו לתוך ספריית התצורה של אותו סשן. הרצה של משימת ענן מתוזמנת לא מקבלת את ה skills של הבעלים שלה, וסשן ששולח בקשות מודל אל Bedrock או Agent Platform לא מוריד אף skill. עבור skill שהסשנים האלה צריכים, הוסיפו אותו לתמונת ה runner שלכם, או, מחוץ לשרשור פרויקט, הגישו אותו אל .claude/skills/ של המאגר.

מחוץ לסשנים של Claude Tag, סשן בסביבה באירוח עצמי רץ עם זיכרון אוטומטי כבוי כברירת מחדל. להוראות שצריכות לעבור בין סשנים, השתמשו ב CLAUDE.md שבתמונת ה runner או שבמאגר.

התצלום של ה runner של ~/.claude/ של המארח משמיט את ספריית projects/. מיקום האחסון בברירת המחדל של הזיכרון האוטומטי נמצא תחת הספרייה הזאת. אם שמים שם קבצי זיכרון, ה runner לא זורע אותם לתוך סשנים, והם לא מפעילים זיכרון אוטומטי.

#עצירת סשנים שנכשלים בשליפת הגדרות מנוהלות בשרת

כדי למנוע מסשן ששולף הגדרות מנוהלות בשרת להתחיל כשהשליפה נכשלת, הגדירו את forceRemoteSettingsRefresh ל true ב managed-settings.json שבתמונת ה runner שלכם. תשובה מ API של Anthropic שלפיה לארגון שלכם אין הגדרות מנוהלות בשרת נחשבת לשליפה מוצלחת.

לפני שמוסיפים את המפתח, בדקו אם הסביבה מריצה סשנים של ערוץ Claude Tag או שולחת בקשות מודל אל Bedrock או Agent Platform:

  • סשנים של ערוץ Claude Tag: השאירו את המפתח בחוץ על runners בסביבה ש Owner מנתב אליה ערוצי Claude Tag. ה API של Anthropic מסרב לבקשה של הסשנים האלה להגדרות מנוהלות בשרת, כך שכשהמפתח מוגדר הם נכשלים בהתחלה. שימו את הכללים שלהם ב managed-settings.json של תמונת ה runner, שהם עדיין קוראים. כדי להשתמש במפתח עבור הסשנים האחרים שלכם, הריצו אותם בסביבה נפרדת.
  • Bedrock או Agent Platform: סשנים אצל הספקים האלה לא שולפים הגדרות מנוהלות בשרת, כך שהם מתחילים בין אם הגדרתם את המפתח ובין אם לא. אכפו את הכללים שלכם מאותו managed-settings.json.

הקובץ managed-settings.json הזה מגדיר את המפתח:

{
  "forceRemoteSettingsRefresh": true
}

הגדירו את המפתח בקובץ הזה גם אם הגדרתם אותו גם בקונסולת הניהול של claude.ai. לסשן חדש אין עותק במטמון של ההגדרות המנוהלות בשרת שלכם, כך שאם מגדירים את המפתח רק בקונסולה, לסשן עדיין אין אותו. אם השליפה שלו נכשלת אחר כך, הסשן רץ בלי ההגדרות האלה עד ששליפה מאוחרת יותר מצליחה.

כדי לאשר ששליפת הגדרות שנכשלה היא הסיבה שסשן לא התחיל, הסתכלו על הפלט האחרון של Claude Code בסשן או ביומן של ה runner. הוא כולל את Your organization requires remote managed settings to load, but they could not be loaded.

#הגדרות מאגר בסשנים עם כמה מאגרים

בשרשור בפרויקט, שום .claude/settings.json של מאגר לא נכנס לתוקף כהגדרות פרויקט, ושום .mcp.json או skills של מאגר לא נטענים, בכל מקום שבו השרשור מתחיל. כדי לתת לשרשורים האלה skill, הוסיפו אותו לתמונת ה runner שלכם.

בכל סשן אחר עם כמה מאגרים, Claude Code קורא הגדרות פרויקט מהספרייה שהסשן מתחיל בה, כך שלכל היותר .claude/settings.json של מאגר אחד נכנס לתוקף כהגדרות פרויקט. hook שמוגדר בקובץ של מאגר אחר לא רץ, כלל deny שבתוכו לא חל, וה env שלו לא מוגדר.

  • --capacity 1, ברירת המחדל, עם ה checkout המובנה: הסשן מתחיל במאגר הראשון ברשימת המאגרים שלו. ה .claude/settings.json של המאגר הזה נכנס לתוקף כהגדרות פרויקט וה .mcp.json שלו נטען, ואלה של המאגרים האחרים לא.
  • --capacity מעל אחד, או הוק checkout: הסשן מתחיל בספרייה נפרדת לכל סשן שמכילה את ה checkouts. שום .claude/settings.json של מאגר לא נכנס לתוקף כהגדרות פרויקט, שום .mcp.json של מאגר לא נטען, ו $CLAUDE_PROJECT_DIR בפקודת hook הוא הספרייה הזאת, לא checkout.

ה CLAUDE.md של כל מאגר נטען בכל מקום שבו הסשן מתחיל. מחוץ לשרשור פרויקט, גם ה skills שלו נטענים. ה runner מעביר כל מאגר אל Claude Code כספרייה נוספת, כך ש Claude Code קורא גם את המפתחות enabledPlugins ו extraKnownMarketplaces מתוך .claude/settings.json של כל מאגר.

כדי להריץ hook או להחיל כלל הרשאה בכל סשן, שימו אותו ב ~/.claude/settings.json על מארח ה runner. ה runner זורע את קובץ המארח לתוך כל סשן, בכל מקום שבו הסשן מתחיל. כתבו נתיב בכלל Read או Edit כ // מוחלט או כ ~/ יחסי לבית, בתבנית, כי תבניות אחרות מעוגנות במקור ההגדרות או בספרייה הנוכחית.

#כללי הרשאה שמוגשים במאגר

אל תשימו רשומה חשופה "Edit", "Write", או "NotebookEdit" ב permissions.allow שמוגש במאגר. כלל כלי קבצים חשוף תואם לכלי בלי קשר לנתיב, ומעניק כתיבה בכל מקום על המארח ולא רק בסביבת העבודה, כך ששומר הגבלת היקף הכתיבה של ה runner מסמן את הסשן. עם --confine-repo-settings enforce הוא מסרב להקים את הסשן, במקום לרשום ביומן ולהמשיך. ראו את סעיף ההקשחה.

מאגר לא זקוק לשום כלל של כלי קבצים: סשני ענן מאשרים מראש עריכות קבצים בלי קשר למצב. אם כן מגישים כלל, תחמו אותו לסביבת העבודה, למשל "Edit(/**)". לוכסן מוביל יחיד הוא יחסי לשורש הפרויקט, שהוא סביבת העבודה של הסשן. כללי כלי קבצים חשופים תקינים ב settings.json ברמת המארח של המפעיל, כי הקובץ הזה לא מוגש במאגר.

ערך defaultMode של auto חל רק כשהוא מגיע מקובץ ההגדרות של כל התמונה או מקובץ ברמת המשתמש, כך שמאגר שעבר checkout לא יכול להעניק לעצמו מצב auto. לגבי אילו מצבים סשני ענן מקבלים ולתחביר המלא של הכללים, ראו מצבי הרשאה.

#מה הלאה