1515 description : ' Context link (e.g., PR URL, issue URL, external reference)'
1616 required : false
1717 type : string
18+ profile :
19+ description : ' Profile'
20+ required : false
21+ default : ' aztec'
22+ type : choice
23+ options : [aztec, barretenberg-audit, noir, aztec-audit, noir-audit]
24+ model :
25+ description : ' Model'
26+ required : false
27+ default : ' sonnet'
28+ type : choice
29+ options : [sonnet, opus]
30+ budget :
31+ description : ' Budget in USD'
32+ required : false
33+ default : ' 1.00'
34+ type : string
1835 target_ref :
1936 description : ' Git ref to checkout in the container (e.g., origin/backport-to-v4-next-staging)'
2037 required : false
@@ -33,23 +50,19 @@ jobs:
3350 if : github.event_name == 'issue_comment'
3451 run : |
3552 ASSOCIATION="${{ github.event.comment.author_association }}"
36- echo "Author association: $ASSOCIATION"
3753 if [[ "$ASSOCIATION" != "OWNER" && "$ASSOCIATION" != "MEMBER" && "$ASSOCIATION" != "COLLABORATOR" ]]; then
38- echo "ERROR: User does not have write access (association: $ASSOCIATION)"
54+ echo "ERROR: No write access ($ASSOCIATION)"
3955 exit 1
4056 fi
41- echo "Access granted."
4257
4358 - name : Add reaction
4459 if : github.event_name == 'issue_comment'
4560 env :
4661 GH_TOKEN : ${{ secrets.AZTEC_BOT_GITHUB_TOKEN }}
4762 run : |
48- gh api \
49- repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }}/reactions \
50- -f content='eyes' || true
63+ gh api repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }}/reactions -f content='eyes' || true
5164
52- - name : Setup SSH tunnel to ClaudeBox
65+ - name : Setup SSH tunnel
5366 env :
5467 BUILD_INSTANCE_SSH_KEY : ${{ secrets.BUILD_INSTANCE_SSH_KEY }}
5568 run : |
@@ -58,18 +71,17 @@ jobs:
5871 echo "${BUILD_INSTANCE_SSH_KEY}" | base64 --decode > ~/.ssh/build_instance_key
5972 chmod 600 ~/.ssh/build_instance_key
6073
61- # SSH tunnel: CI runner :4001 → bastion :3000 → (reverse tunnel) → claude-box :3001
62- ssh -f -N -L 4001 :localhost:3000 \
74+ # SSH tunnel: CI runner :4000 → bastion :3000 → (reverse tunnel) → claude-box :4000
75+ ssh -f -N -L 4000 :localhost:3000 \
6376 -o StrictHostKeyChecking=no \
6477 -o ServerAliveInterval=30 \
6578 -o ServerAliveCountMax=3 \
6679 -o ConnectTimeout=15 \
6780 -i ~/.ssh/build_instance_key \
6881 ubuntu@ci.aztec-labs.com
6982
70- # Wait for tunnel
7183 for i in $(seq 1 15); do
72- if curl -s -o /dev/null --max-time 2 http://localhost:4001/ 2>/dev/null; then
84+ if curl -s -o /dev/null --max-time 2 http://localhost:4000/health 2>/dev/null; then
7385 echo "SSH tunnel ready"
7486 exit 0
7587 fi
@@ -84,13 +96,25 @@ jobs:
8496 COMMENT_BODY : ${{ github.event.comment.body || '' }}
8597 INPUT_PROMPT : ${{ inputs.prompt || '' }}
8698 INPUT_LINK : ${{ inputs.link || '' }}
99+ INPUT_PROFILE : ${{ inputs.profile || 'aztec' }}
100+ INPUT_MODEL : ${{ inputs.model || 'sonnet' }}
101+ INPUT_BUDGET : ${{ inputs.budget || '1.00' }}
102+ INPUT_TARGET_REF : ${{ inputs.target_ref || '' }}
87103 run : |
88104 if [ -n "$INPUT_PROMPT" ]; then
89105 PROMPT="$INPUT_PROMPT"
90106 LINK="$INPUT_LINK"
107+ echo "profile=$INPUT_PROFILE" >> "$GITHUB_OUTPUT"
108+ echo "model=$INPUT_MODEL" >> "$GITHUB_OUTPUT"
109+ echo "budget=$INPUT_BUDGET" >> "$GITHUB_OUTPUT"
110+ echo "target_ref=$INPUT_TARGET_REF" >> "$GITHUB_OUTPUT"
91111 else
92112 PROMPT=$(printf '%s' "$COMMENT_BODY" | sed 's|^/claudebox[[:space:]]*||')
93113 LINK=""
114+ echo "profile=aztec" >> "$GITHUB_OUTPUT"
115+ echo "model=sonnet" >> "$GITHUB_OUTPUT"
116+ echo "budget=1.00" >> "$GITHUB_OUTPUT"
117+ echo "target_ref=" >> "$GITHUB_OUTPUT"
94118 fi
95119
96120 echo "link=$LINK" >> "$GITHUB_OUTPUT"
@@ -118,66 +142,45 @@ jobs:
118142 -f body="$BODY" \
119143 --jq '.id')
120144 echo "run_comment_id=$COMMENT_ID" >> "$GITHUB_OUTPUT"
121- echo "Posted status comment: $COMMENT_ID"
122145
123- - name : Run ClaudeBox
146+ - name : Run session
124147 timeout-minutes : 120
125148 env :
126- CLAUDEBOX_URL : http://localhost:4001
127- CLAUDEBOX_API_SECRET : ${{ secrets.CLAUDEBOX_API_SECRET }}
149+ CLAUDEBOX_URL : http://localhost:4000/${{ steps.parse.outputs.profile }}
150+ CLAUDEBOX_TOKEN : ${{ secrets.CLAUDEBOX_API_SECRET }}
128151 CLAUDEBOX_PROMPT : ${{ steps.parse.outputs.prompt }}
129- CLAUDEBOX_LINK : ${{ steps.parse.outputs.link }}
130- CLAUDEBOX_TARGET_REF : ${{ inputs.target_ref || '' }}
131- COMMENT_ID : ${{ github.event.comment.id || '' }}
132- RUN_COMMENT_ID : ${{ steps.status_comment.outputs.run_comment_id || '' }}
133- REPO : ${{ github.repository }}
134- RUN_URL : ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
135- AUTHOR : ${{ github.event.comment.user.login || github.actor }}
136152 run : |
137- AUTH="Authorization: Bearer ${CLAUDEBOX_API_SECRET}"
138- echo "Creating payload..."
139- PAYLOAD=$(jq -n \
140- --arg prompt "$CLAUDEBOX_PROMPT" \
141- --arg user "$AUTHOR" \
142- --arg comment_id "$COMMENT_ID" \
143- --arg run_comment_id "$RUN_COMMENT_ID" \
144- --arg repo "$REPO" \
145- --arg run_url "$RUN_URL" \
146- --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}')
153+ MODEL="${{ steps.parse.outputs.model }}"
154+ BUDGET="${{ steps.parse.outputs.budget }}"
155+ TARGET_REF="${{ steps.parse.outputs.target_ref }}"
149156
150- echo "Sending payload..."
151- # Start session — returns 202 with log URL
152- RESPONSE=$(curl -sS -w "\n%{http_code}" \
153- -H "$AUTH" -H "Content-Type: application/json" \
154- -d "$PAYLOAD" "${CLAUDEBOX_URL}/run")
157+ BODY=$(jq -n \
158+ --arg prompt "$CLAUDEBOX_PROMPT" \
159+ --arg model "$MODEL" \
160+ --arg budget "$BUDGET" \
161+ --arg target_ref "$TARGET_REF" \
162+ '{prompt: $prompt, model: $model, costUSD: ($budget | tonumber), user: "github-actions", name: $prompt, targetRef: $target_ref}')
155163
156- HTTP_CODE=$(echo "$RESPONSE" | tail -1)
157- BODY=$(echo "$RESPONSE" | head -n -1)
164+ echo "Starting session on ${{ steps.parse.outputs.profile }}..."
165+ RESP=$(curl -sf -X POST "$CLAUDEBOX_URL/sessions" \
166+ -H "Content-Type: application/json" \
167+ -H "Authorization: Bearer $CLAUDEBOX_TOKEN" \
168+ -d "$BODY")
158169
159- if [ "$HTTP_CODE" -ge 400 ] 2>/dev/null; then
160- echo "ClaudeBox returned HTTP $HTTP_CODE: $BODY"
161- exit 1
162- fi
170+ SID=$(echo "$RESP" | jq -r '.id // empty')
171+ echo "Session: $SID"
172+ echo "Status: https://claudebox.work/${{ steps.parse.outputs.profile }}/s/$SID"
163173
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
170174 while true; do
171- sleep 30
172- STATUS_BODY=$(curl -sS -H "$AUTH" "${ CLAUDEBOX_URL}/session/${SESSION_ID}" 2>/dev/null || echo '{}')
175+ sleep 10
176+ STATUS_BODY=$(curl -sf "$ CLAUDEBOX_URL/sessions/$SID" -H "Authorization: Bearer $CLAUDEBOX_TOKEN" || echo '{}')
173177 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
178+ COST=$(echo "$STATUS_BODY" | jq -r '.costUSD // 0')
179+ echo "$(date -u +%H:%M:%S) status=$STATUS cost=\$$COST"
180+ case "$STATUS" in
181+ completed) echo "Done. Cost: \$$COST"; exit 0 ;;
182+ error|cancelled|budget_exhausted) echo "Failed: $STATUS"; exit 1 ;;
183+ esac
181184 done
182185
183186 claude-review :
@@ -189,7 +192,7 @@ jobs:
189192 permissions :
190193 contents : read
191194 steps :
192- - name : Setup SSH tunnel to ClaudeBox
195+ - name : Setup SSH tunnel
193196 env :
194197 BUILD_INSTANCE_SSH_KEY : ${{ secrets.BUILD_INSTANCE_SSH_KEY }}
195198 run : |
@@ -198,7 +201,7 @@ jobs:
198201 echo "${BUILD_INSTANCE_SSH_KEY}" | base64 --decode > ~/.ssh/build_instance_key
199202 chmod 600 ~/.ssh/build_instance_key
200203
201- ssh -f -N -L 4001 :localhost:3000 \
204+ ssh -f -N -L 4000 :localhost:3000 \
202205 -o StrictHostKeyChecking=no \
203206 -o ServerAliveInterval=30 \
204207 -o ServerAliveCountMax=3 \
@@ -207,7 +210,7 @@ jobs:
207210 ubuntu@ci.aztec-labs.com
208211
209212 for i in $(seq 1 15); do
210- if curl -s -o /dev/null --max-time 2 http://localhost:4001/ 2>/dev/null; then
213+ if curl -s -o /dev/null --max-time 2 http://localhost:4000/health 2>/dev/null; then
211214 echo "SSH tunnel ready"
212215 exit 0
213216 fi
@@ -230,24 +233,18 @@ jobs:
230233 -f body="$BODY" \
231234 --jq '.id')
232235 echo "run_comment_id=$COMMENT_ID" >> "$GITHUB_OUTPUT"
233- echo "Posted review status comment: $COMMENT_ID"
234236
235- - name : Trigger ClaudeBox review
237+ - name : Trigger review session
236238 timeout-minutes : 120
237239 env :
238- CLAUDEBOX_URL : http://localhost:4001
239- CLAUDEBOX_API_SECRET : ${{ secrets.CLAUDEBOX_API_SECRET }}
240+ CLAUDEBOX_URL : http://localhost:4000/aztec
241+ CLAUDEBOX_TOKEN : ${{ secrets.CLAUDEBOX_API_SECRET }}
240242 PR_NUMBER : ${{ github.event.pull_request.number }}
241243 PR_TITLE : ${{ github.event.pull_request.title }}
242244 PR_URL : ${{ github.event.pull_request.html_url }}
243245 PR_AUTHOR : ${{ github.event.pull_request.user.login }}
244246 HEAD_REF : ${{ github.event.pull_request.head.ref }}
245- RUN_COMMENT_ID : ${{ steps.status_comment.outputs.run_comment_id || '' }}
246- RUN_URL : ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
247- REPO : ${{ github.repository }}
248247 run : |
249- AUTH="Authorization: Bearer ${CLAUDEBOX_API_SECRET}"
250-
251248 PROMPT="Review PR #${PR_NUMBER}: ${PR_TITLE}
252249 ${PR_URL}
253250
@@ -258,43 +255,29 @@ jobs:
258255 Focus on non-obvious bugs: edge cases, concurrency, security, correctness, compatibility.
259256 If you find a direct fix, create a PR. When done, call manage_review_labels(pr_number=${PR_NUMBER})."
260257
261- PAYLOAD =$(jq -n \
258+ BODY =$(jq -n \
262259 --arg prompt "$PROMPT" \
263260 --arg user "review/${PR_AUTHOR}" \
264- --arg run_comment_id "$RUN_COMMENT_ID" \
265- --arg repo "$REPO" \
266- --arg run_url "$RUN_URL" \
267- --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}')
261+ '{prompt: $prompt, user: $user, model: "sonnet", costUSD: 1, name: $prompt}')
270262
271- RESPONSE=$(curl -sS -w "\n%{http_code}" \
272- -H "$AUTH" -H "Content-Type: application/json" \
273- -d "$PAYLOAD" "${CLAUDEBOX_URL}/run")
263+ RESP=$(curl -sf -X POST "$CLAUDEBOX_URL/sessions" \
264+ -H "Content-Type: application/json" \
265+ -H "Authorization: Bearer $CLAUDEBOX_TOKEN" \
266+ -d "$BODY")
274267
275- HTTP_CODE=$(echo "$RESPONSE" | tail -1)
276- BODY=$(echo "$RESPONSE" | head -n -1)
268+ SID=$(echo "$RESP" | jq -r '.id // empty')
269+ echo "Review session: $SID"
270+ echo "Status: https://claudebox.work/aztec/s/$SID"
277271
278- if [ "$HTTP_CODE" -ge 400 ] 2>/dev/null; then
279- echo "ClaudeBox returned HTTP $HTTP_CODE: $BODY"
280- exit 1
281- fi
282-
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
288272 while true; do
289273 sleep 30
290- STATUS_BODY=$(curl -sS -H "$AUTH" "${ CLAUDEBOX_URL}/session/${SESSION_ID}" 2>/dev/null || echo '{}')
274+ STATUS_BODY=$(curl -sf "$ CLAUDEBOX_URL/sessions/$SID" -H "Authorization: Bearer $CLAUDEBOX_TOKEN" || echo '{}')
291275 STATUS=$(echo "$STATUS_BODY" | jq -r '.status // "unknown"')
292276 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
277+ case "$STATUS" in
278+ completed|error|cancelled|budget_exhausted)
279+ echo "Review finished: $STATUS"
280+ exit 0
281+ ;;
282+ esac
300283 done
0 commit comments