Skip to content

Commit 70171b4

Browse files
Gkrumbach07Ambient Code Botclaudeambient-code[bot]
authored
feat: post check runs on PRs with Amber session link (#1201)
<!-- acp:session_id=session-59da355d-1aa3-4de7-84ae-79ee7b5e2a5a source=#1201 last_action=2026-04-06T17:48:08Z retry_count=0 --> [git remote -v] [git config --get-regexp ^remote\..*\.gh-resolved$] <!-- acp:session_id=session-59da355d-1aa3-4de7-84ae-79ee7b5e2a5a source=#1201 last_action=2026-04-06T16:48:46Z retry_count=0 --> [git remote -v] [git config --get-regexp ^remote\..*\.gh-resolved$] <!-- acp:session_id=session-59da355d-1aa3-4de7-84ae-79ee7b5e2a5a source=#1201 last_action=2026-04-06T16:47:48Z retry_count=1 --> [git remote -v] [git config --get-regexp ^remote\..*\.gh-resolved$] <!-- acp:session_id=session-59da355d-1aa3-4de7-84ae-79ee7b5e2a5a source=#1201 last_action=2026-04-06T16:19:50Z retry_count=1 --> ## Summary When Amber works on a PR, post a GitHub check run so the session shows up in the PR checks tab with a direct link to the session UI. ## Where it posts - **`handle-comment` on PR**: After any `@ambient-code` comment triggers a fix/custom session - **`batch-pr-fixer`**: After each PR is processed in the 30 min cron ## What it shows - **Check name**: "Amber Session" - **Conclusion**: success (Completed/Running), failure (Error/Failed), neutral (other) - **Details URL**: Direct link to the session in Ambient UI - **Summary**: Session name, phase, and prompt type ## Permissions Added `checks: write` to workflow permissions. ## Test plan - [ ] Comment `@ambient-code` on a PR — verify check appears in PR checks tab - [ ] Click "Details" on the check — verify it opens the session URL - [ ] Batch cron processes a PR — verify check appears 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Posts an "Amber Session" status check on pull requests to show session progress and outcome. * Sends Slack notifications for circuit-breaker events when a webhook is configured. * **Behavior Changes** * Automated corrections must be explicitly logged for each fix to improve visibility. * Retry-count and circuit-breaker handling moved into session/frontmatter logic, triggering human review at the threshold. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Ambient Code Bot <bot@ambient-code.local> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: ambient-code[bot] <235912155+ambient-code[bot]@users.noreply.github.com>
1 parent b130dc8 commit 70171b4

1 file changed

Lines changed: 133 additions & 34 deletions

File tree

.github/workflows/amber-issue-handler.yml

Lines changed: 133 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ permissions:
2828
contents: read
2929
issues: write
3030
pull-requests: write
31+
checks: write
3132

3233
jobs:
3334
# -- Issue: labeled ambient-code:auto-fix → fresh session prompt --
@@ -96,6 +97,8 @@ jobs:
9697
model: claude-opus-4-6
9798
wait: 'true'
9899
timeout: '0'
100+
environment-variables: >-
101+
{"SLACK_WEBHOOK_URL": "${{ secrets.SLACK_WEBHOOK_URL }}", "PLATFORM_HOST": "${{ secrets.PLATFORM_HOST }}"}
99102
100103
- name: Post-session labels and comment
101104
if: steps.existing.outputs.skip != 'true'
@@ -205,13 +208,33 @@ jobs:
205208
- Are there merge conflicts? Resolve them.
206209
- Is CI failing? Read the logs and fix the failures.
207210
- Are there review comments (human or bot like CodeRabbit)? Address each comment.
208-
2. Push fixes.
209-
3. Ensure the PR body contains this frontmatter as the first line
211+
2. For each issue you fix, call `log_correction` to record what went wrong and how you fixed it.
212+
3. Push fixes.
213+
4. Ensure the PR body contains this frontmatter as the first line
210214
(read your session ID from the AGENTIC_SESSION_NAME environment variable):
211-
<!-- acp:session_id=$AGENTIC_SESSION_NAME source=#${{ steps.context.outputs.number }} last_action=<ISO8601_NOW> retry_count=0 -->
212-
4. Add the `ambient-code:managed` label.
213-
5. Do not merge. Do not close. Do not force-push.
214-
6. If fundamentally broken beyond repair, add a comment explaining and stop.
215+
<!-- acp:session_id=$AGENTIC_SESSION_NAME source=#${{ steps.context.outputs.number }} last_action=<ISO8601_NOW> retry_count=<N> -->
216+
Increment retry_count from whatever it was before. If retry_count reaches 3 or more,
217+
stop working, add `ambient-code:needs-human` label, remove `ambient-code:managed` label,
218+
comment "AI was unable to resolve after 3 attempts. Needs human attention.",
219+
and send a Slack notification (see below).
220+
5. Add the `ambient-code:managed` label.
221+
6. Do not merge. Do not close. Do not force-push.
222+
7. If fundamentally broken beyond repair, add a comment explaining and stop.
223+
224+
## Slack Notifications
225+
226+
When you need human attention — whether you hit the circuit breaker (3 retries),
227+
you're stuck and can't proceed, or you use the AskUserQuestion tool — send a
228+
Slack notification:
229+
230+
```bash
231+
curl -X POST -H 'Content-type: application/json' \
232+
--data '{"text":"I need human attention\n*PR*: ${{ steps.context.outputs.url }}\n*Session*: '"$PLATFORM_HOST/projects/$AGENTIC_SESSION_NAMESPACE/sessions/$AGENTIC_SESSION_NAME"'\n*Reason*: <brief reason>"}' \
233+
"$SLACK_WEBHOOK_URL"
234+
```
235+
236+
The environment variables SLACK_WEBHOOK_URL, PLATFORM_HOST, AGENTIC_SESSION_NAMESPACE,
237+
and AGENTIC_SESSION_NAME are available in your environment. Only send if SLACK_WEBHOOK_URL is set.
215238
216239
# Fix prompt on an issue: @ambient-code alone — investigate and create PR (same as fresh prompt)
217240
- name: Run fix prompt (issue)
@@ -249,6 +272,8 @@ jobs:
249272
model: claude-opus-4-6
250273
wait: 'true'
251274
timeout: '0'
275+
environment-variables: >-
276+
{"SLACK_WEBHOOK_URL": "${{ secrets.SLACK_WEBHOOK_URL }}", "PLATFORM_HOST": "${{ secrets.PLATFORM_HOST }}"}
252277
253278
# Custom prompt: @ambient-code <instruction> — pass user's text
254279
- name: Run custom prompt
@@ -272,11 +297,56 @@ jobs:
272297
model: claude-opus-4-6
273298
wait: 'true'
274299
timeout: '0'
300+
environment-variables: >-
301+
{"SLACK_WEBHOOK_URL": "${{ secrets.SLACK_WEBHOOK_URL }}", "PLATFORM_HOST": "${{ secrets.PLATFORM_HOST }}"}
302+
303+
- name: Post check run on PR
304+
if: >-
305+
always()
306+
&& steps.context.outputs.is_fork != 'true'
307+
&& steps.context.outputs.type == 'pr'
308+
env:
309+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
310+
run: |
311+
SESSION_NAME="${{ steps.fix-session.outputs.session-name || steps.fix-issue-session.outputs.session-name || steps.custom-session.outputs.session-name }}"
312+
SESSION_URL="${{ steps.fix-session.outputs.session-url || steps.fix-issue-session.outputs.session-url || steps.custom-session.outputs.session-url }}"
313+
SESSION_PHASE="${{ steps.fix-session.outputs.session-phase || steps.fix-issue-session.outputs.session-phase || steps.custom-session.outputs.session-phase }}"
314+
315+
if [ -z "$SESSION_NAME" ]; then
316+
exit 0
317+
fi
318+
319+
# Get PR head SHA
320+
HEAD_SHA=$(gh pr view ${{ steps.context.outputs.number }} --repo "${{ github.repository }}" --json headRefOid --jq '.headRefOid')
321+
322+
# Map session phase to check status/conclusion
323+
CHECK_ARGS=(
324+
-X POST
325+
-f "name=Amber Session"
326+
-f "head_sha=$HEAD_SHA"
327+
-f "details_url=$SESSION_URL"
328+
-f "output[title]=Amber — ${{ steps.context.outputs.prompt_type }} prompt"
329+
-f "output[summary]=Session \`$SESSION_NAME\` (phase: $SESSION_PHASE)"
330+
)
331+
332+
case "$SESSION_PHASE" in
333+
Running)
334+
CHECK_ARGS+=(-f "status=in_progress") ;;
335+
Completed)
336+
CHECK_ARGS+=(-f "status=completed" -f "conclusion=success") ;;
337+
Error|Failed)
338+
CHECK_ARGS+=(-f "status=completed" -f "conclusion=failure") ;;
339+
*)
340+
CHECK_ARGS+=(-f "status=completed" -f "conclusion=neutral") ;;
341+
esac
342+
343+
if ! CHECK_OUTPUT=$(gh api "repos/${{ github.repository }}/check-runs" "${CHECK_ARGS[@]}" 2>&1); then
344+
echo "::warning::Failed to create check run: $CHECK_OUTPUT"
345+
fi
275346
276347
- name: Session summary
277348
if: always() && steps.context.outputs.is_fork != 'true'
278349
run: |
279-
# Get session name from whichever step ran (only one will have output)
280350
SESSION_NAME="${{ steps.fix-session.outputs.session-name || steps.fix-issue-session.outputs.session-name || steps.custom-session.outputs.session-name }}"
281351
SESSION_PHASE="${{ steps.fix-session.outputs.session-phase || steps.fix-issue-session.outputs.session-phase || steps.custom-session.outputs.session-phase }}"
282352
@@ -305,6 +375,8 @@ jobs:
305375
AMBIENT_API_URL: ${{ secrets.AMBIENT_API_URL }}
306376
AMBIENT_API_TOKEN: ${{ secrets.AMBIENT_BOT_TOKEN }}
307377
AMBIENT_PROJECT: ${{ secrets.AMBIENT_PROJECT }}
378+
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
379+
PLATFORM_HOST: ${{ secrets.PLATFORM_HOST }}
308380
run: |
309381
pip install --quiet 'requests>=2.31.0'
310382
@@ -368,6 +440,26 @@ jobs:
368440
print(f" Failed to start session: {e}")
369441
return False
370442
443+
def post_check_run(pr_number, session_name):
444+
"""Post an in_progress check run on the PR linking to the Amber session."""
445+
head_sha = gh("pr", "view", str(pr_number), "--repo", REPO, "--json", "headRefOid", "--jq", ".headRefOid")
446+
if not head_sha:
447+
return
448+
host = os.environ.get("PLATFORM_HOST", "").rstrip("/")
449+
session_url = f"{host}/projects/{PROJECT}/sessions/{session_name}" if host else ""
450+
args = ["api", f"repos/{REPO}/check-runs",
451+
"-X", "POST",
452+
"-f", "name=Amber Session",
453+
"-f", f"head_sha={head_sha}",
454+
"-f", "status=in_progress",
455+
"-f", "output[title]=Amber — batch fix",
456+
"-f", f"output[summary]=Session `{session_name}` triggered for PR #{pr_number}"]
457+
if session_url:
458+
args.extend(["-f", f"details_url={session_url}"])
459+
proc = subprocess.run(["gh"] + list(args), capture_output=True, text=True)
460+
if proc.returncode != 0:
461+
print(f" Warning: failed to create check run for PR #{pr_number}: {proc.stderr or proc.stdout}")
462+
371463
def create_session_api(prompt, session_name="", model="claude-opus-4-6"):
372464
"""Create a new session or send message to existing one."""
373465
@@ -402,9 +494,17 @@ jobs:
402494
403495
# Create new session
404496
url = f"{API_URL.rstrip('/')}/projects/{PROJECT}/agentic-sessions"
497+
slack_url = os.environ.get("SLACK_WEBHOOK_URL", "")
498+
platform_host = os.environ.get("PLATFORM_HOST", "").rstrip("/")
499+
env_vars = {}
500+
if slack_url:
501+
env_vars["SLACK_WEBHOOK_URL"] = slack_url
502+
if platform_host:
503+
env_vars["PLATFORM_HOST"] = platform_host
405504
body = {"initialPrompt": prompt,
406505
"llmSettings": {"model": model},
407-
"repos": [{"url": f"https://github.com/{REPO}", "branch": "main"}]}
506+
"repos": [{"url": f"https://github.com/{REPO}", "branch": "main"}],
507+
**({"environmentVariables": env_vars} if env_vars else {})}
408508
try:
409509
resp = requests.post(url, headers={"Authorization": f"Bearer {API_TOKEN}", "Content-Type": "application/json"},
410510
json=body, timeout=30)
@@ -440,13 +540,6 @@ jobs:
440540
session_id = fm["session_id"]
441541
source = fm["source"]
442542
443-
# Circuit breaker
444-
if fm["retry_count"] >= 3:
445-
print(f"PR #{number}: circuit breaker (retry_count={fm['retry_count']}), adding ambient-code:needs-human")
446-
gh("pr", "edit", str(number), "--repo", REPO, "--add-label", "ambient-code:needs-human", "--remove-label", "ambient-code:managed")
447-
gh("pr", "comment", str(number), "--repo", REPO, "--body", "AI was unable to resolve issues after 3 attempts. Needs human attention.")
448-
continue
449-
450543
# Check for changes using updatedAt from gh pr list (no extra API call)
451544
updated_at = pr.get("updatedAt", "")
452545
if updated_at and updated_at <= fm["last_action"]:
@@ -457,6 +550,7 @@ jobs:
457550
# Trigger fix — reuse session if exists, create new if not
458551
print(f"PR #{number}: triggering fix (session_id={session_id or 'new'})")
459552
553+
current_retry = fm["retry_count"] if fm else 0
460554
prompt = f"""You are maintaining a pull request.
461555
462556
URL: https://github.com/{REPO}/pull/{number}
@@ -467,26 +561,31 @@ jobs:
467561
- Are there merge conflicts? Resolve them.
468562
- Is CI failing? Read the logs and fix the failures.
469563
- Are there review comments (human or bot like CodeRabbit)? Address each comment.
470-
2. Push fixes.
471-
3. Ensure the PR body contains this frontmatter as the first line
564+
2. For each issue you fix, call `log_correction` to record what went wrong and how you fixed it.
565+
3. Push fixes.
566+
4. Ensure the PR body contains this frontmatter as the first line
472567
(read your session ID from the AGENTIC_SESSION_NAME environment variable):
473-
<!-- acp:session_id=$AGENTIC_SESSION_NAME source={source} last_action=<ISO8601_NOW> retry_count=0 -->
474-
4. Add the `ambient-code:managed` label.
475-
5. Do not merge. Do not close. Do not force-push.
476-
6. If fundamentally broken beyond repair, add a comment explaining and stop."""
477-
478-
create_session_api(prompt, session_name=session_id)
479-
480-
# Increment retry_count in frontmatter so circuit breaker advances
481-
if fm:
482-
new_count = fm["retry_count"] + 1
483-
now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
484-
old_fm = f'acp:session_id={fm["session_id"]} source={fm["source"]} last_action={fm["last_action"]} retry_count={fm["retry_count"]}'
485-
new_fm = f'acp:session_id={fm["session_id"]} source={fm["source"]} last_action={now} retry_count={new_count}'
486-
new_body = body.replace(old_fm, new_fm)
487-
if new_body != body:
488-
gh("pr", "edit", str(number), "--repo", REPO, "--body", new_body)
489-
print(f" Updated frontmatter: retry_count={new_count}, last_action={now}")
568+
<!-- acp:session_id=$AGENTIC_SESSION_NAME source={source} last_action=<ISO8601_NOW> retry_count=<N> -->
569+
The current retry_count is {current_retry}. Increment it by 1.
570+
If retry_count reaches 3 or more, stop working, add `ambient-code:needs-human` label,
571+
remove `ambient-code:managed` label, comment on the PR, and send a Slack notification.
572+
5. Add the `ambient-code:managed` label.
573+
6. Do not merge. Do not close. Do not force-push.
574+
7. If fundamentally broken beyond repair, add a comment explaining and stop.
575+
576+
## Slack Notifications
577+
578+
When you need human attention — circuit breaker, stuck, or using AskUserQuestion — send:
579+
580+
curl -X POST -H 'Content-type: application/json' \\
581+
--data '{{"text":"I need human attention\\n*PR*: https://github.com/{REPO}/pull/{number}\\n*Session*: '"$PLATFORM_HOST/projects/$AGENTIC_SESSION_NAMESPACE/sessions/$AGENTIC_SESSION_NAME"'\\n*Reason*: <brief reason>"}}' \\
582+
"$SLACK_WEBHOOK_URL"
583+
584+
Only send if SLACK_WEBHOOK_URL is set."""
585+
586+
result_name = create_session_api(prompt, session_name=session_id)
587+
if result_name:
588+
post_check_run(number, result_name)
490589
491590
processed += 1
492591

0 commit comments

Comments
 (0)