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
80 changes: 41 additions & 39 deletions .github/workflows/amber-issue-handler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
# Single workflow for all Amber AI automation.
#
# TRIGGERS:
# - Issue labeled with: amber:auto-fix → fresh session prompt
# - Comment "@amber" alone on issue/PR → follow-up/fix prompt
# - Comment "@amber <instruction>" on issue/PR → custom prompt
# - Cron every 30 min → shell-driven batch for all amber:managed PRs
# - Issue labeled with: ambient-code:auto-fix → fresh session prompt
# - Comment "@ambient-code" alone on issue/PR → follow-up/fix prompt
# - Comment "@ambient-code <instruction>" on issue/PR → custom prompt
# - Cron every 30 min → shell-driven batch for all ambient-code:managed PRs
# - Manual dispatch → same as cron
#
# SESSION REUSE:
Expand All @@ -30,11 +30,11 @@ permissions:
pull-requests: write

jobs:
# -- Issue: labeled amber:auto-fix → fresh session prompt --
# -- Issue: labeled ambient-code:auto-fix → fresh session prompt --
handle-issue-label:
if: >-
github.event_name == 'issues'
&& github.event.label.name == 'amber:auto-fix'
&& github.event.label.name == 'ambient-code:auto-fix'
concurrency:
group: amber-${{ github.event.issue.number }}
cancel-in-progress: false
Expand All @@ -57,9 +57,9 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
NUMBER="${{ steps.issue.outputs.number }}"
EXISTING=$(gh pr list --repo "${{ github.repository }}" --state open --label "amber:managed" --limit 200 --json number,body --jq ".[] | select((.body // \"\") | test(\"source=#${NUMBER}(\\\\s|$)\")) | .number" | head -1 || echo "")
EXISTING=$(gh pr list --repo "${{ github.repository }}" --state open --label "ambient-code:managed" --limit 200 --json number,body --jq ".[] | select((.body // \"\") | test(\"source=#${NUMBER}(\\\\s|$)\")) | .number" | head -1 || echo "")
if [ -n "$EXISTING" ]; then
echo "Found existing amber:managed PR #$EXISTING for issue #$NUMBER — skipping"
echo "Found existing ambient-code:managed PR #$EXISTING for issue #$NUMBER — skipping"
echo "skip=true" >> $GITHUB_OUTPUT
else
echo "skip=false" >> $GITHUB_OUTPUT
Expand Down Expand Up @@ -88,14 +88,14 @@ jobs:
first line of the PR body (read your session ID from the
AGENTIC_SESSION_NAME environment variable):
<!-- acp:session_id=$AGENTIC_SESSION_NAME source=#${{ steps.issue.outputs.number }} last_action=<ISO8601_NOW> retry_count=0 -->
5. Add the `amber:managed` label to the PR.
5. Add the `ambient-code:managed` label to the PR.
6. Ensure CI passes. If it fails, investigate and fix.
7. Do not merge. Leave the PR open for human review.
repos: >-
[{"url": "https://github.com/${{ github.repository }}", "branch": "main"}]
model: claude-opus-4-6
wait: 'true'
timeout: '60'
timeout: '0'

- name: Post-session labels and comment
if: steps.existing.outputs.skip != 'true'
Expand All @@ -105,8 +105,8 @@ jobs:
SESSION_PHASE: ${{ steps.session.outputs.session-phase }}
run: |
if [ -n "$SESSION_NAME" ]; then
gh issue edit ${{ steps.issue.outputs.number }} --repo "${{ github.repository }}" --add-label "amber:triaged" || true
gh issue comment ${{ steps.issue.outputs.number }} --repo "${{ github.repository }}" --body "Session \`$SESSION_NAME\` created (phase: $SESSION_PHASE). PR will have the \`amber:managed\` label."
gh issue edit ${{ steps.issue.outputs.number }} --repo "${{ github.repository }}" --add-label "ambient-code:triaged" || true
gh issue comment ${{ steps.issue.outputs.number }} --repo "${{ github.repository }}" --body "Session \`$SESSION_NAME\` created (phase: $SESSION_PHASE). PR will have the \`ambient-code:managed\` label."
fi

- name: Session summary
Expand All @@ -124,11 +124,11 @@ jobs:
echo "- **Status**: Failed to create session" >> $GITHUB_STEP_SUMMARY
fi

# -- @amber comment on an issue or PR --
# -- @ambient-code comment on an issue or PR --
handle-comment:
if: >-
github.event_name == 'issue_comment'
&& contains(github.event.comment.body, '@amber')
&& contains(github.event.comment.body, '@ambient-code')
&& (github.event.comment.author_association == 'MEMBER'
|| github.event.comment.author_association == 'OWNER'
|| github.event.comment.author_association == 'COLLABORATOR')
Expand All @@ -143,19 +143,20 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COMMENT_BODY: ${{ github.event.comment.body }}
IS_PR: ${{ github.event.issue.pull_request && 'true' || 'false' }}
run: |
NUMBER="${{ github.event.issue.number }}"
echo "number=$NUMBER" >> $GITHUB_OUTPUT

# Determine if @amber is alone (fix prompt) or has instruction text (custom prompt)
STRIPPED=$(echo "$COMMENT_BODY" | sed 's/@amber//g' | tr -d '[:space:]')
# Determine if @ambient-code is alone (fix prompt) or has instruction text (custom prompt)
STRIPPED=$(echo "$COMMENT_BODY" | sed 's/@ambient-code//g' | tr -d '[:space:]')
if [ -z "$STRIPPED" ]; then
echo "prompt_type=fix" >> $GITHUB_OUTPUT
else
echo "prompt_type=custom" >> $GITHUB_OUTPUT
Comment on lines +151 to 156
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Reuse the parsed command text for custom prompts.

Line 152 strips the command only for classification, but Line 270 still forwards the raw comment body. @ambient-code do X therefore reaches the agent as the literal command instead of just do X.

One way to wire the parsed instruction through
-          STRIPPED=$(echo "$COMMENT_BODY" | sed 's/@ambient-code//g' | tr -d '[:space:]')
-          if [ -z "$STRIPPED" ]; then
+          COMMAND_TEXT=$(printf '%s' "$COMMENT_BODY" | sed -E 's/^[[:space:]]*@ambient-code[[:space:]]*//')
+          if [ -z "$(printf '%s' "$COMMAND_TEXT" | tr -d '[:space:]')" ]; then
             echo "prompt_type=fix" >> $GITHUB_OUTPUT
           else
             echo "prompt_type=custom" >> $GITHUB_OUTPUT
           fi
+          {
+            echo "command_text<<EOF"
+            printf '%s\n' "$COMMAND_TEXT"
+            echo "EOF"
+          } >> $GITHUB_OUTPUT
...
-            ${{ github.event.comment.body }}
+            ${{ steps.context.outputs.command_text }}

Also applies to: 266-270

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/amber-issue-handler.yml around lines 151 - 156, The
workflow strips "@ambient-code" into STRIPPED for classification but still
forwards the raw COMMENT_BODY to the agent; change the plumbing so the parsed
instruction (e.g., STRIPPED or a new variable like PARSED_INSTRUCTION) is
exported and used wherever the raw comment is currently forwarded (the block
that writes the agent input/output currently using COMMENT_BODY). Update the
existing strip logic that produces STRIPPED to trim leading/trailing whitespace
and export that value to GITHUB_OUTPUT (e.g., instruction_text) and replace
references that pass COMMENT_BODY (the forwarding around the prompt_type/custom
prompt code) to use the exported parsed value instead.

fi

if [ -n "${{ github.event.issue.pull_request }}" ]; then
if [ "$IS_PR" = "true" ]; then
echo "type=pr" >> $GITHUB_OUTPUT
echo "url=https://github.com/${{ github.repository }}/pull/$NUMBER" >> $GITHUB_OUTPUT

Expand All @@ -171,8 +172,8 @@ jobs:
echo "url=https://github.com/${{ github.repository }}/issues/$NUMBER" >> $GITHUB_OUTPUT
echo "is_fork=false" >> $GITHUB_OUTPUT

# Check for existing amber:managed PR for this issue and get its session ID
EXISTING_PR=$(gh pr list --repo "${{ github.repository }}" --state open --label "amber:managed" --limit 200 --json number,body --jq ".[] | select((.body // \"\") | test(\"source=#${NUMBER}(\\\\s|$)\"))" | head -1 || echo "")
# Check for existing ambient-code:managed PR for this issue and get its session ID
EXISTING_PR=$(gh pr list --repo "${{ github.repository }}" --state open --label "ambient-code:managed" --limit 200 --json number,body --jq ".[] | select((.body // \"\") | test(\"source=#${NUMBER}(\\\\s|$)\"))" | head -1 || echo "")
if [ -n "$EXISTING_PR" ]; then
SESSION_ID=$(echo "$EXISTING_PR" | jq -r '.body' | grep -oP 'acp:session_id=\K[^ ]+' | head -1 || echo "")
echo "session_id=$SESSION_ID" >> $GITHUB_OUTPUT
Expand All @@ -181,7 +182,7 @@ jobs:
fi
fi

# Fix prompt on a PR: @amber alone — assess and fix CI/conflicts/reviews
# Fix prompt on a PR: @ambient-code alone — assess and fix CI/conflicts/reviews
- name: Run fix prompt (PR)
if: >-
steps.context.outputs.is_fork != 'true'
Expand Down Expand Up @@ -209,11 +210,11 @@ jobs:
3. 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 `amber:managed` label.
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.

# Fix prompt on an issue: @amber alone — investigate and create PR (same as fresh prompt)
# Fix prompt on an issue: @ambient-code alone — investigate and create PR (same as fresh prompt)
- name: Run fix prompt (issue)
if: >-
steps.context.outputs.is_fork != 'true'
Expand Down Expand Up @@ -241,16 +242,16 @@ jobs:
first line of the PR body (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 -->
5. Add the `amber:managed` label to the PR.
5. Add the `ambient-code:managed` label to the PR.
6. Ensure CI passes. If it fails, investigate and fix.
7. Do not merge. Leave the PR open for human review.
repos: >-
[{"url": "https://github.com/${{ github.repository }}", "branch": "main"}]
model: claude-opus-4-6
wait: 'true'
timeout: '60'
timeout: '0'

# Custom prompt: @amber <instruction> — pass user's text
# Custom prompt: @ambient-code <instruction> — pass user's text
- name: Run custom prompt
if: >-
steps.context.outputs.is_fork != 'true'
Expand All @@ -271,14 +272,14 @@ jobs:
[{"url": "https://github.com/${{ github.repository }}", "branch": "main"}]
model: claude-opus-4-6
wait: 'true'
timeout: '60'
timeout: '0'

- name: Session summary
if: always() && steps.context.outputs.is_fork != 'true'
run: |
# Get session name from whichever step ran
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 }}"
# 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 }}"

echo "### Amber — ${{ steps.context.outputs.type }} #${{ steps.context.outputs.number }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
Expand All @@ -290,7 +291,7 @@ jobs:
echo "- **Status**: Failed to create session" >> $GITHUB_STEP_SUMMARY
fi

# -- Batch: manage all amber:managed PRs (shell-driven) --
# -- Batch: manage all ambient-code:managed PRs (shell-driven) --
batch-pr-fixer:
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
concurrency:
Expand All @@ -299,7 +300,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- name: Find and process amber:managed PRs
- name: Find and process ambient-code:managed PRs
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
AMBIENT_API_URL: ${{ secrets.AMBIENT_API_URL }}
Expand All @@ -313,6 +314,8 @@ jobs:
import os
import re
import subprocess
import time
import uuid
import requests
from datetime import datetime, timezone

Expand Down Expand Up @@ -368,7 +371,6 @@ jobs:

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

if session_name:
# Ensure session is running
Expand Down Expand Up @@ -401,7 +403,7 @@ jobs:

# Create new session
url = f"{API_URL.rstrip('/')}/projects/{PROJECT}/agentic-sessions"
body = {"initialPrompt": prompt, "inactivityTimeout": 60,
body = {"initialPrompt": prompt,
"llmSettings": {"model": model},
"repos": [{"url": f"https://github.com/{REPO}", "branch": "main"}]}
Comment on lines +406 to 408
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Omitting inactivityTimeout here does not disable inactivity stopping.

At Line 405, the create-session payload now omits inactivityTimeout; with current operator behavior this falls back to the default inactivity timeout (24h). If the goal is “no inactivity timer,” this change does not implement that behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/amber-issue-handler.yml around lines 405 - 407, The
create-session payload currently built into the body variable (containing
initialPrompt, llmSettings, repos) omits inactivityTimeout so the operator uses
the default 24h; to disable inactivity stopping add an explicit
inactivityTimeout field to the payload (e.g., "inactivityTimeout": 0 or the
operator-supported sentinel) when constructing body so the session is created
with no inactivity timer. Make the change where body is defined (next to
initialPrompt/llmSettings/repos) so the operator receives the explicit setting.

try:
Expand All @@ -415,12 +417,12 @@ jobs:
print(f" Failed to create session: {e}")
return None

# Get all open amber:managed PRs (include updatedAt to avoid per-PR API calls)
# Get all open ambient-code:managed PRs (include updatedAt to avoid per-PR API calls)
prs_json = gh("pr", "list", "--repo", REPO, "--state", "open",
"--label", "amber:managed", "--limit", "200",
"--label", "ambient-code:managed", "--limit", "200",
"--json", "number,body,title,updatedAt")
prs = json.loads(prs_json) if prs_json else []
print(f"Found {len(prs)} amber:managed PRs")
print(f"Found {len(prs)} ambient-code:managed PRs")

processed = 0
skipped = 0
Expand All @@ -441,8 +443,8 @@ jobs:

# Circuit breaker
if fm["retry_count"] >= 3:
print(f"PR #{number}: circuit breaker (retry_count={fm['retry_count']}), adding amber:needs-human")
gh("pr", "edit", str(number), "--repo", REPO, "--add-label", "amber:needs-human", "--remove-label", "amber:managed")
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

Expand Down Expand Up @@ -470,7 +472,7 @@ jobs:
3. 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 `amber:managed` label.
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."""

Expand Down
Loading