From 0ed9bb21ca33d4c5f03d14617788cea454bded09 Mon Sep 17 00:00:00 2001 From: Kevin Heis Date: Mon, 17 Aug 2026 16:27:19 +0000 Subject: [PATCH 1/3] Restrict merging to main to the Technical Content team (#62790) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e69dde28-9eec-4ca7-b322-eafff4217240 --- .github/workflows/restrict-merge-queue.yml | 216 +++++++++++++++++++++ 1 file changed, 216 insertions(+) create mode 100644 .github/workflows/restrict-merge-queue.yml diff --git a/.github/workflows/restrict-merge-queue.yml b/.github/workflows/restrict-merge-queue.yml new file mode 100644 index 000000000000..bbaec5ac9f21 --- /dev/null +++ b/.github/workflows/restrict-merge-queue.yml @@ -0,0 +1,216 @@ +name: Restrict who can queue merges + +# **What it does**: +# On a `merge_group` event, checks whether the person who put the pull +# request into the merge queue is on github/technical-content. If they +# are not, comments on the pull request saying so and fails, which +# ejects the entry from the queue. +# **Why we have it**: +# Classic branch protection used to restrict who could push to `main`, +# but that rule was swept org-wide on 2026-06-22, so today anyone with +# write access can merge. Rebuilding it means also enabling a merge +# queue on the same rule, which could collide with the merge queue on +# our ruleset and lock the branch for everyone. This does the same job +# with machinery we own outright. +# **Who does it impact**: Anyone merging to `main`. + +# Two things to know before changing this: +# +# 1. `merge-queue-restriction` has to be a required status check on the ruleset targeting `refs/heads/main`, or this +# enforces nothing. Add it there only after this workflow is on `main` and reporting. The other order makes the +# check required before it has ever reported, which blocks every pull request. To turn enforcement off again, +# remove it from the ruleset. This workflow keeps running and keeps passing. +# +# 2. The `pull_request` runs do no work. They exist so the required check reports a passing context on the pull +# request itself. Drop them and the check sits pending forever and nothing can ever be enqueued. + +on: + pull_request: + types: [opened, reopened, synchronize, ready_for_review] + merge_group: + +permissions: + contents: read + pull-requests: write + +# Keyed on head SHA rather than pull request number. Webhook delivery order is not guaranteed, and keying on the pull +# request would let a late event for an old SHA cancel the run for a newer one. +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha }} + cancel-in-progress: true + +jobs: + # The job id doubles as the check run name because this job deliberately has no `name:` key. Renaming this job renames + # the required status check, which silently stops enforcing anything. + merge-queue-restriction: + # This repository syncs a subset of files to the public github/docs, including workflows. Nothing here applies there. + if: github.repository == 'github/docs-internal' + runs-on: ubuntu-latest + steps: + - name: Check that the enqueuer is on the Technical Content team + if: github.event_name == 'merge_group' + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + # Reading org team membership needs `read:org`, which GITHUB_TOKEN does not have. `merge_group` always runs in + # the base repository, so this secret is always available here, including for pull requests from forks. + github-token: ${{ secrets.DOCS_BOT_PAT_BASE }} + script: | + // Addressed by numeric ID (org github = 9919, team technical-content = 325922) because IDs survive renames + // and slugs do not. This team was called `docs` until recently and the rename broke a pile of automation. + const ORG_ID = 9919 + const TEAM_ID = 325922 + const TEAM = 'github/technical-content' + const MARKER = '' + const EXEMPT_USERS = ['docs-bot'] + const CONTENT_SLACK_CHANNEL = 'C0E9DK082' + const MAX_ATTEMPTS = 3 + + // `github.actor` is the person who enqueued. On a re-run it stays the original actor, unlike + // `github.triggering_actor`, so re-running cannot launder a failing check into a passing one. + const actor = context.actor + core.info(`This merge group was queued by @${actor}.`) + + // A GitHub App actor always ends in `[bot]`, and `[` is not a valid character in a username, so nobody can + // impersonate one. `docs-bot` is a plain User account and has to be named explicitly. + core.info(`Checking whether @${actor} is an automation account...`) + if (actor.endsWith('[bot]') || EXEMPT_USERS.includes(actor)) { + core.info(`Checked: @${actor} is an automation account. Allowing the merge.`) + return + } + core.info(`Checked: @${actor} is a person, so they need to be on the team.`) + + // Every request retries transient failures before giving up, then fails closed. Failing closed is safe + // here: github/technical-content is an `always` bypass actor on the ruleset, so a broken check stops + // non-Docs merges but never stops Docs. A 404 comes back as null data rather than as an error, because on + // both of the endpoints below it is an answer rather than a failure. + async function ask(description, route, params) { + for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) { + core.info(`${description} (attempt ${attempt} of ${MAX_ATTEMPTS})...`) + try { + const { data } = await github.request(route, params) + return { data } + } catch (error) { + if (error.status === 404) return { data: null } + if (attempt === MAX_ATTEMPTS) return { error } + + const seconds = attempt * 2 + core.warning(`Asked and failed with HTTP ${error.status}. Retrying in ${seconds}s.`) + await new Promise((resolve) => setTimeout(resolve, seconds * 1000)) + } + } + } + + // Deliberately says nothing on the pull request. We only comment when we know the answer, and here we + // do not. + function giveUp(detail) { + core.setFailed(`${detail} Failing closed, so this stays out of the queue. Ask in #docs-content.`) + } + + // Checking the parent team is enough. Every member of every child team (docs-content, docs-engineering, + // docs-localization, docs-content-systems, docs-product-managers, docs-open-source, docs-design, + // docs-content-design, copilot-docs) also resolves as a member of the parent. + const membershipResult = await ask( + `Asking the API whether @${actor} is on ${TEAM}`, + 'GET /organizations/{org_id}/team/{team_id}/memberships/{username}', + { org_id: ORG_ID, team_id: TEAM_ID, username: actor }, + ) + + if (membershipResult.error) { + giveUp( + `Could not check ${TEAM} membership for @${actor}. ` + + `The last attempt returned HTTP ${membershipResult.error.status}.`, + ) + return + } + + const membership = membershipResult.data + + if (membership) { + core.info(`Asked: @${actor} has membership state "${membership.state}" on ${TEAM}.`) + } else { + core.info(`Asked: the API reports no membership for @${actor} on ${TEAM} (HTTP 404).`) + + // That 404 is ambiguous. It is byte for byte the same response for "not a member", "team no longer + // exists", and "the token lost visibility into the org". Read the team back before believing it, + // otherwise a deleted team or a downgraded token would blame every single person who tries to merge. + const teamResult = await ask( + `A 404 is ambiguous, so reading ${TEAM} back to confirm it is still visible`, + 'GET /organizations/{org_id}/team/{team_id}', + { org_id: ORG_ID, team_id: TEAM_ID }, + ) + + if (teamResult.error || !teamResult.data) { + giveUp( + `Could not read ${TEAM} itself, so the 404 for @${actor} says nothing about their membership. ` + + `The last attempt returned HTTP ${teamResult.error?.status ?? 404}. ` + + 'Either the team is gone or this token lost access to it.', + ) + return + } + + core.info(`Read it back: ${TEAM} is visible as "${teamResult.data.slug}", so the 404 is a real answer.`) + } + + if (membership?.state === 'active') { + core.info(`@${actor} is an active member of ${TEAM}. Allowing the merge.`) + return + } + + const reason = membership + ? `@${actor} has a "${membership.state}" membership on ${TEAM} rather than an active one.` + : `@${actor} is not a member of ${TEAM}.` + core.info(`Blocking the merge. ${reason}`) + + // A failed check on a `gh-readonly-queue` ref is not something anyone goes looking for, so say why on the + // pull request itself. The merge group ref is `refs/heads/gh-readonly-queue//pr--`, and + // the pull request it names is the one this actor just enqueued, so the number and the actor correspond. + const ref = context.payload.merge_group?.head_ref ?? context.ref + core.info(`Working out which pull request this merge group is for, from "${ref}"...`) + const number = Number(ref.match(/\/pr-(\d+)-[0-9a-f]+$/)?.[1]) + + if (!number) { + core.warning(`Worked it out: could not find a pull request number in "${ref}". Skipping the comment.`) + } else { + core.info(`Worked it out: this merge group is for #${number}.`) + + // Only comment once. Someone who tries to enqueue again already has the explanation, and repeating it + // turns a useful comment into noise. + core.info(`Reading the existing comments on #${number}...`) + const comments = await github.paginate(github.rest.issues.listComments, { + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: number, + per_page: 100, + }) + core.info(`Read ${comments.length} comment(s) on #${number}.`) + + if (comments.some((comment) => comment.body?.includes(MARKER))) { + core.info(`#${number} already has this explanation, so not commenting again.`) + } else { + core.info(`Commenting on #${number} to explain...`) + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: number, + body: [ + // The marker has to be on its own line. GitHub parses ``, so anything sharing that + // line renders as literal text: no code spans, no links. + MARKER, + [ + `👋 Hi @${actor}, this pull request was removed from the merge queue.`, + 'Only the GitHub Technical Content team merges to `main` in this repository.', + 'Once this is reviewed and ready, ask in', + `[#docs-content](https://github.slack.com/archives/${CONTENT_SLACK_CHANNEL})`, + 'and someone on the team can merge it for you.', + ].join(' '), + ].join('\n'), + }) + core.info(`Commented on #${number}.`) + } + } + + core.setFailed( + `Only ${TEAM} merges to main in this repository. ${reason} ` + + 'Ask in #docs-content and someone on the team can merge this for you.', + ) From e3bdc42e27a93f56ee647ef559425a32f0bc7113 Mon Sep 17 00:00:00 2001 From: docs-bot <77750099+docs-bot@users.noreply.github.com> Date: Mon, 17 Aug 2026 16:41:15 +0000 Subject: [PATCH 2/3] GraphQL schema update (#62806) Co-authored-by: github-merge-queue <118344674+github-merge-queue@users.noreply.github.com> --- src/graphql/data/fpt/changelog.json | 13 +++++++++++++ src/graphql/data/fpt/schema-enterprise-admin.json | 4 ---- src/graphql/data/fpt/schema.docs.graphql | 5 ----- src/graphql/data/ghec/schema-enterprise-admin.json | 4 ---- src/graphql/data/ghec/schema.docs.graphql | 5 ----- 5 files changed, 13 insertions(+), 18 deletions(-) diff --git a/src/graphql/data/fpt/changelog.json b/src/graphql/data/fpt/changelog.json index 5a316bb93a98..c84b778cce8d 100644 --- a/src/graphql/data/fpt/changelog.json +++ b/src/graphql/data/fpt/changelog.json @@ -1,4 +1,17 @@ [ + { + "schemaChanges": [ + { + "title": "The GraphQL schema includes these changes:", + "changes": [ + "

Enum value 'SECURITY_KEYwas removed from enumProofOfPresenceRequirement'

" + ] + } + ], + "previewChanges": [], + "upcomingChanges": [], + "date": "2026-08-17" + }, { "schemaChanges": [ { diff --git a/src/graphql/data/fpt/schema-enterprise-admin.json b/src/graphql/data/fpt/schema-enterprise-admin.json index 919728cdcb7c..94793fd5ef18 100644 --- a/src/graphql/data/fpt/schema-enterprise-admin.json +++ b/src/graphql/data/fpt/schema-enterprise-admin.json @@ -11825,10 +11825,6 @@ { "name": "REAUTH", "description": "

Members must complete a fresh re-authentication against the enterprise identity provider.

" - }, - { - "name": "SECURITY_KEY", - "description": "

Members must satisfy a phishing-resistant security key re-authentication (Microsoft Entra only).

" } ], "category": "enterprise-admin" diff --git a/src/graphql/data/fpt/schema.docs.graphql b/src/graphql/data/fpt/schema.docs.graphql index af988e028264..a984c46c8e0b 100644 --- a/src/graphql/data/fpt/schema.docs.graphql +++ b/src/graphql/data/fpt/schema.docs.graphql @@ -44014,11 +44014,6 @@ enum ProofOfPresenceRequirement @docsCategory(name: "enterprise-admin") { Members must complete a fresh re-authentication against the enterprise identity provider. """ REAUTH - - """ - Members must satisfy a phishing-resistant security key re-authentication (Microsoft Entra only). - """ - SECURITY_KEY } """ diff --git a/src/graphql/data/ghec/schema-enterprise-admin.json b/src/graphql/data/ghec/schema-enterprise-admin.json index 919728cdcb7c..94793fd5ef18 100644 --- a/src/graphql/data/ghec/schema-enterprise-admin.json +++ b/src/graphql/data/ghec/schema-enterprise-admin.json @@ -11825,10 +11825,6 @@ { "name": "REAUTH", "description": "

Members must complete a fresh re-authentication against the enterprise identity provider.

" - }, - { - "name": "SECURITY_KEY", - "description": "

Members must satisfy a phishing-resistant security key re-authentication (Microsoft Entra only).

" } ], "category": "enterprise-admin" diff --git a/src/graphql/data/ghec/schema.docs.graphql b/src/graphql/data/ghec/schema.docs.graphql index af988e028264..a984c46c8e0b 100644 --- a/src/graphql/data/ghec/schema.docs.graphql +++ b/src/graphql/data/ghec/schema.docs.graphql @@ -44014,11 +44014,6 @@ enum ProofOfPresenceRequirement @docsCategory(name: "enterprise-admin") { Members must complete a fresh re-authentication against the enterprise identity provider. """ REAUTH - - """ - Members must satisfy a phishing-resistant security key re-authentication (Microsoft Entra only). - """ - SECURITY_KEY } """ From 3de47bde96597f63cb6c02ddaaad8d4676957884 Mon Sep 17 00:00:00 2001 From: Steve Ward Date: Mon, 17 Aug 2026 19:13:37 +0000 Subject: [PATCH 3/3] Move Copilot integrations articles to new top-level category (#62770) Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Copilot-Session: 019627e9-343f-4c9a-8811-b9743e15f4a1 --- .../copilot/how-tos/copilot-integrations/index.md | 14 ++++++++++++++ .../integrate-cloud-agent-with-azure-boards.md | 1 + .../integrate-cloud-agent-with-jira.md | 1 + .../integrate-cloud-agent-with-linear.md | 1 + .../integrate-cloud-agent-with-slack.md | 1 + .../integrate-cloud-agent-with-teams.md | 1 + content/copilot/how-tos/index.md | 1 + .../use-copilot-agents/cloud-agent/index.md | 5 ----- 8 files changed, 20 insertions(+), 5 deletions(-) create mode 100644 content/copilot/how-tos/copilot-integrations/index.md rename content/copilot/how-tos/{use-copilot-agents/cloud-agent => copilot-integrations}/integrate-cloud-agent-with-azure-boards.md (98%) rename content/copilot/how-tos/{use-copilot-agents/cloud-agent => copilot-integrations}/integrate-cloud-agent-with-jira.md (99%) rename content/copilot/how-tos/{use-copilot-agents/cloud-agent => copilot-integrations}/integrate-cloud-agent-with-linear.md (98%) rename content/copilot/how-tos/{use-copilot-agents/cloud-agent => copilot-integrations}/integrate-cloud-agent-with-slack.md (99%) rename content/copilot/how-tos/{use-copilot-agents/cloud-agent => copilot-integrations}/integrate-cloud-agent-with-teams.md (98%) diff --git a/content/copilot/how-tos/copilot-integrations/index.md b/content/copilot/how-tos/copilot-integrations/index.md new file mode 100644 index 000000000000..78aace864282 --- /dev/null +++ b/content/copilot/how-tos/copilot-integrations/index.md @@ -0,0 +1,14 @@ +--- +title: GitHub Copilot integrations +shortTitle: Copilot integrations +intro: Learn how to use {% data variables.copilot.copilot_cloud_agent %} from the chat and project management tools where you already work. +versions: + feature: copilot +children: + - /integrate-cloud-agent-with-slack + - /integrate-cloud-agent-with-teams + - /integrate-cloud-agent-with-jira + - /integrate-cloud-agent-with-linear + - /integrate-cloud-agent-with-azure-boards +contentType: how-tos +--- diff --git a/content/copilot/how-tos/use-copilot-agents/cloud-agent/integrate-cloud-agent-with-azure-boards.md b/content/copilot/how-tos/copilot-integrations/integrate-cloud-agent-with-azure-boards.md similarity index 98% rename from content/copilot/how-tos/use-copilot-agents/cloud-agent/integrate-cloud-agent-with-azure-boards.md rename to content/copilot/how-tos/copilot-integrations/integrate-cloud-agent-with-azure-boards.md index e88d57420a62..a34bb684e624 100644 --- a/content/copilot/how-tos/use-copilot-agents/cloud-agent/integrate-cloud-agent-with-azure-boards.md +++ b/content/copilot/how-tos/copilot-integrations/integrate-cloud-agent-with-azure-boards.md @@ -10,6 +10,7 @@ contentType: how-tos category: - Integrate Copilot with your tools redirect_from: + - /copilot/how-tos/use-copilot-agents/cloud-agent/integrate-cloud-agent-with-azure-boards - /copilot/how-tos/use-copilot-agents/coding-agent/integrate-coding-agent-with-azure-boards --- diff --git a/content/copilot/how-tos/use-copilot-agents/cloud-agent/integrate-cloud-agent-with-jira.md b/content/copilot/how-tos/copilot-integrations/integrate-cloud-agent-with-jira.md similarity index 99% rename from content/copilot/how-tos/use-copilot-agents/cloud-agent/integrate-cloud-agent-with-jira.md rename to content/copilot/how-tos/copilot-integrations/integrate-cloud-agent-with-jira.md index 93c862da02b2..8c3b7247aabe 100644 --- a/content/copilot/how-tos/use-copilot-agents/cloud-agent/integrate-cloud-agent-with-jira.md +++ b/content/copilot/how-tos/copilot-integrations/integrate-cloud-agent-with-jira.md @@ -7,6 +7,7 @@ product: '{% data reusables.copilot.plans.permission-paid-plans-cfi %}' versions: feature: copilot redirect_from: + - /copilot/how-tos/use-copilot-agents/cloud-agent/integrate-cloud-agent-with-jira - /copilot/how-tos/use-copilot-agents/coding-agent/integrate-coding-agent-with-jira - /early-access/copilot/integrate-coding-agent-with-jira contentType: how-tos diff --git a/content/copilot/how-tos/use-copilot-agents/cloud-agent/integrate-cloud-agent-with-linear.md b/content/copilot/how-tos/copilot-integrations/integrate-cloud-agent-with-linear.md similarity index 98% rename from content/copilot/how-tos/use-copilot-agents/cloud-agent/integrate-cloud-agent-with-linear.md rename to content/copilot/how-tos/copilot-integrations/integrate-cloud-agent-with-linear.md index f3b0f1b9fc78..a44935853f95 100644 --- a/content/copilot/how-tos/use-copilot-agents/cloud-agent/integrate-cloud-agent-with-linear.md +++ b/content/copilot/how-tos/copilot-integrations/integrate-cloud-agent-with-linear.md @@ -10,6 +10,7 @@ contentType: how-tos category: - Integrate Copilot with your tools redirect_from: + - /copilot/how-tos/use-copilot-agents/cloud-agent/integrate-cloud-agent-with-linear - /copilot/how-tos/use-copilot-agents/coding-agent/integrate-coding-agent-with-linear --- diff --git a/content/copilot/how-tos/use-copilot-agents/cloud-agent/integrate-cloud-agent-with-slack.md b/content/copilot/how-tos/copilot-integrations/integrate-cloud-agent-with-slack.md similarity index 99% rename from content/copilot/how-tos/use-copilot-agents/cloud-agent/integrate-cloud-agent-with-slack.md rename to content/copilot/how-tos/copilot-integrations/integrate-cloud-agent-with-slack.md index 65d9d06a05d2..125ac8edc0f7 100644 --- a/content/copilot/how-tos/use-copilot-agents/cloud-agent/integrate-cloud-agent-with-slack.md +++ b/content/copilot/how-tos/copilot-integrations/integrate-cloud-agent-with-slack.md @@ -10,6 +10,7 @@ contentType: how-tos category: - Integrate Copilot with your tools redirect_from: + - /copilot/how-tos/use-copilot-agents/cloud-agent/integrate-cloud-agent-with-slack - /copilot/how-tos/use-copilot-agents/coding-agent/integrate-coding-agent-with-slack --- diff --git a/content/copilot/how-tos/use-copilot-agents/cloud-agent/integrate-cloud-agent-with-teams.md b/content/copilot/how-tos/copilot-integrations/integrate-cloud-agent-with-teams.md similarity index 98% rename from content/copilot/how-tos/use-copilot-agents/cloud-agent/integrate-cloud-agent-with-teams.md rename to content/copilot/how-tos/copilot-integrations/integrate-cloud-agent-with-teams.md index 45674bcabf36..79f98ef085bc 100644 --- a/content/copilot/how-tos/use-copilot-agents/cloud-agent/integrate-cloud-agent-with-teams.md +++ b/content/copilot/how-tos/copilot-integrations/integrate-cloud-agent-with-teams.md @@ -10,6 +10,7 @@ contentType: how-tos category: - Integrate Copilot with your tools redirect_from: + - /copilot/how-tos/use-copilot-agents/cloud-agent/integrate-cloud-agent-with-teams - /copilot/how-tos/use-copilot-agents/coding-agent/integrate-coding-agent-with-teams --- diff --git a/content/copilot/how-tos/index.md b/content/copilot/how-tos/index.md index 1306151effec..715b41da8b1d 100644 --- a/content/copilot/how-tos/index.md +++ b/content/copilot/how-tos/index.md @@ -15,6 +15,7 @@ children: - /copilot-sdk - /github-agentic-workflows - /use-copilot-agents + - /copilot-integrations - /use-ai-models - /provide-context - /configure-custom-instructions-in-your-ide diff --git a/content/copilot/how-tos/use-copilot-agents/cloud-agent/index.md b/content/copilot/how-tos/use-copilot-agents/cloud-agent/index.md index e7cf6696d58c..fcee18ceb244 100644 --- a/content/copilot/how-tos/use-copilot-agents/cloud-agent/index.md +++ b/content/copilot/how-tos/use-copilot-agents/cloud-agent/index.md @@ -19,11 +19,6 @@ children: - /use-cloud-agent-via-the-api - /use-cloud-agent-from-cli - /use-cloud-agent-with-mcp - - /integrate-cloud-agent-with-jira - - /integrate-cloud-agent-with-slack - - /integrate-cloud-agent-with-teams - - /integrate-cloud-agent-with-linear - - /integrate-cloud-agent-with-azure-boards - /use-cloud-agent-from-raycast - /troubleshoot-cloud-agent redirect_from: