Skip to content

[Bug]: Sync API — pw.stop() raises "This event loop is already running" at teardown after 1.64 sync-dispatcher rework (#3230) #3

[Bug]: Sync API — pw.stop() raises "This event loop is already running" at teardown after 1.64 sync-dispatcher rework (#3230)

[Bug]: Sync API — pw.stop() raises "This event loop is already running" at teardown after 1.64 sync-dispatcher rework (#3230) #3

Workflow file for this run

# Copilot triages and fixes an issue, or works on a task given by hand, without write credentials.
# The agent leaves the calls to make on GitHub in calls.json. A fresh job posts the run link on the
# issue first. After the agent, a fresh job checks the calls and makes them using the app: it opens
# the fix as a draft PR, so CI runs on it, and comments on the issue. The steps that are the same
# for every Playwright repo are actions in microsoft/playwright. The skill in
# .claude/skills/playwright-triage/ has what is specific to this repo.
name: Issue triage
on:
# Apply needs-triage, needs-triage-mac or needs-triage-win; re-apply it to trigger another pass.
issues:
types: [labeled]
workflow_dispatch:
inputs:
issue:
description: "Issue number to triage; leave empty to work on the task alone"
type: string
required: false
default: ""
platform:
description: "Platform to triage on"
type: choice
required: true
default: linux
options:
- linux
- mac
- windows
task:
description: "Task to direct the triage, such as what to focus on; the whole job when there is no issue"
type: string
required: false
default: ""
permissions: {}
jobs:
start:
name: Post the run link on the issue
# The triage job's condition, for runs that have an issue.
if: >-
github.repository == 'microsoft/playwright-python' &&
((github.event_name == 'workflow_dispatch' && inputs.issue != '') ||
contains(fromJSON('["needs-triage","needs-triage-mac","needs-triage-win"]'), github.event.label.name))
runs-on: ubuntu-latest
permissions:
issues: write
outputs:
comment: ${{ steps.post.outputs.id }}
env:
ISSUE: ${{ github.event.issue.number || inputs.issue }}
PLATFORM_NAME: ${{ fromJSON('{"linux":"Linux","mac":"macOS","windows":"Windows","needs-triage":"Linux","needs-triage-mac":"macOS","needs-triage-win":"Windows"}')[inputs.platform || github.event.label.name] }}
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
WORKFLOW_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
steps:
- name: Post the run link
id: post
run: |
[[ "$ISSUE" =~ ^[1-9][0-9]*$ ]] || { echo "issue must be a positive integer" >&2; exit 1; }
body=$(printf "Hi, I'm the Playwright bot. I started to triage this issue on %s: [agent run](%s)\n<!-- playwright-triage-started -->" "$PLATFORM_NAME" "$WORKFLOW_URL")
id=$(gh api "repos/$GH_REPO/issues/$ISSUE/comments" -f body="$body" --jq .node_id)
echo "id=$id" >> "$GITHUB_OUTPUT"
triage:
name: Triage ${{ (github.event.issue.number || inputs.issue) && format('issue {0}', github.event.issue.number || inputs.issue) || 'task' }} (${{ inputs.platform || github.event.label.name }})
if: >-
github.repository == 'microsoft/playwright-python' &&
(github.event_name == 'workflow_dispatch' ||
contains(fromJSON('["needs-triage","needs-triage-mac","needs-triage-win"]'), github.event.label.name))
# Falls back to a runner name for unrelated labels, where the job is skipped anyway.
runs-on: ${{ fromJSON('{"linux":"playwright-x64-ubuntu24-64-core","mac":"macos-15-xlarge","windows":"playwright-x64-server2025-64-core"}')[inputs.platform || fromJSON('{"needs-triage":"linux","needs-triage-mac":"mac","needs-triage-win":"windows"}')[github.event.label.name]] || 'ubuntu-latest' }}
timeout-minutes: 240
concurrency:
group: triage-${{ github.event.issue.number || inputs.issue || github.run_id }}
cancel-in-progress: false
defaults:
run:
shell: bash
env:
ISSUE: ${{ github.event.issue.number || inputs.issue }}
TRIAGE_PLATFORM: ${{ inputs.platform || fromJSON('{"needs-triage":"linux","needs-triage-mac":"mac","needs-triage-win":"windows"}')[github.event.label.name] }}
TASK: ${{ inputs.task }}
permissions:
copilot-requests: write
outputs:
base: ${{ steps.base.outputs.sha }}
setup: ${{ steps.setup.outcome }}
agent: ${{ steps.agent.outputs.outcome }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: main
persist-credentials: false
- name: Record the base and the directories
id: base
run: |
if [[ -n "$ISSUE" ]]; then
[[ "$ISSUE" =~ ^[1-9][0-9]*$ ]] || { echo "issue must be a positive integer" >&2; exit 1; }
elif [[ -z "${TASK//[[:space:]]/}" ]]; then
echo "give an issue, a task or both" >&2; exit 1
fi
echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
echo "OUTPUT_DIR=${RUNNER_TEMP//\\//}/triage" >> "$GITHUB_ENV"
echo "SCRATCH_DIR=${RUNNER_TEMP//\\//}/scratch" >> "$GITHUB_ENV"
echo "REPO_DIR=${GITHUB_WORKSPACE//\\//}" >> "$GITHUB_ENV"
mkdir -p "${RUNNER_TEMP//\\//}/triage" "${RUNNER_TEMP//\\//}/scratch"
- name: Set up the runner
id: setup
uses: ./.github/actions/setup-triage-runner
- name: Write the prompt
id: prompt
run: |
delimiter="PROMPT_$RANDOM$RANDOM"
{
echo "prompt<<$delimiter"
echo "Follow .claude/skills/playwright-triage/SKILL.md. Read it, the guide and the AGENTS.md"
echo "it points to in full before starting."
if [[ -n "$ISSUE" ]]; then
printf '\nIssue: https://github.com/%s/issues/%s\n' "$GITHUB_REPOSITORY" "$ISSUE"
fi
if [[ -n "$TASK" ]]; then
printf '\nTask: %s\n' "$TASK"
fi
echo "$delimiter"
} >> "$GITHUB_OUTPUT"
- name: Triage with Copilot CLI
id: agent
uses: microsoft/playwright/.github/actions/run-agent@main
with:
prompt: ${{ steps.prompt.outputs.prompt }}
output-dir: ${{ env.OUTPUT_DIR }}
result-file: calls.json
artifact-name: triage
github-tools: true
exec: ${{ env.CI_ENV_EXEC }}
publish:
name: Publish the results
needs: [start, triage]
# Runs after every triage run, also a failed one: without a comment from the agent, the comment
# says why the bot could not finish.
if: ${{ !cancelled() && needs.triage.result != 'skipped' }}
runs-on: ubuntu-latest
steps:
- uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0
id: app-token
with:
app-id: ${{ vars.PLAYWRIGHT_APP_ID }}
private-key: ${{ secrets.PLAYWRIGHT_PRIVATE_KEY }}
permission-contents: write
permission-issues: write
permission-pull-requests: write
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ needs.triage.outputs.base }}
token: ${{ steps.app-token.outputs.token }}
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
# A failed triage run may not have uploaded anything.
continue-on-error: true
with:
name: triage
path: ${{ runner.temp }}/triage
# Without a comment from the agent on the issue, for example after a failed run, the issue gets
# one that says that the bot could not finish.
- name: Add a comment when the agent left none
if: github.event.issue.number || inputs.issue
env:
CALLS: ${{ runner.temp }}/triage/calls.json
ISSUE: ${{ github.event.issue.number || inputs.issue }}
SETUP: ${{ needs.triage.outputs.setup }}
AGENT: ${{ needs.triage.outputs.agent }}
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
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
mkdir -p "$(dirname "$CALLS")"
if jq -e --argjson issue "$ISSUE" 'any(.[]; .call == "comment" and .issue == $issue)' "$CALLS" > /dev/null 2>&1; then
exit 0
fi
if [[ "$SETUP" == "failure" ]]; then
reason="setting up the runner failed"
elif [[ "$AGENT" == "failure" ]]; then
reason="the Copilot session failed or ran out of budget"
else
reason="the session ended without a comment"
fi
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")
comment=$(jq -n --arg repo "$GITHUB_REPOSITORY" --argjson issue "$ISSUE" --arg body "$body" '{call: "comment", repo: $repo, issue: $issue, body: $body}')
# A calls.json that is not an array is replaced: its calls would be rejected anyway.
jq --argjson comment "$comment" '. + [$comment]' "$CALLS" > "$CALLS.tmp" 2> /dev/null \
|| jq -n --argjson comment "$comment" '[$comment]' > "$CALLS.tmp"
mv "$CALLS.tmp" "$CALLS"
# Never execute code or scripts from the artifact in the app job. The calls are data.
- name: Check and make the calls
id: publish
uses: microsoft/playwright/.github/actions/publish-calls@main
with:
token: ${{ steps.app-token.outputs.token }}
dir: ${{ runner.temp }}/triage
- name: Hide the comment with the run link
if: ${{ !cancelled() && steps.publish.outputs.comment == 'true' && needs.start.outputs.comment != '' }}
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
ID: ${{ needs.start.outputs.comment }}
run: |
gh api graphql -f query='mutation($id: ID!) { minimizeComment(input: { subjectId: $id, classifier: OUTDATED }) { clientMutationId } }' -f id="$ID" \
|| echo "::warning::Could not hide the comment with the run link"