Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 133 additions & 34 deletions .github/workflows/amber-issue-handler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ permissions:
contents: read
issues: write
pull-requests: write
checks: write

jobs:
# -- Issue: labeled ambient-code:auto-fix → fresh session prompt --
Expand Down Expand Up @@ -96,6 +97,8 @@ jobs:
model: claude-opus-4-6
wait: 'true'
timeout: '0'
environment-variables: >-
{"SLACK_WEBHOOK_URL": "${{ secrets.SLACK_WEBHOOK_URL }}", "PLATFORM_HOST": "${{ secrets.PLATFORM_HOST }}"}

- name: Post-session labels and comment
if: steps.existing.outputs.skip != 'true'
Expand Down Expand Up @@ -206,13 +209,33 @@ jobs:
- Are there merge conflicts? Resolve them.
- Is CI failing? Read the logs and fix the failures.
- Are there review comments (human or bot like CodeRabbit)? Address each comment.
2. Push fixes.
3. Ensure the PR body contains this frontmatter as the first line
2. For each issue you fix, call `log_correction` to record what went wrong and how you fixed it.
3. Push fixes.
4. Ensure the PR body contains this frontmatter as the first line
(read your session ID from the AGENTIC_SESSION_NAME environment variable):
<!-- acp:session_id=$AGENTIC_SESSION_NAME source=#${{ steps.context.outputs.number }} last_action=<ISO8601_NOW> retry_count=0 -->
4. Add the `ambient-code:managed` label.
5. Do not merge. Do not close. Do not force-push.
6. If fundamentally broken beyond repair, add a comment explaining and stop.
<!-- acp:session_id=$AGENTIC_SESSION_NAME source=#${{ steps.context.outputs.number }} last_action=<ISO8601_NOW> retry_count=<N> -->
Increment retry_count from whatever it was before. If retry_count reaches 3 or more,
stop working, add `ambient-code:needs-human` label, remove `ambient-code:managed` label,
comment "AI was unable to resolve after 3 attempts. Needs human attention.",
and send a Slack notification (see below).
5. Add the `ambient-code:managed` label.
6. Do not merge. Do not close. Do not force-push.
7. If fundamentally broken beyond repair, add a comment explaining and stop.

## Slack Notifications

When you need human attention — whether you hit the circuit breaker (3 retries),
you're stuck and can't proceed, or you use the AskUserQuestion tool — send a
Slack notification:

```bash
curl -X POST -H 'Content-type: application/json' \
--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>"}' \
"$SLACK_WEBHOOK_URL"
```

The environment variables SLACK_WEBHOOK_URL, PLATFORM_HOST, AGENTIC_SESSION_NAMESPACE,
and AGENTIC_SESSION_NAME are available in your environment. Only send if SLACK_WEBHOOK_URL is set.

# Fix prompt on an issue: @ambient-code alone — investigate and create PR (same as fresh prompt)
- name: Run fix prompt (issue)
Expand Down Expand Up @@ -250,6 +273,8 @@ jobs:
model: claude-opus-4-6
wait: 'true'
timeout: '0'
environment-variables: >-
{"SLACK_WEBHOOK_URL": "${{ secrets.SLACK_WEBHOOK_URL }}", "PLATFORM_HOST": "${{ secrets.PLATFORM_HOST }}"}

# Custom prompt: @ambient-code <instruction> — pass user's text
- name: Run custom prompt
Expand All @@ -273,11 +298,56 @@ jobs:
model: claude-opus-4-6
wait: 'true'
timeout: '0'
environment-variables: >-
{"SLACK_WEBHOOK_URL": "${{ secrets.SLACK_WEBHOOK_URL }}", "PLATFORM_HOST": "${{ secrets.PLATFORM_HOST }}"}

- name: Post check run on PR
if: >-
always()
&& steps.context.outputs.is_fork != 'true'
&& steps.context.outputs.type == 'pr'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
SESSION_NAME="${{ steps.fix-session.outputs.session-name || steps.fix-issue-session.outputs.session-name || steps.custom-session.outputs.session-name }}"
SESSION_URL="${{ steps.fix-session.outputs.session-url || steps.fix-issue-session.outputs.session-url || steps.custom-session.outputs.session-url }}"
SESSION_PHASE="${{ steps.fix-session.outputs.session-phase || steps.fix-issue-session.outputs.session-phase || steps.custom-session.outputs.session-phase }}"

if [ -z "$SESSION_NAME" ]; then
exit 0
fi

# Get PR head SHA
HEAD_SHA=$(gh pr view ${{ steps.context.outputs.number }} --repo "${{ github.repository }}" --json headRefOid --jq '.headRefOid')

# Map session phase to check status/conclusion
CHECK_ARGS=(
-X POST
-f "name=Amber Session"
-f "head_sha=$HEAD_SHA"
-f "details_url=$SESSION_URL"
-f "output[title]=Amber — ${{ steps.context.outputs.prompt_type }} prompt"
-f "output[summary]=Session \`$SESSION_NAME\` (phase: $SESSION_PHASE)"
)

case "$SESSION_PHASE" in
Running)
CHECK_ARGS+=(-f "status=in_progress") ;;
Completed)
CHECK_ARGS+=(-f "status=completed" -f "conclusion=success") ;;
Error|Failed)
CHECK_ARGS+=(-f "status=completed" -f "conclusion=failure") ;;
*)
CHECK_ARGS+=(-f "status=completed" -f "conclusion=neutral") ;;
esac

if ! CHECK_OUTPUT=$(gh api "repos/${{ github.repository }}/check-runs" "${CHECK_ARGS[@]}" 2>&1); then
echo "::warning::Failed to create check run: $CHECK_OUTPUT"
fi

- name: Session summary
if: always() && steps.context.outputs.is_fork != 'true'
run: |
# Get session name from whichever step ran (only one will have output)
SESSION_NAME="${{ steps.fix-session.outputs.session-name || steps.fix-issue-session.outputs.session-name || steps.custom-session.outputs.session-name }}"
SESSION_PHASE="${{ steps.fix-session.outputs.session-phase || steps.fix-issue-session.outputs.session-phase || steps.custom-session.outputs.session-phase }}"

Expand Down Expand Up @@ -306,6 +376,8 @@ jobs:
AMBIENT_API_URL: ${{ secrets.AMBIENT_API_URL }}
AMBIENT_API_TOKEN: ${{ secrets.AMBIENT_BOT_TOKEN }}
AMBIENT_PROJECT: ${{ secrets.AMBIENT_PROJECT }}
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
PLATFORM_HOST: ${{ secrets.PLATFORM_HOST }}
run: |
pip install --quiet 'requests>=2.31.0'

Expand Down Expand Up @@ -369,6 +441,26 @@ jobs:
print(f" Failed to start session: {e}")
return False

def post_check_run(pr_number, session_name):
"""Post an in_progress check run on the PR linking to the Amber session."""
head_sha = gh("pr", "view", str(pr_number), "--repo", REPO, "--json", "headRefOid", "--jq", ".headRefOid")
if not head_sha:
return
host = os.environ.get("PLATFORM_HOST", "").rstrip("/")
session_url = f"{host}/projects/{PROJECT}/sessions/{session_name}" if host else ""
args = ["api", f"repos/{REPO}/check-runs",
"-X", "POST",
"-f", "name=Amber Session",
"-f", f"head_sha={head_sha}",
"-f", "status=in_progress",
"-f", "output[title]=Amber — batch fix",
"-f", f"output[summary]=Session `{session_name}` triggered for PR #{pr_number}"]
if session_url:
args.extend(["-f", f"details_url={session_url}"])
proc = subprocess.run(["gh"] + list(args), capture_output=True, text=True)
if proc.returncode != 0:
print(f" Warning: failed to create check run for PR #{pr_number}: {proc.stderr or proc.stdout}")

def create_session_api(prompt, session_name="", model="claude-opus-4-6"):
"""Create a new session or send message to existing one."""

Expand Down Expand Up @@ -403,9 +495,17 @@ jobs:

# Create new session
url = f"{API_URL.rstrip('/')}/projects/{PROJECT}/agentic-sessions"
slack_url = os.environ.get("SLACK_WEBHOOK_URL", "")
platform_host = os.environ.get("PLATFORM_HOST", "").rstrip("/")
env_vars = {}
if slack_url:
env_vars["SLACK_WEBHOOK_URL"] = slack_url
if platform_host:
env_vars["PLATFORM_HOST"] = platform_host
body = {"initialPrompt": prompt,
"llmSettings": {"model": model},
"repos": [{"url": f"https://github.com/{REPO}", "branch": "main"}]}
"repos": [{"url": f"https://github.com/{REPO}", "branch": "main"}],
**({"environmentVariables": env_vars} if env_vars else {})}
try:
resp = requests.post(url, headers={"Authorization": f"Bearer {API_TOKEN}", "Content-Type": "application/json"},
json=body, timeout=30)
Expand Down Expand Up @@ -441,13 +541,6 @@ jobs:
session_id = fm["session_id"]
source = fm["source"]

# Circuit breaker
if fm["retry_count"] >= 3:
print(f"PR #{number}: circuit breaker (retry_count={fm['retry_count']}), adding ambient-code:needs-human")
gh("pr", "edit", str(number), "--repo", REPO, "--add-label", "ambient-code:needs-human", "--remove-label", "ambient-code:managed")
gh("pr", "comment", str(number), "--repo", REPO, "--body", "AI was unable to resolve issues after 3 attempts. Needs human attention.")
continue

# Check for changes using updatedAt from gh pr list (no extra API call)
updated_at = pr.get("updatedAt", "")
if updated_at and updated_at <= fm["last_action"]:
Expand All @@ -458,6 +551,7 @@ jobs:
# Trigger fix — reuse session if exists, create new if not
print(f"PR #{number}: triggering fix (session_id={session_id or 'new'})")

current_retry = fm["retry_count"] if fm else 0
prompt = f"""You are maintaining a pull request.

URL: https://github.com/{REPO}/pull/{number}
Expand All @@ -468,26 +562,31 @@ jobs:
- Are there merge conflicts? Resolve them.
- Is CI failing? Read the logs and fix the failures.
- Are there review comments (human or bot like CodeRabbit)? Address each comment.
2. Push fixes.
3. Ensure the PR body contains this frontmatter as the first line
2. For each issue you fix, call `log_correction` to record what went wrong and how you fixed it.
3. Push fixes.
4. Ensure the PR body contains this frontmatter as the first line
(read your session ID from the AGENTIC_SESSION_NAME environment variable):
<!-- acp:session_id=$AGENTIC_SESSION_NAME source={source} last_action=<ISO8601_NOW> retry_count=0 -->
4. Add the `ambient-code:managed` label.
5. Do not merge. Do not close. Do not force-push.
6. If fundamentally broken beyond repair, add a comment explaining and stop."""

create_session_api(prompt, session_name=session_id)

# Increment retry_count in frontmatter so circuit breaker advances
if fm:
new_count = fm["retry_count"] + 1
now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
old_fm = f'acp:session_id={fm["session_id"]} source={fm["source"]} last_action={fm["last_action"]} retry_count={fm["retry_count"]}'
new_fm = f'acp:session_id={fm["session_id"]} source={fm["source"]} last_action={now} retry_count={new_count}'
new_body = body.replace(old_fm, new_fm)
if new_body != body:
gh("pr", "edit", str(number), "--repo", REPO, "--body", new_body)
print(f" Updated frontmatter: retry_count={new_count}, last_action={now}")
<!-- acp:session_id=$AGENTIC_SESSION_NAME source={source} last_action=<ISO8601_NOW> retry_count=<N> -->
The current retry_count is {current_retry}. Increment it by 1.
If retry_count reaches 3 or more, stop working, add `ambient-code:needs-human` label,
remove `ambient-code:managed` label, comment on the PR, and send a Slack notification.
5. Add the `ambient-code:managed` label.
6. Do not merge. Do not close. Do not force-push.
7. If fundamentally broken beyond repair, add a comment explaining and stop.

## Slack Notifications

When you need human attention — circuit breaker, stuck, or using AskUserQuestion — send:

curl -X POST -H 'Content-type: application/json' \\
--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>"}}' \\
"$SLACK_WEBHOOK_URL"

Only send if SLACK_WEBHOOK_URL is set."""

result_name = create_session_api(prompt, session_name=session_id)
if result_name:
post_check_run(number, result_name)

processed += 1

Expand Down
Loading