Skip to content

Commit deaaf28

Browse files
authored
feat(infra): add workflow to tag external issues and PRs (#1441)
Ready to go, but before it will work we need to: 1. Create a GitHub app and install it in our org/on this repo. It needs permissions to read org membership and to write to PR/issues (to add the labels). 2. Add these secrets to this repo: - `ORG_MEMBERSHIP_APP_ID` - `ORG_MEMBERSHIP_APP_PRIVATE_KEY`
1 parent 180428a commit deaaf28

File tree

1 file changed

+157
-0
lines changed

1 file changed

+157
-0
lines changed
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
# Automatically tag issues and pull requests as "external" or "internal"
2+
# based on whether the author is a member of the langchain-ai
3+
# GitHub organization.
4+
#
5+
# Setup Requirements:
6+
# Option 1: GitHub App (Recommended - no user dependency)
7+
# 1. Create a GitHub App with permissions:
8+
# - Repository: Issues (write), Pull requests (write)
9+
# - Organization: Members (read)
10+
# 2. Install the app on your organization
11+
# 3. Add these secrets:
12+
# - ORG_MEMBERSHIP_APP_ID: Your app's ID
13+
# - ORG_MEMBERSHIP_APP_PRIVATE_KEY: Your app's private key
14+
#
15+
# Option 2: Personal Access Token (simpler but user-dependent)
16+
# 1. Create a Personal Access Token (classic) with:
17+
# - repo scope (for issues/PRs access)
18+
# - read:org scope (for organization membership checks)
19+
# 2. Add the PAT as ORG_MEMBERSHIP_TOKEN repository secret
20+
#
21+
# The workflow will fall back to GITHUB_TOKEN if no secrets are set,
22+
# but this will only show public organization memberships.
23+
24+
name: Tag External Contributions
25+
26+
on:
27+
issues:
28+
types: [opened]
29+
pull_request_target:
30+
types: [opened]
31+
32+
jobs:
33+
tag-external:
34+
runs-on: ubuntu-latest
35+
permissions:
36+
issues: write
37+
pull-requests: write
38+
39+
steps:
40+
- name: Generate GitHub App token
41+
if: ${{ secrets.ORG_MEMBERSHIP_APP_ID && secrets.ORG_MEMBERSHIP_APP_PRIVATE_KEY }}
42+
id: app-token
43+
uses: tibdex/github-app-token@v1
44+
with:
45+
app_id: ${{ secrets.ORG_MEMBERSHIP_APP_ID }}
46+
private_key: ${{ secrets.ORG_MEMBERSHIP_APP_PRIVATE_KEY }}
47+
48+
- name: Check if contributor is external
49+
id: check-membership
50+
uses: actions/github-script@v7
51+
with:
52+
# Use GitHub App token, PAT, or fallback to GITHUB_TOKEN
53+
github-token: ${{ steps.app-token.outputs.token || secrets.ORG_MEMBERSHIP_TOKEN || secrets.GITHUB_TOKEN }}
54+
script: |
55+
const { owner, repo } = context.repo;
56+
const author = context.payload.sender.login;
57+
58+
try {
59+
// Check if the author is a member of the langchain-ai organization
60+
// This requires org:read permissions to see private memberships
61+
const membership = await github.rest.orgs.getMembershipForUser({
62+
org: 'langchain-ai',
63+
username: author
64+
});
65+
66+
// Check if membership is active (not just pending invitation)
67+
if (membership.data.state === 'active') {
68+
console.log(`User ${author} is an active member of langchain-ai organization`);
69+
core.setOutput('is-external', 'false');
70+
} else {
71+
console.log(`User ${author} has pending membership in langchain-ai organization`);
72+
core.setOutput('is-external', 'true');
73+
}
74+
} catch (error) {
75+
if (error.status === 404) {
76+
console.log(`User ${author} is not a member of langchain-ai organization`);
77+
core.setOutput('is-external', 'true');
78+
} else {
79+
console.error('Error checking membership:', error);
80+
console.log('Status:', error.status);
81+
console.log('Message:', error.message);
82+
// If we can't determine membership due to API error, assume external for safety
83+
core.setOutput('is-external', 'true');
84+
}
85+
}
86+
87+
- name: Add external label to issue
88+
if: steps.check-membership.outputs.is-external == 'true' && github.event_name == 'issues'
89+
uses: actions/github-script@v7
90+
with:
91+
github-token: ${{ secrets.GITHUB_TOKEN }}
92+
script: |
93+
const { owner, repo } = context.repo;
94+
const issue_number = context.payload.issue.number;
95+
96+
await github.rest.issues.addLabels({
97+
owner,
98+
repo,
99+
issue_number,
100+
labels: ['external']
101+
});
102+
103+
console.log(`Added 'external' label to issue #${issue_number}`);
104+
105+
- name: Add external label to pull request
106+
if: steps.check-membership.outputs.is-external == 'true' && github.event_name == 'pull_request_target'
107+
uses: actions/github-script@v7
108+
with:
109+
github-token: ${{ secrets.GITHUB_TOKEN }}
110+
script: |
111+
const { owner, repo } = context.repo;
112+
const pull_number = context.payload.pull_request.number;
113+
114+
await github.rest.issues.addLabels({
115+
owner,
116+
repo,
117+
issue_number: pull_number,
118+
labels: ['external']
119+
});
120+
121+
console.log(`Added 'external' label to pull request #${pull_number}`);
122+
123+
- name: Add internal label to issue
124+
if: steps.check-membership.outputs.is-external == 'false' && github.event_name == 'issues'
125+
uses: actions/github-script@v7
126+
with:
127+
github-token: ${{ secrets.GITHUB_TOKEN }}
128+
script: |
129+
const { owner, repo } = context.repo;
130+
const issue_number = context.payload.issue.number;
131+
132+
await github.rest.issues.addLabels({
133+
owner,
134+
repo,
135+
issue_number,
136+
labels: ['internal']
137+
});
138+
139+
console.log(`Added 'internal' label to issue #${issue_number}`);
140+
141+
- name: Add internal label to pull request
142+
if: steps.check-membership.outputs.is-external == 'false' && github.event_name == 'pull_request_target'
143+
uses: actions/github-script@v7
144+
with:
145+
github-token: ${{ secrets.GITHUB_TOKEN }}
146+
script: |
147+
const { owner, repo } = context.repo;
148+
const pull_number = context.payload.pull_request.number;
149+
150+
await github.rest.issues.addLabels({
151+
owner,
152+
repo,
153+
issue_number: pull_number,
154+
labels: ['internal']
155+
});
156+
157+
console.log(`Added 'internal' label to pull request #${pull_number}`);

0 commit comments

Comments
 (0)