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

תיעוד 142

בדיקת סביבות באירוח עצמי מקצה לקצה

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

[!NOTE]

סביבות באירוח עצמי נמצאות בבטא ציבורית בתוכניות Team ו-Enterprise. זמינות ומגבלות מכסה את מסלול ההפעלה. הדף הזה הוא מתכון בדיקת CI. ראו את ההתחלה מהירה להתקנה ואת פריסה לייצור למתכוני הצי.

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

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

#התקנת הוק הלכידה על רץ הבדיקה

הקריאה חזרה עובדת דרך הוק Stop של Claude Code: כש-Claude מסיים תור, ההוק מקבל את הודעת העוזר הסופית כ-last_assistant_message ב-JSON של stdin ומוסיף אותה ל-$E2E_REPLY_DIR/<session_id>.txt. מתקינים אותו באותה דרך כמו הוק Stop של דחיפת commit, על ~/.claude/ של מארח הרץ, שהרץ זורע לכל סשן.

#שמירת קובצי ההוק

שמרו את שני הקבצים שלמטה על מארח הרץ:

  • בלוק ההגדרות: מזגו ל-~/.claude/settings.json על מארח הרץ
  • הסקריפט: שמרו כ-~/.claude/hooks/e2e-stop-hook-capture.sh על מארח הרץ והפכו אותו לבר הרצה
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "timeout": 10,
            "command": "\"$CLAUDE_CONFIG_DIR/hooks/e2e-stop-hook-capture.sh\""
          }
        ]
      }
    ]
  }
}
#!/bin/sh
# Stop hook for testing a self-hosted environment end to end: writes each
# turn's final assistant reply to $E2E_REPLY_DIR/<session_id>.txt so a
# co-located test driver can read it without calling the Anthropic API.
# Install on the TEST runner only. Requires jq.

# No-op unless the driver is listening. Never fail the turn.
[ -n "${E2E_REPLY_DIR:-}" ] && [ -d "$E2E_REPLY_DIR" ] || exit 0

# CLAUDE_CODE_REMOTE_SESSION_ID is exported in cse_... form; the session
# id the dispatch CLI prints is in session_... form. Same id, different
# prefix.
sid=$(printf '%s' "${CLAUDE_CODE_REMOTE_SESSION_ID:-}" | sed 's/^cse_/session_/')
[ -n "$sid" ] || exit 0

# last_assistant_message is absent when the final assistant turn had no
# text, such as a tool-use-only turn. The `// empty` filter makes that a
# zero-byte write rather than the literal string "null".
jq -r '.last_assistant_message // empty' >> "$E2E_REPLY_DIR/$sid.txt" 2>/dev/null
exit 0

#לפני שמפעילים את הרץ

להוק יש הדרישות האלה:

  • התקינו אותו לפני שמפעילים את הרץ. הרץ מצלם את ~/.claude/ פעם אחת בהפעלה, כך שהוק שנוסף לרץ שכבר רץ נכנס לתוקף רק אחרי הפעלה מחדש.
  • ייצאו את E2E_REPLY_DIR לתהליך הרץ. ההוק לא עושה דבר כשהמשתנה לא מוגדר או שהתיקייה לא קיימת, אז הגדירו אותו בכל מקום שמפעילים את הרץ, למשל יחידת systemd, מפרט pod, או שלב CI. סקריפט הבדיקה שלמטה דורש אותו גם כן.

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

#הרצת לולאת הבדיקה

דגלי השליחה --environment ו---ref דורשים Claude Code בגרסה v2.1.224 או חדשה יותר במכונה שמריצה את הסקריפט, אותו רצפה כמו הרץ עצמו. כשההוק במקום ורץ הופעל על המארח הזה, סקריפט הבדיקה:

  1. יוצר סשן בסביבת הבדיקה עם claude -p "<prompt>" --environment <environment-id> --output-format json. הריצו את הפקודה מתוך checkout של git כדי שה-CLI יוכל לזהות אוטומטית את המאגר מה-remote של origin. הדגל האופציונלי --ref <branch> מבסס את ה-checkout של הסשן על ref בשם במקום על HEAD המקומי. הפקודה יוצאת בלי להמתין לתשובה של Claude. מה שהיא מדפיסה אומר לסקריפט את התוצאה:
    • הסשן נוצר: שורת JSON אחת כגון {"ok":true,"session_id":"session_...","title":"...","url":"...","pool_id":"..."}
    • יצירת הסשן נכשלה: השורה {"ok":false,"error":"..."}, והפקודה יוצאת עם סטטוס 1
    • חלק מהשגיאות המוקדמות יותר, כגון שסשני ענן לא זמינים לארגון או שחסר prompt: השגיאה ב-stderr בלי שורת JSON, והפקודה יוצאת עם סטטוס 1
  2. ממתין שהתשובה תופיע ב-$E2E_REPLY_DIR/<session_id>.txt, שנכתב על ידי הוק Stop על הרץ אחרי שהתור מסתיים.
  3. שולח המשך עם claude -p "<message>" --cloud <session_id> --output-format json (ראו שליחת הודעת המשך לסשן רץ), ששולח אירוע משתמש לסשן הקיים ויוצא.
  4. ממתין לתשובת ההמשך באותה דרך כמו בשלב 2.

#התנהגות השליחה של --environment

Claude Code יוצר את הסשן, מדפיס את מזהה הסשן וקישור אליו, ויוצא.

הדגל מקבל עדיפות על פני ההגדרה remote.defaultEnvironmentId. הוא לא תומך ב---output-format stream-json, ואי אפשר לשלב אותו עם דגלים שמחדשים, מתחברים אל, או מגדירים מראש סשן, כגון --resume, --continue, --teleport, --session-id, או --init-only. --cloud נדחה עם מזהה סשן או URL, ובהרצות לא אינטראקטיביות כשהוא נושא תיאור. --cloud חשוף נחשב כנעדר. ממסוף אפשר להעביר את המשימה כתיאור של --cloud במקום prompt מיקומי.

#סקריפט לדוגמה

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

  • checkout של מאגר: הריצו את הסקריפט מתוך checkout של git של המאגר שבו רוצים שהסשן יעבוד.
  • רץ: הפעילו רץ על המארח הזה עם הוק הלכידה מותקן ו-E2E_REPLY_DIR מיוצא.
  • כניסה: היכנסו עם חשבון claude.ai במכונה שמריצה את הסקריפט, כפי שמתאר אימות מתוך CI.
  • מזהה סביבה: הגדירו את CLAUDE_TEST_ENVIRONMENT_ID למזהה ccpool_... של סביבת הבדיקה, שמוצג בתיבת הפרטים של הסביבה בדף הניהול או שמוחזר על ידי קריאת יצירת הסביבה.

הסקריפט שלמטה מריץ את הלולאה המלאה מול $CLAUDE_TEST_ENVIRONMENT_ID ובודק ביטוי סמן בכל תשובה.

#!/usr/bin/env bash
# End-to-end test against a self-hosted environment, using Stop-hook read-back.
# Prereqs: `claude auth login` has been run on this machine (see "Authenticate
# from CI" below); jq is installed; CLAUDE_TEST_ENVIRONMENT_ID names an
# environment whose runner is the one on this host, with the capture hook
# installed and E2E_REPLY_DIR in its environment.

set -euo pipefail

: "${CLAUDE_TEST_ENVIRONMENT_ID:=${CLAUDE_TEST_POOL_ID:-}}"  # CLAUDE_TEST_POOL_ID is the legacy spelling
: "${CLAUDE_TEST_ENVIRONMENT_ID:?set CLAUDE_TEST_ENVIRONMENT_ID to a ccpool_... id served by a runner on this host}"
: "${E2E_REPLY_DIR:?set E2E_REPLY_DIR to the directory the Stop hook on your test runner writes to, and export it to the runner process}"
: "${TEST_REPO_REF:=main}"

[ -d "$E2E_REPLY_DIR" ] || {
  echo "FAIL: E2E_REPLY_DIR ($E2E_REPLY_DIR) does not exist. The Stop hook on the runner needs it." >&2
  exit 1
}

# Waits until $E2E_REPLY_DIR/<session_id>.txt contains $2, or fails after
# 90 seconds. Tune the timeout to your environment's cold-start time. The
# file is written by the Stop hook on the runner.
await_reply() {
  local expect="$2" f="$E2E_REPLY_DIR/$1.txt"
  local deadline=$(($(date +%s) + 90))
  while :; do
    if [ -f "$f" ] && grep -qF -- "$expect" "$f"; then
      return
    fi
    [ "$(date +%s)" -lt "$deadline" ] || {
      echo "FAIL: '$expect' not in $f within 90s. The Stop hook on the runner did not write it." >&2
      echo "-- $E2E_REPLY_DIR contents --" >&2; ls -la "$E2E_REPLY_DIR" >&2
      [ -f "$f" ] && { echo "-- $f --" >&2; cat "$f" >&2; }
      exit 1
    }
    sleep 1
  done
}

# 1. Create the session on the test environment. Run from a git checkout
# so the CLI can auto-detect the repo. --ref pins the checkout to a named
# ref regardless of local HEAD.
TURN1="e2e-probe-$(date +%s)-$$: say exactly 'ok: custom tools are reachable' and nothing else"
EXPECT1="ok: custom tools are reachable"
create_json=$(claude -p "$TURN1" --environment "$CLAUDE_TEST_ENVIRONMENT_ID" \
  --ref "$TEST_REPO_REF" --output-format json < /dev/null)
echo "create: $create_json"
SESSION_ID=$(jq -er '.session_id' <<<"$create_json")

# 2. Wait for the turn-1 reply.
await_reply "$SESSION_ID" "$EXPECT1"
echo "turn-1 reply ok"

# 3. Post a follow-up via the CLI.
TURN2="e2e-probe-followup-$(date +%s): say exactly 'ok: follow-up delivered' and nothing else"
EXPECT2="ok: follow-up delivered"
followup_json=$(claude -p "$TURN2" --cloud "$SESSION_ID" --output-format json < /dev/null)
echo "followup: $followup_json"
jq -e '.ok == true' <<<"$followup_json" >/dev/null

# 4. Wait for the turn-2 reply.
await_reply "$SESSION_ID" "$EXPECT2"
echo "turn-2 reply ok"

echo "PASS: test-environment round-trip (session $SESSION_ID)"

החליפו את ה-prompts של TURN1/TURN2 ואת הסמנים EXPECT1/EXPECT2 בכל מה שמתרגל את ההגדרה שלכם, למשל לבקש מ-Claude להריץ אחד מכלי ה-MCP המותאמים ולבדוק את הפלט שלו.

#רצי בדיקה מרוחקים

אם רצי הבדיקה נמצאים על תשתית נפרדת, למשל צי Kubernetes קבוע שעבודת ה-CI לא יכולה לשתף איתו מערכת קבצים, החליפו את כתיבת הקובץ בהוק Stop ב-POST לנקודת קצה שהדרייבר מאזין לה:

#!/bin/sh
# Variant of the capture hook for runners on separate infrastructure.
# Set E2E_REPLY_URL on the runner to an endpoint the driver controls.
[ -n "${E2E_REPLY_URL:-}" ] || exit 0
sid=$(printf '%s' "${CLAUDE_CODE_REMOTE_SESSION_ID:-}" | sed 's/^cse_/session_/')
[ -n "$sid" ] || exit 0
jq -r '.last_assistant_message // empty' | \
  curl -fsS -X POST --data-binary @- "$E2E_REPLY_URL/$sid" >/dev/null 2>&1
exit 0

בצד הדרייבר, הריצו כל דבר שמקבל את ה-POST ומחזיק את התשובה עד שהבדיקה מבקשת אותה, למשל מאזין HTTP קטן בתוך עבודת ה-CI או מקלט webhook שכבר מריצים. ההוק רץ על התשתית שלכם, כך שנקודת הקצה צריכה להיות נגישה רק מהרצים שלכם.

#אימות מתוך CI

גם claude -p ... --environment וגם claude -p ... --cloud מאמתים עם אסימון OAuth של claude.ai. מפתחות API, כגון sk-ant-xxxxx, לא מתקבלים לאף אחת מהקריאות. שתי גישות הופכות אסימון לזמין ב-CI.

#מארח CI ארוך טווח

הריצו את claude auth login פעם אחת באופן אינטראקטיבי במכונה שמבצעת את הסקריפט, עם חשבון משתמש ייעודי לאוטומציה. Claude Code שומר את האסימון במחזיק המפתחות של מערכת ההפעלה ב-macOS, או ב-~/.claude/.credentials.json ב-Linux וב-Windows. במארח macOS שמחזיק המפתחות שלו לא ניתן לכתיבה, כפי שאופייני בסשן SSH שבו מחזיק המפתחות של הכניסה נשאר נעול, Claude Code שומר את האסימון ב-~/.claude/.credentials.json גם שם. ראו ניהול אישורים.

ה-CLI מרענן את אסימון הגישה קצר החיים אוטומטית בכל הפעלה, אבל מענק אסימון הרענון שמתחתיו מוגבל ל-30 יום מהכניסה הראשונית, אז הריצו שוב את claude auth login באופן אינטראקטיבי על המארח הזה כל 30 יום.

#רצי CI ארעיים

אין היום אסימון CI ארוך טווח לזה. ההיקף שמעניק שליטה בסשני ענן, user:sessions:claude_code, מוגבל בצד השרת ל-30 יום, כך ש-claude setup-token, שמנפיק אסימון להסקה בלבד לשנה, לא מכסה אותו. גם סוד הסביבה לא מתקבל, כי הוא מאשר רק לרץ להירשם לסביבה, לא ליצור סשנים.

כדי לספק כניסה שמורה לרץ ארעי, הגדירו את CLAUDE_CODE_OAUTH_REFRESH_TOKEN ו-CLAUDE_CODE_OAUTH_SCOPES כך ש-claude auth login יחליף את האסימון בלי דפדפן. אותה תקרת 30 יום חלה על מענק הרענון. פנו לצוות החשבון של Anthropic אם אתם צריכים מסלול זהות מכונה שלא קשור לחשבון אנושי.

#יצירת סביבת בדיקה ייעודית

צרו ומחקו סביבות באופן תכנותי כדי שכל הרצת CI תקבל סביבה נקייה. הרץ שעבודת ה-CI מפעילה נרשם לסביבה החדשה. קריאות היצירה והמחיקה שלמטה הן אותן נקודות קצה שדף הניהול Cloud environments ב-claude.ai משתמש בהן, והן דורשות את הכותרת anthropic-beta: ccr-byoc-2025-07-29.

#הנפקת אסימון הניהול

$ADMIN_TOKEN הוא אסימון גישה מסוג OAuth של claude.ai לחשבון שמחזיק בתפקיד Owner, שמונפק באותה דרך כמו אימות מתוך CI:

  • הנפיקו אותו: הריצו את claude auth login עם חשבון שמחזיק בתפקיד Owner, ואז קראו את אסימון הגישה הנוכחי מהמקום שבו מארח CI ארוך טווח אומר ש-Claude Code שמר אותו.
  • קראו אותו מחדש בכל הרצה: ה-CLI מסובב את אסימון הגישה, ואותה תקרת מענק רענון של 30 יום חלה, אז אל תשמרו עותק.
  • העבירו אותו דרך stdin: כמו בדוגמה, כדי שהאסימון לעולם לא ייכנס לרשימת הארגומנטים של curl או ללוג הבנייה.

#יצירת הסביבה

לכדו את התשובה בלי להדהד אותה: pool_secret הוא אישור ארוך טווח שיכול לרשום רצים לסביבה, אז שמרו אותו כסוד CI מוסתר והדפיסו רק את מזהה הסביבה. הצורה -H @- שמשאירה את האסימון מחוץ לרשימת התהליכים דורשת curl 7.55 או חדש יותר. curl ישן יותר מתייחס ל-@- ככותרת מילולית ושולח את הבקשה בלי הרשאה.

create=$(curl -fsS -X POST -H @- \
  -H "anthropic-beta: ccr-byoc-2025-07-29" -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{"name":"ci-test-environment"}' \
  https://api.anthropic.com/v1/code/runners/self-hosted/pools \
  <<<"Authorization: Bearer $ADMIN_TOKEN")
ENVIRONMENT_ID=$(jq -er .pool.pool_id <<<"$create")
ENVIRONMENT_SECRET=$(jq -er .pool_secret <<<"$create")

עד שOwner מפעיל את Allow self-hosted environments לארגון, הקריאה נכשלת עם 403 permission_error שקורא self-hosted runners are disabled by your organization's policy.

הפעילו רץ על המארח הזה עם SELF_HOSTED_RUNNER_ENVIRONMENT_SECRET=$ENVIRONMENT_SECRET, ועם הוק הלכידה ו-E2E_REPLY_DIR לפי התקנת הוק הלכידה, ואז הריצו את סקריפט הבדיקה.

#מחיקת הסביבה

מחקו את הסביבה כשההרצה מסתיימת, כדי שכל הרצת CI תתחיל נקייה:

curl -fsS -X DELETE -H @- \
  -H "anthropic-beta: ccr-byoc-2025-07-29" -H "anthropic-version: 2023-06-01" \
  "https://api.anthropic.com/v1/code/runners/self-hosted/pools/$ENVIRONMENT_ID" \
  <<<"Authorization: Bearer $ADMIN_TOKEN"