Skip to content

Commit 8b7a362

Browse files
AztecBotclaude
andcommitted
feat: migrate claudebox CI to new format — SSH tunnel + direct profile server API
- SSH tunnel now routes to port 4000 (was 3001 via proxy) - Use /sessions endpoint directly instead of /run proxy - Add profile/model/budget/target_ref inputs for workflow_dispatch - claude-review job uses same new format Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 3c1f54c commit 8b7a362

1 file changed

Lines changed: 85 additions & 102 deletions

File tree

.github/workflows/claudebox.yml

Lines changed: 85 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,23 @@ on:
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

Comments
 (0)