[Issue #38236] Bug: Browser processes not cleaned up after automation tasks #7
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Labeler | |
| on: | |
| pull_request_target: | |
| types: [opened, synchronize, reopened] | |
| issues: | |
| types: [opened] | |
| workflow_dispatch: | |
| inputs: | |
| max_prs: | |
| description: "Maximum number of open PRs to process (0 = all)" | |
| required: false | |
| default: "200" | |
| per_page: | |
| description: "PRs per page (1-100)" | |
| required: false | |
| default: "50" | |
| permissions: {} | |
| jobs: | |
| label: | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| runs-on: blacksmith-16vcpu-ubuntu-2404 | |
| steps: | |
| - uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1 | |
| id: app-token | |
| continue-on-error: true | |
| with: | |
| app-id: "2729701" | |
| private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} | |
| - uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1 | |
| id: app-token-fallback | |
| if: steps.app-token.outcome == 'failure' | |
| with: | |
| app-id: "2971289" | |
| private-key: ${{ secrets.GH_APP_PRIVATE_KEY_FALLBACK }} | |
| - uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5 | |
| with: | |
| configuration-path: .github/labeler.yml | |
| repo-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }} | |
| sync-labels: true | |
| - name: Apply PR size label | |
| uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 | |
| with: | |
| github-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }} | |
| script: | | |
| const pullRequest = context.payload.pull_request; | |
| if (!pullRequest) { | |
| return; | |
| } | |
| const sizeLabels = ["size: XS", "size: S", "size: M", "size: L", "size: XL"]; | |
| const labelColor = "b76e79"; | |
| for (const label of sizeLabels) { | |
| try { | |
| await github.rest.issues.getLabel({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| name: label, | |
| }); | |
| } catch (error) { | |
| if (error?.status !== 404) { | |
| throw error; | |
| } | |
| await github.rest.issues.createLabel({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| name: label, | |
| color: labelColor, | |
| }); | |
| } | |
| } | |
| const files = await github.paginate(github.rest.pulls.listFiles, { | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: pullRequest.number, | |
| per_page: 100, | |
| }); | |
| const excludedLockfiles = new Set(["pnpm-lock.yaml", "package-lock.json", "yarn.lock", "bun.lockb"]); | |
| const totalChangedLines = files.reduce((total, file) => { | |
| const path = file.filename ?? ""; | |
| if (path === "docs.acp.md" || path.startsWith("docs/") || excludedLockfiles.has(path)) { | |
| return total; | |
| } | |
| return total + (file.additions ?? 0) + (file.deletions ?? 0); | |
| }, 0); | |
| let targetSizeLabel = "size: XL"; | |
| if (totalChangedLines < 50) { | |
| targetSizeLabel = "size: XS"; | |
| } else if (totalChangedLines < 200) { | |
| targetSizeLabel = "size: S"; | |
| } else if (totalChangedLines < 500) { | |
| targetSizeLabel = "size: M"; | |
| } else if (totalChangedLines < 1000) { | |
| targetSizeLabel = "size: L"; | |
| } | |
| const currentLabels = await github.paginate(github.rest.issues.listLabelsOnIssue, { | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: pullRequest.number, | |
| per_page: 100, | |
| }); | |
| for (const label of currentLabels) { | |
| const name = label.name ?? ""; | |
| if (!sizeLabels.includes(name)) { | |
| continue; | |
| } | |
| if (name === targetSizeLabel) { | |
| continue; | |
| } | |
| await github.rest.issues.removeLabel({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: pullRequest.number, | |
| name, | |
| }); | |
| } | |
| await github.rest.issues.addLabels({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: pullRequest.number, | |
| labels: [targetSizeLabel], | |
| }); | |
| - name: Apply maintainer or trusted-contributor label | |
| uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 | |
| with: | |
| github-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }} | |
| script: | | |
| const login = context.payload.pull_request?.user?.login; | |
| if (!login) { | |
| return; | |
| } | |
| const repo = `${context.repo.owner}/${context.repo.repo}`; | |
| const trustedLabel = "trusted-contributor"; | |
| const experiencedLabel = "experienced-contributor"; | |
| const trustedThreshold = 4; | |
| const experiencedThreshold = 10; | |
| let isMaintainer = false; | |
| try { | |
| const membership = await github.rest.teams.getMembershipForUserInOrg({ | |
| org: context.repo.owner, | |
| team_slug: "maintainer", | |
| username: login, | |
| }); | |
| isMaintainer = membership?.data?.state === "active"; | |
| } catch (error) { | |
| if (error?.status !== 404) { | |
| throw error; | |
| } | |
| } | |
| if (isMaintainer) { | |
| await github.rest.issues.addLabels({ | |
| ...context.repo, | |
| issue_number: context.payload.pull_request.number, | |
| labels: ["maintainer"], | |
| }); | |
| return; | |
| } | |
| const mergedQuery = `repo:${repo} is:pr is:merged author:${login}`; | |
| let mergedCount = 0; | |
| try { | |
| const merged = await github.rest.search.issuesAndPullRequests({ | |
| q: mergedQuery, | |
| per_page: 1, | |
| }); | |
| mergedCount = merged?.data?.total_count ?? 0; | |
| } catch (error) { | |
| if (error?.status !== 422) { | |
| throw error; | |
| } | |
| core.warning(`Skipping merged search for ${login}; treating as 0.`); | |
| } | |
| if (mergedCount >= experiencedThreshold) { | |
| await github.rest.issues.addLabels({ | |
| ...context.repo, | |
| issue_number: context.payload.pull_request.number, | |
| labels: [experiencedLabel], | |
| }); | |
| return; | |
| } | |
| if (mergedCount >= trustedThreshold) { | |
| await github.rest.issues.addLabels({ | |
| ...context.repo, | |
| issue_number: context.payload.pull_request.number, | |
| labels: [trustedLabel], | |
| }); | |
| } | |
| backfill-pr-labels: | |
| if: github.event_name == 'workflow_dispatch' | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| runs-on: blacksmith-16vcpu-ubuntu-2404 | |
| steps: | |
| - uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1 | |
| id: app-token | |
| continue-on-error: true | |
| with: | |
| app-id: "2729701" | |
| private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} | |
| - uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1 | |
| id: app-token-fallback | |
| if: steps.app-token.outcome == 'failure' | |
| with: | |
| app-id: "2971289" | |
| private-key: ${{ secrets.GH_APP_PRIVATE_KEY_FALLBACK }} | |
| - name: Backfill PR labels | |
| uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 | |
| with: | |
| github-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }} | |
| script: | | |
| const owner = context.repo.owner; | |
| const repo = context.repo.repo; | |
| const repoFull = `${owner}/${repo}`; | |
| const inputs = context.payload.inputs ?? {}; | |
| const maxPrsInput = inputs.max_prs ?? "200"; | |
| const perPageInput = inputs.per_page ?? "50"; | |
| const parsedMaxPrs = Number.parseInt(maxPrsInput, 10); | |
| const parsedPerPage = Number.parseInt(perPageInput, 10); | |
| const maxPrs = Number.isFinite(parsedMaxPrs) ? parsedMaxPrs : 200; | |
| const perPage = Number.isFinite(parsedPerPage) ? Math.min(100, Math.max(1, parsedPerPage)) : 50; | |
| const processAll = maxPrs <= 0; | |
| const maxCount = processAll ? Number.POSITIVE_INFINITY : Math.max(1, maxPrs); | |
| const sizeLabels = ["size: XS", "size: S", "size: M", "size: L", "size: XL"]; | |
| const labelColor = "b76e79"; | |
| const trustedLabel = "trusted-contributor"; | |
| const experiencedLabel = "experienced-contributor"; | |
| const trustedThreshold = 4; | |
| const experiencedThreshold = 10; | |
| const contributorCache = new Map(); | |
| async function ensureSizeLabels() { | |
| for (const label of sizeLabels) { | |
| try { | |
| await github.rest.issues.getLabel({ | |
| owner, | |
| repo, | |
| name: label, | |
| }); | |
| } catch (error) { | |
| if (error?.status !== 404) { | |
| throw error; | |
| } | |
| await github.rest.issues.createLabel({ | |
| owner, | |
| repo, | |
| name: label, | |
| color: labelColor, | |
| }); | |
| } | |
| } | |
| } | |
| async function resolveContributorLabel(login) { | |
| if (contributorCache.has(login)) { | |
| return contributorCache.get(login); | |
| } | |
| let isMaintainer = false; | |
| try { | |
| const membership = await github.rest.teams.getMembershipForUserInOrg({ | |
| org: owner, | |
| team_slug: "maintainer", | |
| username: login, | |
| }); | |
| isMaintainer = membership?.data?.state === "active"; | |
| } catch (error) { | |
| if (error?.status !== 404) { | |
| throw error; | |
| } | |
| } | |
| if (isMaintainer) { | |
| contributorCache.set(login, "maintainer"); | |
| return "maintainer"; | |
| } | |
| const mergedQuery = `repo:${repoFull} is:pr is:merged author:${login}`; | |
| let mergedCount = 0; | |
| try { | |
| const merged = await github.rest.search.issuesAndPullRequests({ | |
| q: mergedQuery, | |
| per_page: 1, | |
| }); | |
| mergedCount = merged?.data?.total_count ?? 0; | |
| } catch (error) { | |
| if (error?.status !== 422) { | |
| throw error; | |
| } | |
| core.warning(`Skipping merged search for ${login}; treating as 0.`); | |
| } | |
| let label = null; | |
| if (mergedCount >= experiencedThreshold) { | |
| label = experiencedLabel; | |
| } else if (mergedCount >= trustedThreshold) { | |
| label = trustedLabel; | |
| } | |
| contributorCache.set(login, label); | |
| return label; | |
| } | |
| async function applySizeLabel(pullRequest, currentLabels, labelNames) { | |
| const files = await github.paginate(github.rest.pulls.listFiles, { | |
| owner, | |
| repo, | |
| pull_number: pullRequest.number, | |
| per_page: 100, | |
| }); | |
| const excludedLockfiles = new Set(["pnpm-lock.yaml", "package-lock.json", "yarn.lock", "bun.lockb"]); | |
| const totalChangedLines = files.reduce((total, file) => { | |
| const path = file.filename ?? ""; | |
| if (path === "docs.acp.md" || path.startsWith("docs/") || excludedLockfiles.has(path)) { | |
| return total; | |
| } | |
| return total + (file.additions ?? 0) + (file.deletions ?? 0); | |
| }, 0); | |
| let targetSizeLabel = "size: XL"; | |
| if (totalChangedLines < 50) { | |
| targetSizeLabel = "size: XS"; | |
| } else if (totalChangedLines < 200) { | |
| targetSizeLabel = "size: S"; | |
| } else if (totalChangedLines < 500) { | |
| targetSizeLabel = "size: M"; | |
| } else if (totalChangedLines < 1000) { | |
| targetSizeLabel = "size: L"; | |
| } | |
| for (const label of currentLabels) { | |
| const name = label.name ?? ""; | |
| if (!sizeLabels.includes(name)) { | |
| continue; | |
| } | |
| if (name === targetSizeLabel) { | |
| continue; | |
| } | |
| await github.rest.issues.removeLabel({ | |
| owner, | |
| repo, | |
| issue_number: pullRequest.number, | |
| name, | |
| }); | |
| labelNames.delete(name); | |
| } | |
| if (!labelNames.has(targetSizeLabel)) { | |
| await github.rest.issues.addLabels({ | |
| owner, | |
| repo, | |
| issue_number: pullRequest.number, | |
| labels: [targetSizeLabel], | |
| }); | |
| labelNames.add(targetSizeLabel); | |
| } | |
| } | |
| async function applyContributorLabel(pullRequest, labelNames) { | |
| const login = pullRequest.user?.login; | |
| if (!login) { | |
| return; | |
| } | |
| const label = await resolveContributorLabel(login); | |
| if (!label) { | |
| return; | |
| } | |
| if (labelNames.has(label)) { | |
| return; | |
| } | |
| await github.rest.issues.addLabels({ | |
| owner, | |
| repo, | |
| issue_number: pullRequest.number, | |
| labels: [label], | |
| }); | |
| labelNames.add(label); | |
| } | |
| await ensureSizeLabels(); | |
| let page = 1; | |
| let processed = 0; | |
| while (processed < maxCount) { | |
| const remaining = maxCount - processed; | |
| const pageSize = processAll ? perPage : Math.min(perPage, remaining); | |
| const { data: pullRequests } = await github.rest.pulls.list({ | |
| owner, | |
| repo, | |
| state: "open", | |
| per_page: pageSize, | |
| page, | |
| }); | |
| if (pullRequests.length === 0) { | |
| break; | |
| } | |
| for (const pullRequest of pullRequests) { | |
| if (!processAll && processed >= maxCount) { | |
| break; | |
| } | |
| const currentLabels = await github.paginate(github.rest.issues.listLabelsOnIssue, { | |
| owner, | |
| repo, | |
| issue_number: pullRequest.number, | |
| per_page: 100, | |
| }); | |
| const labelNames = new Set( | |
| currentLabels.map((label) => label.name).filter((name) => typeof name === "string"), | |
| ); | |
| await applySizeLabel(pullRequest, currentLabels, labelNames); | |
| await applyContributorLabel(pullRequest, labelNames); | |
| processed += 1; | |
| } | |
| if (pullRequests.length < pageSize) { | |
| break; | |
| } | |
| page += 1; | |
| } | |
| core.info(`Processed ${processed} pull requests.`); | |
| label-issues: | |
| permissions: | |
| issues: write | |
| runs-on: blacksmith-16vcpu-ubuntu-2404 | |
| steps: | |
| - uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1 | |
| id: app-token | |
| continue-on-error: true | |
| with: | |
| app-id: "2729701" | |
| private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} | |
| - uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1 | |
| id: app-token-fallback | |
| if: steps.app-token.outcome == 'failure' | |
| with: | |
| app-id: "2971289" | |
| private-key: ${{ secrets.GH_APP_PRIVATE_KEY_FALLBACK }} | |
| - name: Apply maintainer or trusted-contributor label | |
| uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 | |
| with: | |
| github-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }} | |
| script: | | |
| const login = context.payload.issue?.user?.login; | |
| if (!login) { | |
| return; | |
| } | |
| const repo = `${context.repo.owner}/${context.repo.repo}`; | |
| const trustedLabel = "trusted-contributor"; | |
| const experiencedLabel = "experienced-contributor"; | |
| const trustedThreshold = 4; | |
| const experiencedThreshold = 10; | |
| let isMaintainer = false; | |
| try { | |
| const membership = await github.rest.teams.getMembershipForUserInOrg({ | |
| org: context.repo.owner, | |
| team_slug: "maintainer", | |
| username: login, | |
| }); | |
| isMaintainer = membership?.data?.state === "active"; | |
| } catch (error) { | |
| if (error?.status !== 404) { | |
| throw error; | |
| } | |
| } | |
| if (isMaintainer) { | |
| await github.rest.issues.addLabels({ | |
| ...context.repo, | |
| issue_number: context.payload.issue.number, | |
| labels: ["maintainer"], | |
| }); | |
| return; | |
| } | |
| const mergedQuery = `repo:${repo} is:pr is:merged author:${login}`; | |
| let mergedCount = 0; | |
| try { | |
| const merged = await github.rest.search.issuesAndPullRequests({ | |
| q: mergedQuery, | |
| per_page: 1, | |
| }); | |
| mergedCount = merged?.data?.total_count ?? 0; | |
| } catch (error) { | |
| if (error?.status !== 422) { | |
| throw error; | |
| } | |
| core.warning(`Skipping merged search for ${login}; treating as 0.`); | |
| } | |
| if (mergedCount >= experiencedThreshold) { | |
| await github.rest.issues.addLabels({ | |
| ...context.repo, | |
| issue_number: context.payload.issue.number, | |
| labels: [experiencedLabel], | |
| }); | |
| return; | |
| } | |
| if (mergedCount >= trustedThreshold) { | |
| await github.rest.issues.addLabels({ | |
| ...context.repo, | |
| issue_number: context.payload.issue.number, | |
| labels: [trustedLabel], | |
| }); | |
| } |