1616 required : false
1717 type : string
1818 target_ref :
19- description : ' Git ref to checkout in the container (e.g., origin/backport-to-v4-next-staging)'
19+ description : ' Git ref the session should work against (e.g., origin/merge-train/barretenberg)'
20+ required : false
21+ type : string
22+ slack_channel :
23+ description : ' Slack channel ID to thread status into (set by the kickoff script)'
24+ required : false
25+ type : string
26+ slack_thread_ts :
27+ description : ' Slack thread timestamp to reply under (set by the kickoff script)'
2028 required : false
2129 type : string
2230
31+ # ClaudeBox v2 runs as a public service at https://claudebox.work. CI hands a
32+ # job off by POSTing to its /run webhook (Bearer-authed with
33+ # CLAUDEBOX_API_SECRET) and returns immediately — the session reports progress
34+ # back to the bound Slack thread and to any GitHub comment IDs we pass through.
35+ # The old v1 server lived on a private build instance reached over an SSH
36+ # tunnel; that path is retired.
37+ env :
38+ CLAUDEBOX_URL : ${{ vars.CLAUDEBOX_URL || 'https://claudebox.work' }}
39+
2340jobs :
2441 claudebox :
2542 if : >-
@@ -49,41 +66,13 @@ jobs:
4966 repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }}/reactions \
5067 -f content='eyes' || true
5168
52- - name : Setup SSH tunnel to ClaudeBox
53- env :
54- BUILD_INSTANCE_SSH_KEY : ${{ secrets.BUILD_INSTANCE_SSH_KEY }}
55- run : |
56- set -eu
57- mkdir -p ~/.ssh
58- echo "${BUILD_INSTANCE_SSH_KEY}" | base64 --decode > ~/.ssh/build_instance_key
59- chmod 600 ~/.ssh/build_instance_key
60-
61- # SSH tunnel: CI runner :4001 → bastion :3000 → (reverse tunnel) → claude-box :3001
62- ssh -f -N -L 4001:localhost:3000 \
63- -o StrictHostKeyChecking=no \
64- -o ServerAliveInterval=30 \
65- -o ServerAliveCountMax=3 \
66- -o ConnectTimeout=15 \
67- -i ~/.ssh/build_instance_key \
68- ubuntu@ci.aztec-labs.com
69-
70- # Wait for tunnel
71- for i in $(seq 1 15); do
72- if curl -s -o /dev/null --max-time 2 http://localhost:4001/ 2>/dev/null; then
73- echo "SSH tunnel ready"
74- exit 0
75- fi
76- sleep 1
77- done
78- echo "ERROR: SSH tunnel failed to connect"
79- exit 1
80-
8169 - name : Parse command
8270 id : parse
8371 env :
8472 COMMENT_BODY : ${{ github.event.comment.body || '' }}
8573 INPUT_PROMPT : ${{ inputs.prompt || '' }}
8674 INPUT_LINK : ${{ inputs.link || '' }}
75+ INPUT_TARGET_REF : ${{ inputs.target_ref || '' }}
8776 run : |
8877 if [ -n "$INPUT_PROMPT" ]; then
8978 PROMPT="$INPUT_PROMPT"
9382 LINK=""
9483 fi
9584
85+ # ClaudeBox v2 has no separate target_ref input; fold it into the
86+ # prompt so the agent fetches and bases its branch on the right ref.
87+ if [ -n "$INPUT_TARGET_REF" ]; then
88+ PROMPT="$PROMPT
89+
90+ Work against git ref: $INPUT_TARGET_REF. Fetch it and base your branch on it."
91+ fi
92+
9693 echo "link=$LINK" >> "$GITHUB_OUTPUT"
9794 {
9895 echo "prompt<<PROMPT_EOF"
@@ -120,22 +117,24 @@ jobs:
120117 echo "run_comment_id=$COMMENT_ID" >> "$GITHUB_OUTPUT"
121118 echo "Posted status comment: $COMMENT_ID"
122119
123- - name : Run ClaudeBox
124- timeout-minutes : 120
120+ - name : Dispatch ClaudeBox v2
125121 env :
126- CLAUDEBOX_URL : http://localhost:4001
127122 CLAUDEBOX_API_SECRET : ${{ secrets.CLAUDEBOX_API_SECRET }}
128123 CLAUDEBOX_PROMPT : ${{ steps.parse.outputs.prompt }}
129124 CLAUDEBOX_LINK : ${{ steps.parse.outputs.link }}
130- CLAUDEBOX_TARGET_REF : ${{ inputs.target_ref || '' }}
131125 COMMENT_ID : ${{ github.event.comment.id || '' }}
132126 RUN_COMMENT_ID : ${{ steps.status_comment.outputs.run_comment_id || '' }}
133127 REPO : ${{ github.repository }}
134128 RUN_URL : ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
135129 AUTHOR : ${{ github.event.comment.user.login || github.actor }}
130+ SLACK_CHANNEL : ${{ inputs.slack_channel || '' }}
131+ SLACK_THREAD_TS : ${{ inputs.slack_thread_ts || '' }}
136132 run : |
137- AUTH="Authorization: Bearer ${CLAUDEBOX_API_SECRET}"
138- echo "Creating payload..."
133+ if [ -z "${CLAUDEBOX_API_SECRET:-}" ]; then
134+ echo "ERROR: CLAUDEBOX_API_SECRET is not set; cannot dispatch ClaudeBox v2"
135+ exit 1
136+ fi
137+
139138 PAYLOAD=$(jq -n \
140139 --arg prompt "$CLAUDEBOX_PROMPT" \
141140 --arg user "$AUTHOR" \
@@ -144,41 +143,29 @@ jobs:
144143 --arg repo "$REPO" \
145144 --arg run_url "$RUN_URL" \
146145 --arg link "$CLAUDEBOX_LINK" \
147- --arg target_ref "$CLAUDEBOX_TARGET_REF" \
148- '{prompt: $prompt, user: $user, comment_id: $comment_id, run_comment_id: $run_comment_id, repo: $repo, run_url: $run_url, link: $link, target_ref: $target_ref}')
146+ --arg slack_channel "$SLACK_CHANNEL" \
147+ --arg slack_thread_ts "$SLACK_THREAD_TS" \
148+ '{prompt: $prompt, user: $user, repo: $repo, run_url: $run_url, link: $link, slack_channel: $slack_channel, slack_thread_ts: $slack_thread_ts}
149+ + (if $comment_id != "" then {comment_id: ($comment_id | tonumber)} else {} end)
150+ + (if $run_comment_id != "" then {run_comment_id: ($run_comment_id | tonumber)} else {} end)')
149151
150- echo "Sending payload..."
151- # Start session — returns 202 with log URL
152+ # Fire-and-forget: v2 reports progress to Slack / GitHub comments.
152153 RESPONSE=$(curl -sS -w "\n%{http_code}" \
153- -H "$AUTH" -H "Content-Type: application/json" \
154+ -H "Authorization: Bearer ${CLAUDEBOX_API_SECRET}" \
155+ -H "Content-Type: application/json" \
154156 -d "$PAYLOAD" "${CLAUDEBOX_URL}/run")
155157
156158 HTTP_CODE=$(echo "$RESPONSE" | tail -1)
157159 BODY=$(echo "$RESPONSE" | head -n -1)
158160
159161 if [ "$HTTP_CODE" -ge 400 ] 2>/dev/null; then
160- echo "ClaudeBox returned HTTP $HTTP_CODE: $BODY"
162+ echo "ClaudeBox v2 returned HTTP $HTTP_CODE: $BODY"
161163 exit 1
162164 fi
163165
164- LOG_URL=$(echo "$BODY" | jq -r '.log_url // empty')
165- SESSION_ID=$(basename "$LOG_URL")
166- echo "Session started: $LOG_URL"
167-
168- echo "Session received, polling..."
169- # Poll until completed
170- while true; do
171- sleep 30
172- STATUS_BODY=$(curl -sS -H "$AUTH" "${CLAUDEBOX_URL}/session/${SESSION_ID}" 2>/dev/null || echo '{}')
173- STATUS=$(echo "$STATUS_BODY" | jq -r '.status // "unknown"')
174- echo "$(date -u +%H:%M:%S) status=$STATUS"
175- if [ "$STATUS" != "running" ]; then
176- EXIT_CODE=$(echo "$STATUS_BODY" | jq -r '.exit_code // 1')
177- echo "Session finished: status=$STATUS exit_code=$EXIT_CODE"
178- echo "Log: $LOG_URL"
179- exit "$EXIT_CODE"
180- fi
181- done
166+ SESSION_ID=$(echo "$BODY" | jq -r '.session_id // empty')
167+ echo "ClaudeBox v2 session: ${CLAUDEBOX_URL}/v2/sessions/${SESSION_ID}"
168+ echo "Status: $(echo "$BODY" | jq -r '.status // "unknown"')"
182169
183170 claude-review :
184171 if : >-
@@ -189,33 +176,6 @@ jobs:
189176 permissions :
190177 contents : read
191178 steps :
192- - name : Setup SSH tunnel to ClaudeBox
193- env :
194- BUILD_INSTANCE_SSH_KEY : ${{ secrets.BUILD_INSTANCE_SSH_KEY }}
195- run : |
196- set -eu
197- mkdir -p ~/.ssh
198- echo "${BUILD_INSTANCE_SSH_KEY}" | base64 --decode > ~/.ssh/build_instance_key
199- chmod 600 ~/.ssh/build_instance_key
200-
201- ssh -f -N -L 4001:localhost:3000 \
202- -o StrictHostKeyChecking=no \
203- -o ServerAliveInterval=30 \
204- -o ServerAliveCountMax=3 \
205- -o ConnectTimeout=15 \
206- -i ~/.ssh/build_instance_key \
207- ubuntu@ci.aztec-labs.com
208-
209- for i in $(seq 1 15); do
210- if curl -s -o /dev/null --max-time 2 http://localhost:4001/ 2>/dev/null; then
211- echo "SSH tunnel ready"
212- exit 0
213- fi
214- sleep 1
215- done
216- echo "ERROR: SSH tunnel failed to connect"
217- exit 1
218-
219179 - name : Post review status comment
220180 id : status_comment
221181 env :
@@ -232,10 +192,8 @@ jobs:
232192 echo "run_comment_id=$COMMENT_ID" >> "$GITHUB_OUTPUT"
233193 echo "Posted review status comment: $COMMENT_ID"
234194
235- - name : Trigger ClaudeBox review
236- timeout-minutes : 120
195+ - name : Dispatch ClaudeBox v2 review
237196 env :
238- CLAUDEBOX_URL : http://localhost:4001
239197 CLAUDEBOX_API_SECRET : ${{ secrets.CLAUDEBOX_API_SECRET }}
240198 PR_NUMBER : ${{ github.event.pull_request.number }}
241199 PR_TITLE : ${{ github.event.pull_request.title }}
@@ -246,7 +204,10 @@ jobs:
246204 RUN_URL : ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
247205 REPO : ${{ github.repository }}
248206 run : |
249- AUTH="Authorization: Bearer ${CLAUDEBOX_API_SECRET}"
207+ if [ -z "${CLAUDEBOX_API_SECRET:-}" ]; then
208+ echo "ERROR: CLAUDEBOX_API_SECRET is not set; cannot dispatch ClaudeBox v2"
209+ exit 1
210+ fi
250211
251212 PROMPT="Review PR #${PR_NUMBER}: ${PR_TITLE}
252213 ${PR_URL}
@@ -265,36 +226,22 @@ jobs:
265226 --arg repo "$REPO" \
266227 --arg run_url "$RUN_URL" \
267228 --arg link "$PR_URL" \
268- --arg profile "review" \
269- '{prompt: $prompt, user: $user, run_comment_id: $run_comment_id, repo: $repo, run_url: $run_url, link: $link, profile: $profile} ')
229+ '{prompt: $prompt, user: $user, repo: $repo, run_url: $run_url, link: $link}
230+ + (if $ run_comment_id != "" then {run_comment_id: ($run_comment_id | tonumber)} else {} end) ')
270231
271232 RESPONSE=$(curl -sS -w "\n%{http_code}" \
272- -H "$AUTH" -H "Content-Type: application/json" \
233+ -H "Authorization: Bearer ${CLAUDEBOX_API_SECRET}" \
234+ -H "Content-Type: application/json" \
273235 -d "$PAYLOAD" "${CLAUDEBOX_URL}/run")
274236
275237 HTTP_CODE=$(echo "$RESPONSE" | tail -1)
276238 BODY=$(echo "$RESPONSE" | head -n -1)
277239
278240 if [ "$HTTP_CODE" -ge 400 ] 2>/dev/null; then
279- echo "ClaudeBox returned HTTP $HTTP_CODE: $BODY"
280- exit 1
241+ echo "ClaudeBox v2 returned HTTP $HTTP_CODE: $BODY"
242+ # Review dispatch failures are informational — don't fail the workflow.
243+ exit 0
281244 fi
282245
283- LOG_URL=$(echo "$BODY" | jq -r '.log_url // empty')
284- SESSION_ID=$(basename "$LOG_URL")
285- echo "Review session started: $LOG_URL"
286-
287- # Poll until completed
288- while true; do
289- sleep 30
290- STATUS_BODY=$(curl -sS -H "$AUTH" "${CLAUDEBOX_URL}/session/${SESSION_ID}" 2>/dev/null || echo '{}')
291- STATUS=$(echo "$STATUS_BODY" | jq -r '.status // "unknown"')
292- echo "$(date -u +%H:%M:%S) status=$STATUS"
293- if [ "$STATUS" != "running" ]; then
294- EXIT_CODE=$(echo "$STATUS_BODY" | jq -r '.exit_code // 1')
295- echo "Review finished: status=$STATUS exit_code=$EXIT_CODE"
296- echo "Log: $LOG_URL"
297- # Don't fail the workflow on review errors — the review itself is informational
298- exit 0
299- fi
300- done
246+ SESSION_ID=$(echo "$BODY" | jq -r '.session_id // empty')
247+ echo "ClaudeBox v2 review session: ${CLAUDEBOX_URL}/v2/sessions/${SESSION_ID}"
0 commit comments