Skip to content

Repo sync

Repo sync #32989

name: Confirm internal staff meant to post in public

Check warning on line 1 in .github/workflows/confirm-internal-staff-work-in-docs.yml

View workflow run for this annotation

GitHub Actions / Confirm internal staff meant to post in public

Workflow execution policy warning (evaluate mode)

On November 2, 2026, GitHub will restrict `pull_request_target` on public repositories by default. To continue allowing the event trigger, configure an Actions policy. Learn more: https://gh.io/securely-using-pull_request_target#default-policy-for-pull_request_target
# Asks GitHub staff to confirm public issues and PRs belong in github/docs.
on:
issues:
types:
- opened
- transferred
# pull_request_target lets the workflow check org membership for forked PR authors.
pull_request_target:
types:
- opened
permissions:
contents: read
jobs:
check-team-membership:
runs-on: ubuntu-latest
continue-on-error: true
if: github.repository == 'github/docs' && github.actor != 'docs-bot'
steps:
- id: membership_check
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3
env:
TEAM_CONTENT_REPO: ${{ secrets.TEAM_CONTENT_REPO }}
with:
github-token: ${{ secrets.DOCS_BOT_PAT_BASE }}
script: |
// Only GitHub employees need confirmation before public posts stay public.
try {
await github.rest.teams.getMembershipForUserInOrg({
org: 'github',
team_slug: 'employees',
username: context.payload.sender.login,
});
} catch(err) {
// The employee membership lookup throws for non-employees, so skip confirmation.
return
}
// Docs team members can intentionally work in github/docs.
try {
// 9919 is the github org and 325922 the docs team; numeric IDs survive renames.
await github.request('GET /organizations/{org_id}/team/{team_id}/memberships/{username}', {
org_id: 9919,
team_id: 325922,
username: context.payload.sender.login,
});
return
} catch(err) {
// The docs team membership lookup throws for non-members, who need an internal confirmation issue.
}
const issueNo = context.number || context.issue.number
await github.rest.issues.create({
owner: 'github',
repo: process.env.TEAM_CONTENT_REPO,
title: `@${context.payload.sender.login} confirm that \#${issueNo} should be in the public github/docs repo`,
body: `@${context.payload.sender.login} opened https://github.com/github/docs/issues/${issueNo} publicly in the github/docs repo, instead of the private github/${process.env.TEAM_CONTENT_REPO} repo.\n\n@${context.payload.sender.login}, if this specifically belongs in the public repo, and no sensitive information is disclosed, please ignore this message. Otherwise, please ${context.payload.pull_request ? 'close the pull request and recreate it internally in https://github.com/github/docs-content.' : 'close the issue and reopen it internally in https://github.com/github/docs-content, or transfer it there.'}\n\nIf sensitive information was shared, please close https://github.com/github/docs/issues/${issueNo} and notify us in the \#docs-open-source channel, so we can delete it. Thanks!`,
labels: ['OS confirmation', 'skip FR board'],
});
core.setOutput('did_warn', 'true')
- name: Resolve Slack user ID from username
id: slack_user
if: ${{ steps.membership_check.outputs.did_warn == 'true' }}
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_DOCS_BOT_TOKEN }}
with:
github-token: ${{ github.token }}
script: |
const githubLogin = context.payload.sender.login.toLowerCase();
core.info(`Looking up Slack user for @${githubLogin}...`);
core.setOutput('slack_user_id', '');
core.setOutput('slack_mention', `GitHub user \`${context.payload.sender.login}\``);
const token = process.env.SLACK_BOT_TOKEN;
if (!token) {
throw new Error('SLACK_DOCS_BOT_TOKEN is not configured.');
}
let cursor = '';
let pages = 0;
const matches = [];
do {
core.info(`Fetching Slack users.list page ${pages + 1}...`);
const url = new URL('https://slack.com/api/users.list');
url.searchParams.set('limit', '200');
if (cursor) {
url.searchParams.set('cursor', cursor);
}
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${token}`,
},
});
if (!response.ok) {
throw new Error(`Slack users.list returned HTTP ${response.status}.`);
}
const result = await response.json();
if (!result.ok) {
throw new Error(`Slack users.list failed: ${result.error || 'unknown error'}.`);
}
if (!Array.isArray(result.members)) {
throw new Error('Slack users.list response did not include a members array.');
}
pages += 1;
matches.push(...result.members.filter((member) =>
!member.deleted &&
!member.is_bot &&
!member.is_app_user &&
typeof member.id === 'string' &&
typeof member.name === 'string' &&
member.name.toLowerCase() === githubLogin
));
cursor = result.response_metadata?.next_cursor || '';
} while (cursor);
core.info(`Checked ${pages} pages of users.list. Found ${matches.length} match(es) for @${githubLogin}.`);
if (matches.length !== 1) {
core.warning(`Expected one Slack username matching @${githubLogin}; found ${matches.length}. No DM will be sent.`);
return;
}
core.info(`Will DM Slack user ${matches[0].id}.`);
core.setOutput('slack_user_id', matches[0].id);
core.setOutput('slack_mention', `<@${matches[0].id}>`);
- name: DM the author in Slack
id: slack_dm
if: ${{ steps.slack_user.outputs.slack_user_id != '' }}
uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0
with:
method: chat.postMessage
token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }}
errors: true
payload: |
channel: ${{ steps.slack_user.outputs.slack_user_id }}
text: ${{ toJSON(format('You opened {0} publicly in github/docs instead of the private repo. An internal confirmation issue was created.', github.event.issue.html_url || github.event.pull_request.html_url)) }}
- name: Log Slack DM result
if: ${{ !cancelled() && steps.membership_check.outputs.did_warn == 'true' }}
env:
SLACK_USER_ID: ${{ steps.slack_user.outputs.slack_user_id }}
SLACK_LOOKUP_OUTCOME: ${{ steps.slack_user.outcome }}
SLACK_DM_OUTCOME: ${{ steps.slack_dm.outcome }}
SLACK_DM_OK: ${{ steps.slack_dm.outputs.ok }}
SLACK_DM_RESPONSE: ${{ steps.slack_dm.outputs.response }}
SLACK_DM_TS: ${{ steps.slack_dm.outputs.ts }}
run: |
echo "Slack user lookup outcome: ${SLACK_LOOKUP_OUTCOME:-not run}"
if [ -z "$SLACK_USER_ID" ]; then
echo "Slack DM skipped because no Slack user ID was resolved."
elif [ "$SLACK_DM_OK" = "true" ]; then
echo "Slack DM sent successfully (timestamp: ${SLACK_DM_TS:-unavailable})."
else
echo "Slack DM did not return success (step outcome: ${SLACK_DM_OUTCOME:-unknown})."
if [ -n "$SLACK_DM_RESPONSE" ]; then
printf '%s\n' "$SLACK_DM_RESPONSE" | jq -r 'if type == "object" and (.error | type) == "string" then "Slack API error: " + .error else "Slack API response did not include an error field." end'
else
echo "Slack DM API response was unavailable."
fi
fi
- name: Notify the docs-open-source channel
id: slack_channel_notification
if: ${{ !cancelled() && steps.membership_check.outputs.did_warn == 'true' }}
uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0
env:
SLACK_CHANNEL_ID: ${{ secrets.DOCS_OPEN_SOURCE_SLACK_CHANNEL_ID }}
SLACK_MESSAGE: ${{ format('{0} opened {1} publicly in github/docs instead of the private repo. An internal confirmation issue was created.', steps.slack_user.outputs.slack_mention, github.event.issue.html_url || github.event.pull_request.html_url) }}
with:
method: chat.postMessage
token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }}
errors: true
payload: |
channel: ${{ toJSON(env.SLACK_CHANNEL_ID) }}
text: ${{ toJSON(env.SLACK_MESSAGE) }}
- name: Log Slack channel notification result
if: ${{ !cancelled() && steps.membership_check.outputs.did_warn == 'true' }}
env:
SLACK_CHANNEL_OUTCOME: ${{ steps.slack_channel_notification.outcome }}
SLACK_CHANNEL_OK: ${{ steps.slack_channel_notification.outputs.ok }}
SLACK_CHANNEL_RESPONSE: ${{ steps.slack_channel_notification.outputs.response }}
SLACK_CHANNEL_TS: ${{ steps.slack_channel_notification.outputs.ts }}
run: |
if [ "$SLACK_CHANNEL_OK" = "true" ]; then
echo "Slack channel notification sent successfully (timestamp: ${SLACK_CHANNEL_TS:-unavailable})."
else
echo "Slack channel notification did not return success (step outcome: ${SLACK_CHANNEL_OUTCOME:-unknown})."
if [ -n "$SLACK_CHANNEL_RESPONSE" ]; then
printf '%s\n' "$SLACK_CHANNEL_RESPONSE" | jq -r 'if type == "object" and (.error | type) == "string" then "Slack API error: " + .error else "Slack API response did not include an error field." end'
else
echo "Slack channel API response was unavailable."
fi
fi
- name: Check out repo
if: ${{ failure() && github.event_name != 'pull_request_target' }}
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: ./.github/actions/slack-alert
if: ${{ failure() && github.event_name != 'pull_request_target' }}
with:
slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }}