11name : PR Fixer
22
33on :
4- # Immediate: when a PR gets the agent-managed label
5- pull_request :
6- types : [labeled]
7-
84 # Immediate: when someone comments @ambient-fix on a PR
95 issue_comment :
106 types : [created]
117
12- # Cadence: every 4 hours including overnight
8+ # Cadence: every 30 minutes on weekdays
139 schedule :
14- - cron : ' 0 */4 * * 1-5'
10+ - cron : ' */30 * * * 1-5'
1511
16- # Manual: for one-off fixes
12+ # Manual: for batch run
1713 workflow_dispatch :
18- inputs :
19- pr_number :
20- description : ' PR number to fix'
21- required : true
22- type : number
2314
2415permissions :
2516 contents : read
@@ -29,29 +20,18 @@ jobs:
2920 # -- Single PR: triggered by label, comment, or manual dispatch --
3021 fix-single :
3122 if : >-
32- (github.event_name == 'pull_request'
33- && github.event.label.name == 'agent-managed'
34- && !github.event.pull_request.head.repo.fork)
35- || github.event_name == 'workflow_dispatch'
36- || (github.event_name == 'issue_comment'
37- && github.event.issue.pull_request
38- && contains(github.event.comment.body, '@ambient-fix')
39- && (github.event.comment.author_association == 'MEMBER'
40- || github.event.comment.author_association == 'OWNER'
41- || github.event.comment.author_association == 'COLLABORATOR'))
23+ github.event_name == 'issue_comment'
24+ && github.event.issue.pull_request
25+ && contains(github.event.comment.body, '@ambient-fix')
26+ && (github.event.comment.author_association == 'MEMBER'
27+ || github.event.comment.author_association == 'OWNER'
28+ || github.event.comment.author_association == 'COLLABORATOR')
4229 runs-on : ubuntu-latest
4330 timeout-minutes : 30
4431 steps :
4532 - name : Resolve PR number
4633 id : pr
47- run : |
48- if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
49- echo "number=${{ inputs.pr_number }}" >> $GITHUB_OUTPUT
50- elif [ "${{ github.event_name }}" = "issue_comment" ]; then
51- echo "number=${{ github.event.issue.number }}" >> $GITHUB_OUTPUT
52- else
53- echo "number=${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT
54- fi
34+ run : echo "number=${{ github.event.issue.number }}" >> $GITHUB_OUTPUT
5535
5636 - name : Check PR is not a fork (issue_comment)
5737 if : github.event_name == 'issue_comment'
@@ -67,26 +47,53 @@ jobs:
6747 echo "skip=false" >> $GITHUB_OUTPUT
6848 fi
6949
50+ - name : Check for existing session
51+ if : steps.fork_check.outputs.skip != 'true'
52+ id : existing
53+ env :
54+ GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
55+ run : |
56+ # Read PR body and extract session_id from frontmatter
57+ BODY=$(gh pr view ${{ steps.pr.outputs.number }} --repo "${{ github.repository }}" --json body --jq '.body')
58+ SESSION_ID=$(echo "$BODY" | grep -oP 'acp:session_id=\K[^ ]+' | head -1 || echo "")
59+ SOURCE=$(echo "$BODY" | grep -oP 'source=\K[^ ]+' | head -1 || echo "")
60+ echo "session_id=$SESSION_ID" >> $GITHUB_OUTPUT
61+ echo "source=$SOURCE" >> $GITHUB_OUTPUT
62+ if [ -n "$SESSION_ID" ]; then
63+ echo "Found existing session: $SESSION_ID"
64+ else
65+ echo "No existing session found — will create new one"
66+ fi
67+
7068 - name : Fix PR
7169 if : steps.fork_check.outputs.skip != 'true'
7270 id : session
73- uses : ambient-code/ambient-action@v0.0.2
71+ uses : ambient-code/ambient-action@v0.0.3
7472 with :
7573 api-url : ${{ secrets.AMBIENT_API_URL }}
7674 api-token : ${{ secrets.AMBIENT_BOT_TOKEN }}
7775 project : ${{ secrets.AMBIENT_PROJECT }}
78- prompt : >-
79- Fix PR #${{ steps.pr.outputs.number }} in https://github.com/${{ github.repository }}.
80- Fetch all PR data, rebase to resolve conflicts, evaluate reviewer
81- comments (fix valid issues, respond to invalid ones), run lints
82- and tests, and push the fixes.
76+ prompt : |
77+ You are maintaining open pull request #${{ steps.pr.outputs.number }} in https://github.com/${{ github.repository }}.
78+
79+ ## Instructions
80+
81+ 1. Check out the PR branch.
82+ 2. Assess the current state:
83+ - Are there merge conflicts? Resolve them.
84+ - Is CI failing? Read the logs and fix the failures.
85+ - Are there review comments (human or bot like CodeRabbit)? Address each comment.
86+ 3. Push fixes to the PR branch.
87+ 4. Ensure the PR body contains this frontmatter as the first line
88+ (read your session ID from the AGENTIC_SESSION_NAME environment variable):
89+ <!-- acp:session_id=$AGENTIC_SESSION_NAME source=${{ steps.existing.outputs.source || 'unknown' }} last_action=<ISO8601_NOW> retry_count=0 -->
90+ 5. Do not merge. Do not close. Do not force-push.
91+ 6. If the PR is fundamentally broken beyond repair, add a comment explaining the situation and stop.
8392 repos : >-
8493 [{"url": "https://github.com/${{ github.repository }}", "branch": "main"}]
85- workflow : >-
86- {"gitUrl": "https://github.com/ambient-code/workflows", "branch": "main", "path": "internal-workflows/pr-fixer"}
87- model : claude-sonnet-4-5
94+ model : claude-opus-4-6
8895 wait : ' true'
89- timeout : ' 25 '
96+ timeout : ' 60 '
9097
9198 - name : Session summary
9299 if : always() && steps.fork_check.outputs.skip != 'true'
@@ -105,106 +112,88 @@ jobs:
105112 echo "- **Status**: Failed to create session" >> $GITHUB_STEP_SUMMARY
106113 fi
107114
108- # -- Batch: scheduled cadence for all agent -managed PRs --
115+ # -- Batch: scheduled cadence for all ai -managed PRs --
109116 fix-batch :
110- if : github.event_name == 'schedule'
111- runs-on : ubuntu-latest
112- timeout-minutes : 10
113- outputs :
114- pr_numbers : ${{ steps.find.outputs.pr_numbers }}
115- steps :
116- - name : Find agent-managed PRs that need work
117- id : find
118- env :
119- GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
120- run : |
121- # Get all open PRs with agent-managed label, including last committer
122- PRS=$(gh pr list --repo "${{ github.repository }}" \
123- --label "agent-managed" \
124- --state open \
125- --json number,updatedAt,isCrossRepository \
126- --limit 20)
127-
128- # Filter: updated in the last 4 hours, not a fork,
129- # and last update wasn't by the fixer bot itself
130- NEEDS_WORK=$(echo "$PRS" | python3 -c "
131- import json, sys
132- from datetime import datetime, timezone, timedelta
133-
134- prs = json.load(sys.stdin)
135- cutoff = datetime.now(timezone.utc) - timedelta(hours=4)
136- active = []
137- for pr in prs:
138- # Skip fork PRs
139- if pr.get('isCrossRepository', False):
140- continue
141- updated = pr.get('updatedAt', '')
142- if updated:
143- try:
144- dt = datetime.fromisoformat(updated.replace('Z', '+00:00'))
145- if dt > cutoff:
146- active.append(pr['number'])
147- except Exception:
148- active.append(pr['number'])
149- print(json.dumps(active))
150- ")
151-
152- echo "pr_numbers=$NEEDS_WORK" >> $GITHUB_OUTPUT
153- COUNT=$(echo "$NEEDS_WORK" | python3 -c "import json,sys; print(len(json.load(sys.stdin)))")
154- echo "Found $COUNT agent-managed PRs with recent activity"
155-
156- fix-each :
157- needs : fix-batch
158- if : needs.fix-batch.outputs.pr_numbers != '[]'
117+ if : github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
159118 runs-on : ubuntu-latest
160- timeout-minutes : 30
161- strategy :
162- matrix :
163- pr_number : ${{ fromJson(needs.fix-batch.outputs.pr_numbers) }}
119+ timeout-minutes : 45
164120 steps :
165- - name : Skip if last update was by the fixer
166- id : churn_check
167- env :
168- GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
169- run : |
170- # Check if the most recent comment is from the fixer bot
171- LAST_AUTHOR=$(gh api "repos/${{ github.repository }}/issues/${{ matrix.pr_number }}/comments" \
172- --jq 'last | .body // ""' 2>/dev/null || echo "")
173- if echo "$LAST_AUTHOR" | grep -q '<!-- pr-fixer-bot -->'; then
174- echo "Last activity was fixer bot — skipping"
175- echo "skip=true" >> $GITHUB_OUTPUT
176- else
177- echo "skip=false" >> $GITHUB_OUTPUT
178- fi
179-
180- - name : Fix PR # ${{ matrix.pr_number }}
181- if : steps.churn_check.outputs.skip != 'true'
121+ - name : Run PR fixer orchestrator
182122 id : session
183- uses : ambient-code/ambient-action@v0.0.2
123+ uses : ambient-code/ambient-action@v0.0.3
184124 with :
185125 api-url : ${{ secrets.AMBIENT_API_URL }}
186126 api-token : ${{ secrets.AMBIENT_BOT_TOKEN }}
187127 project : ${{ secrets.AMBIENT_PROJECT }}
188- prompt : >-
189- Fix PR #${{ matrix.pr_number }} in https://github.com/${{ github.repository }}.
190- Fetch all PR data, rebase to resolve conflicts, evaluate reviewer
191- comments (fix valid issues, respond to invalid ones), run lints
192- and tests, and push the fixes.
128+ prompt : |
129+ You are a PR fixer orchestrator. Manage all open ai-managed PRs.
130+
131+ ## Find PRs
132+
133+ Run: gh pr list --repo ${{ github.repository }} --state open --label ai-managed --search "draft:false"
134+
135+ For each PR:
136+
137+ ## 1. Read frontmatter
138+ Parse <!-- acp:session_id=<ID> source=<KEY> last_action=<TIMESTAMP> retry_count=<N> --> from the PR body.
139+ If no frontmatter: create a new Fix PR session, update the PR body with frontmatter. Continue.
140+
141+ ## 2. Circuit breaker
142+ If retry_count >= 3: comment "AI was unable to resolve after 3 attempts. Needs human attention.",
143+ add ai-needs-human label, remove ai-managed label. Skip.
144+
145+ ## 3. Check for changes since last_action
146+ Ignore commits authored by the bot. Only look for:
147+ - New commits by someone other than the bot
148+ - New or updated review comments
149+ - New CI failures
150+ - Merge conflicts from base branch changes
151+
152+ If nothing changed → skip entirely.
153+
154+ ## 4. Something changed — act
155+ - CI failing → send message to existing session with CI logs
156+ - New review comments → send message with the comments
157+ - Merge conflicts → send message to rebase
158+ - New external commits → send message to review and ensure CI passes
159+
160+ ## 5. Session management
161+ Before sending a message, check session status:
162+ - Running → send the message
163+ - Stopped → restart (reuse), then send
164+ - Not found → create new session with this prompt:
165+ "You are maintaining an open pull request.
166+ PR: <URL> Source issue: <KEY> (if known)
167+ 1. Check out the PR branch.
168+ 2. Resolve merge conflicts, fix CI failures, address review comments.
169+ 3. Push fixes. Do not merge/close/force-push.
170+ 4. Write frontmatter: <!-- acp:session_id=$AGENTIC_SESSION_NAME source=<KEY> last_action=<NOW> retry_count=0 -->
171+ 5. If broken beyond repair, comment and stop."
172+
173+ After sending: increment retry_count and update last_action in frontmatter.
174+
175+ ## Rules
176+ - No-op if nothing changed. Do not interact with the session.
177+ - Use real ACP session IDs from AGENTIC_SESSION_NAME env var.
178+ - Send messages to existing sessions, don't recreate.
179+ - Ignore bot's own commits.
180+ - Do not merge PRs.
181+
182+ All child sessions use model claude-opus-4-6.
193183 repos : >-
194184 [{"url": "https://github.com/${{ github.repository }}", "branch": "main"}]
195- workflow : >-
196- {"gitUrl": "https://github.com/ambient-code/workflows", "branch": "main", "path": "internal-workflows/pr-fixer"}
197- model : claude-sonnet-4-5
198- wait : ' false'
185+ model : claude-opus-4-6
186+ wait : ' true'
187+ timeout : ' 60'
199188
200189 - name : Session summary
201- if : always() && steps.churn_check.outputs.skip != 'true'
190+ if : always()
202191 env :
203192 SESSION_NAME : ${{ steps.session.outputs.session-name }}
204193 SESSION_UID : ${{ steps.session.outputs.session-uid }}
205194 SESSION_PHASE : ${{ steps.session.outputs.session-phase }}
206195 run : |
207- echo "### PR Fixer — #${{ matrix.pr_number }} " >> $GITHUB_STEP_SUMMARY
196+ echo "### PR Fixer — Batch " >> $GITHUB_STEP_SUMMARY
208197 echo "" >> $GITHUB_STEP_SUMMARY
209198 if [ -n "$SESSION_NAME" ]; then
210199 echo "- **Session**: \`$SESSION_NAME\`" >> $GITHUB_STEP_SUMMARY
0 commit comments