From 420a419f5ebc8d1200628e6fa781ad204c0d8702 Mon Sep 17 00:00:00 2001 From: Bryan Morgan Date: Thu, 15 Jan 2026 23:38:27 -0500 Subject: [PATCH] fix(infra): use GraphQL to detect direct parents in rollup workflow (#16811) --- .github/scripts/sync-maintainer-labels.cjs | 2 +- .github/workflows/label-workstream-rollup.yml | 94 +++++++++++++++++++ 2 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/label-workstream-rollup.yml diff --git a/.github/scripts/sync-maintainer-labels.cjs b/.github/scripts/sync-maintainer-labels.cjs index c44a69489d..ab2358d369 100644 --- a/.github/scripts/sync-maintainer-labels.cjs +++ b/.github/scripts/sync-maintainer-labels.cjs @@ -23,7 +23,7 @@ const ROOT_ISSUES = [ { owner: REPO_OWNER, repo: PUBLIC_REPO, number: 15324 }, ]; -const TARGET_LABEL = 'workstream-rollup'; +const TARGET_LABEL = '🔒 maintainer only'; const isDryRun = process.argv.includes('--dry-run') || process.env.DRY_RUN === 'true'; diff --git a/.github/workflows/label-workstream-rollup.yml b/.github/workflows/label-workstream-rollup.yml new file mode 100644 index 0000000000..dc790c6c27 --- /dev/null +++ b/.github/workflows/label-workstream-rollup.yml @@ -0,0 +1,94 @@ +name: 'Label Child Issues for Project Rollup' + +on: + issues: + types: ['opened', 'edited', 'reopened'] + schedule: + - cron: '0 * * * *' + workflow_dispatch: + +jobs: + labeler: + runs-on: 'ubuntu-latest' + permissions: + issues: 'write' + steps: + - name: 'Check for Parent Workstream and Apply Label' + uses: 'actions/github-script@v7' + with: + script: | + const labelToAdd = 'workstream-rollup'; + + // Allow-list of parent issue URLs + const allowedParentUrls = [ + 'https://github.com/google-gemini/gemini-cli/issues/15374', + 'https://github.com/google-gemini/gemini-cli/issues/15456', + 'https://github.com/google-gemini/gemini-cli/issues/15324' + ]; + + async function getIssueParent(owner, repo, number) { + const query = ` + query($owner:String!, $repo:String!, $number:Int!) { + repository(owner:$owner, name:$repo) { + issue(number:$number) { + parent { + url + } + } + } + } + `; + try { + const result = await github.graphql(query, { owner, repo, number }); + return result.repository.issue.parent ? result.repository.issue.parent.url : null; + } catch (error) { + console.error(`Failed to fetch parent for #${number}:`, error); + return null; + } + } + + // Determine which issues to process + let issuesToProcess = []; + + if (context.eventName === 'issues') { + // Context payload for 'issues' event already has the issue object + issuesToProcess.push({ + number: context.payload.issue.number, + owner: context.repo.owner, + repo: context.repo.repo + }); + } else { + // For schedule/dispatch, fetch open issues (lightweight list) + console.log(`Running for event: ${context.eventName}. Fetching open issues...`); + const openIssues = await github.paginate(github.rest.issues.listForRepo, { + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open' + }); + issuesToProcess = openIssues.map(i => ({ + number: i.number, + owner: context.repo.owner, + repo: context.repo.repo + })); + } + + console.log(`Processing ${issuesToProcess.length} issue(s)...`); + + for (const issue of issuesToProcess) { + const parentUrl = await getIssueParent(issue.owner, issue.repo, issue.number); + + if (parentUrl && allowedParentUrls.includes(parentUrl)) { + console.log(`SUCCESS: Issue #${issue.number} is a direct child of ${parentUrl}. Adding label.`); + await github.rest.issues.addLabels({ + owner: issue.owner, + repo: issue.repo, + issue_number: issue.number, + labels: [labelToAdd] + }); + } else { + // logging only for single execution to avoid spam + if (context.eventName === 'issues') { + console.log(`Issue #${issue.number} parent is ${parentUrl || 'None'}. No action.`); + } + } + }