|
1 | 1 | # Copilot triages and fixes an issue, or works on a task given by hand, without write credentials. |
2 | | -# Fresh jobs post the run link on the issue, open the completed fix as a draft PR using the app, so |
3 | | -# CI runs on it, and comment on the issue. The steps that are the same for every Playwright repo are |
4 | | -# actions in microsoft/playwright. The skill in .claude/skills/playwright-triage/ has what is |
5 | | -# specific to this repo. |
| 2 | +# The agent leaves the calls to make on GitHub in calls.json. A fresh job posts the run link on the |
| 3 | +# issue first. After the agent, a fresh job checks the calls and makes them using the app: it opens |
| 4 | +# the fix as a draft PR, so CI runs on it, and comments on the issue. The steps that are the same |
| 5 | +# for every Playwright repo are actions in microsoft/playwright. The skill in |
| 6 | +# .claude/skills/playwright-triage/ has what is specific to this repo. |
6 | 7 | name: Issue triage |
7 | 8 |
|
8 | 9 | on: |
|
86 | 87 | base: ${{ steps.base.outputs.sha }} |
87 | 88 | setup: ${{ steps.setup.outcome }} |
88 | 89 | agent: ${{ steps.agent.outputs.outcome }} |
89 | | - has_fix: ${{ steps.agent.outputs.has-result }} |
90 | 90 | steps: |
91 | 91 | - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 |
92 | 92 | with: |
@@ -130,98 +130,78 @@ jobs: |
130 | 130 | with: |
131 | 131 | prompt: ${{ steps.prompt.outputs.prompt }} |
132 | 132 | output-dir: ${{ env.OUTPUT_DIR }} |
133 | | - result-file: fix/proposal.json |
| 133 | + result-file: calls.json |
134 | 134 | artifact-name: triage |
135 | 135 | github-tools: true |
136 | 136 | exec: ${{ env.CI_ENV_EXEC }} |
137 | | - - name: Check the upstream bug report |
138 | | - run: | |
139 | | - if [[ -f "$OUTPUT_DIR/upstream/report.json" ]]; then |
140 | | - title=$(node -p 'String(JSON.parse(require("fs").readFileSync(process.argv[1], "utf8")).title ?? "")' "$OUTPUT_DIR/upstream/report.json" || true) |
141 | | - if [[ -z "$title" ]]; then |
142 | | - echo "::warning::upstream/report.json has no title, so the report is not appended to the comment" |
143 | | - else |
144 | | - echo "Upstream bug report: $title" >> "$GITHUB_STEP_SUMMARY" |
145 | | - fi |
146 | | - fi |
147 | 137 |
|
148 | 138 | publish: |
149 | | - name: Open draft pull request |
150 | | - needs: triage |
151 | | - # proposal.json is written last, so a finished proposal is published even when the job failed later. |
152 | | - if: ${{ !cancelled() && needs.triage.outputs.has_fix == 'true' }} |
| 139 | + name: Publish the results |
| 140 | + needs: [start, triage] |
| 141 | + # Runs after every triage run, also a failed one: without a comment from the agent, the comment |
| 142 | + # says why the bot could not finish. |
| 143 | + if: ${{ !cancelled() && needs.triage.result != 'skipped' }} |
153 | 144 | runs-on: ubuntu-latest |
154 | | - outputs: |
155 | | - pr: ${{ steps.publish.outputs.pull-request }} |
156 | 145 | steps: |
157 | 146 | - uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0 |
158 | 147 | id: app-token |
159 | 148 | with: |
160 | 149 | app-id: ${{ vars.PLAYWRIGHT_APP_ID }} |
161 | 150 | private-key: ${{ secrets.PLAYWRIGHT_PRIVATE_KEY }} |
| 151 | + permission-contents: write |
| 152 | + permission-issues: write |
| 153 | + permission-pull-requests: write |
162 | 154 | - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 |
163 | 155 | with: |
164 | 156 | ref: ${{ needs.triage.outputs.base }} |
165 | 157 | token: ${{ steps.app-token.outputs.token }} |
166 | | - - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 |
167 | | - with: |
168 | | - name: triage |
169 | | - path: ${{ runner.temp }}/triage |
170 | | - # Never execute code or scripts from the proposed changes in the app job. |
171 | | - - name: Validate, push and open draft |
172 | | - id: publish |
173 | | - uses: microsoft/playwright/.github/actions/publish-patch@main |
174 | | - with: |
175 | | - token: ${{ steps.app-token.outputs.token }} |
176 | | - dir: ${{ runner.temp }}/triage/fix |
177 | | - patch: fix.patch |
178 | | - metadata: proposal.json |
179 | | - body-file: pr-body.md |
180 | | - branch: triage/${{ github.event.issue.number || inputs.issue || 'task' }}/${{ github.run_id }}-${{ github.run_attempt }} |
181 | | - commit-body: "${{ (github.event.issue.number || inputs.issue) && format('Fixes: https://github.com/{0}/issues/{1}', github.repository, github.event.issue.number || inputs.issue) || '' }}" |
182 | | - pull-request: true |
183 | | - |
184 | | - comment: |
185 | | - name: Comment on the issue |
186 | | - needs: [start, triage, publish] |
187 | | - # Runs after every triage run that has an issue, including failed ones, and whatever publish did. |
188 | | - if: ${{ !cancelled() && needs.triage.result != 'skipped' && (github.event_name == 'issues' || inputs.issue != '') }} |
189 | | - runs-on: ubuntu-latest |
190 | | - permissions: |
191 | | - issues: write |
192 | | - steps: |
193 | 158 | - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 |
194 | 159 | # A failed triage run may not have uploaded anything. |
195 | 160 | continue-on-error: true |
196 | 161 | with: |
197 | 162 | name: triage |
198 | 163 | path: ${{ runner.temp }}/triage |
199 | | - # The bot never files upstream; the comment hands the report to a maintainer. |
200 | | - - name: Describe the upstream bug report |
201 | | - id: upstream |
| 164 | + # Without a comment from the agent on the issue, for example after a failed run, the issue gets |
| 165 | + # one that says that the bot could not finish. |
| 166 | + - name: Add a comment when the agent left none |
| 167 | + if: github.event.issue.number || inputs.issue |
202 | 168 | env: |
203 | | - OUTPUT_DIR: ${{ runner.temp }}/triage |
| 169 | + CALLS: ${{ runner.temp }}/triage/calls.json |
| 170 | + ISSUE: ${{ github.event.issue.number || inputs.issue }} |
| 171 | + SETUP: ${{ needs.triage.outputs.setup }} |
| 172 | + AGENT: ${{ needs.triage.outputs.agent }} |
| 173 | + FOOTER: Triaged on ${{ fromJSON('{"linux":"Linux","mac":"macOS","windows":"Windows","needs-triage":"Linux","needs-triage-mac":"macOS","needs-triage-win":"Windows"}')[inputs.platform || github.event.label.name] }} by the Playwright bot |
| 174 | + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} |
204 | 175 | run: | |
205 | | - extra="" |
206 | | - if [[ -s "$OUTPUT_DIR/upstream/report.md" && -f "$OUTPUT_DIR/upstream/report.json" ]]; then |
207 | | - title=$(jq -r '.title // empty' "$OUTPUT_DIR/upstream/report.json") |
208 | | - if [[ -n "$title" ]]; then |
209 | | - extra=$(printf 'Upstream bug report, for a maintainer to file in microsoft/playwright:\n\n**Title:** %s\n\n<details>\n<summary>Body</summary>\n\n````markdown\n%s\n````\n\n</details>' "$title" "$(cat "$OUTPUT_DIR/upstream/report.md")") |
210 | | - fi |
| 176 | + mkdir -p "$(dirname "$CALLS")" |
| 177 | + if jq -e --argjson issue "$ISSUE" 'any(.[]; .call == "comment" and .issue == $issue)' "$CALLS" > /dev/null 2>&1; then |
| 178 | + exit 0 |
211 | 179 | fi |
212 | | - delimiter="EXTRA_$RANDOM$RANDOM" |
213 | | - { echo "extra<<$delimiter"; echo "$extra"; echo "$delimiter"; } >> "$GITHUB_OUTPUT" |
214 | | - - name: Post the triage comment |
215 | | - uses: microsoft/playwright/.github/actions/triage-comment@main |
| 180 | + if [[ "$SETUP" == "failure" ]]; then |
| 181 | + reason="setting up the runner failed" |
| 182 | + elif [[ "$AGENT" == "failure" ]]; then |
| 183 | + reason="the Copilot session failed or ran out of budget" |
| 184 | + else |
| 185 | + reason="the session ended without a comment" |
| 186 | + fi |
| 187 | + body=$(printf "Hi, I'm the Playwright bot. I couldn't finish triaging this issue: %s. The agent run has the details.\n\n<sub>%s - [agent run](%s)</sub>\n<!-- playwright-ai-triage -->" "$reason" "$FOOTER" "$RUN_URL") |
| 188 | + comment=$(jq -n --arg repo "$GITHUB_REPOSITORY" --argjson issue "$ISSUE" --arg body "$body" '{call: "comment", repo: $repo, issue: $issue, body: $body}') |
| 189 | + # A calls.json that is not an array is replaced: its calls would be rejected anyway. |
| 190 | + jq --argjson comment "$comment" '. + [$comment]' "$CALLS" > "$CALLS.tmp" 2> /dev/null \ |
| 191 | + || jq -n --argjson comment "$comment" '[$comment]' > "$CALLS.tmp" |
| 192 | + mv "$CALLS.tmp" "$CALLS" |
| 193 | + # Never execute code or scripts from the artifact in the app job. The calls are data. |
| 194 | + - name: Check and make the calls |
| 195 | + id: publish |
| 196 | + uses: microsoft/playwright/.github/actions/publish-calls@main |
216 | 197 | with: |
217 | | - issue: ${{ github.event.issue.number || inputs.issue }} |
| 198 | + token: ${{ steps.app-token.outputs.token }} |
218 | 199 | dir: ${{ runner.temp }}/triage |
219 | | - footer: Triaged on ${{ fromJSON('{"linux":"Linux","mac":"macOS","windows":"Windows","needs-triage":"Linux","needs-triage-mac":"macOS","needs-triage-win":"Windows"}')[inputs.platform || github.event.label.name] }} by the Playwright bot |
220 | | - job-result: ${{ needs.triage.result }} |
221 | | - setup-outcome: ${{ needs.triage.outputs.setup }} |
222 | | - agent-outcome: ${{ needs.triage.outputs.agent }} |
223 | | - setup-failure-reason: setting up the runner, building the project or downloading the browsers failed |
224 | | - pull-request: ${{ needs.publish.outputs.pr }} |
225 | | - publish-result: ${{ needs.publish.result }} |
226 | | - extra: ${{ steps.upstream.outputs.extra }} |
227 | | - start-comment: ${{ needs.start.outputs.comment }} |
| 200 | + - name: Hide the comment with the run link |
| 201 | + if: ${{ !cancelled() && steps.publish.outputs.comment == 'true' && needs.start.outputs.comment != '' }} |
| 202 | + env: |
| 203 | + GH_TOKEN: ${{ steps.app-token.outputs.token }} |
| 204 | + ID: ${{ needs.start.outputs.comment }} |
| 205 | + run: | |
| 206 | + gh api graphql -f query='mutation($id: ID!) { minimizeComment(input: { subjectId: $id, classifier: OUTDATED }) { clientMutationId } }' -f id="$ID" \ |
| 207 | + || echo "::warning::Could not hide the comment with the run link" |
0 commit comments