Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Before you create this pull request

Thanks for your interest. This repository is a read-only mirror of a private repository and **we don't accept pull requests**. A workflow will close this PR automatically.

Do this instead:

- Cancel this PR.
- Report bugs, request features, or share feedback in the [Shopify dev community forums](https://community.shopify.dev/c/shopify-cli-libraries/14)

For more details see [CONTRIBUTING.md](https://github.com/Shopify/shopify-app-python/blob/main/CONTRIBUTING.md).
64 changes: 64 additions & 0 deletions .github/workflows/close-external-prs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
name: Close External PRs

on:

Copilot AI Jan 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using pull_request_target for external PRs is a known security risk as it runs with elevated permissions and access to secrets in the context of the base branch. While this workflow doesn't check out code, it's still important to ensure that no user-controlled input is used in potentially dangerous operations. The current implementation appears safe, but consider adding a comment documenting why pull_request_target is necessary here (to have write permissions for closing PRs) and confirming that no user input is used unsafely.

Suggested change
on:
on:
# NOTE: We intentionally use pull_request_target here so this workflow has write
# permissions to comment on and close PRs opened from forks/external contributors.
# This workflow does NOT check out or execute code from the PR, and the only
# user-controlled inputs used are the PR author login and PR number, which are
# passed directly as identifiers to GitHub's REST API (no commands or secrets).

Copilot uses AI. Check for mistakes.
pull_request_target:
types: [opened, reopened]

permissions:
pull-requests: write

jobs:
close-external-pr:
runs-on: ubuntu-latest
steps:
- name: Check if PR author is from Shopify
id: check-author
uses: actions/github-script@v7
with:
script: |
const author = context.payload.pull_request.user.login;

try {
// Check if the author is a member of the Shopify organization
await github.rest.orgs.checkMembershipForUser({
org: 'Shopify',
username: author
});

console.log(`${author} is a Shopify member`);
core.setOutput('is-shopify', 'true');
} catch (error) {
console.log(`${author} is not a Shopify member or the check failed: ${error.message}`);
Comment on lines +20 to +31

Copilot AI Jan 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The checkMembershipForUser API call only succeeds if the organization membership is public or if the GitHub token has appropriate permissions to see private memberships. This could result in false negatives where actual Shopify members with private membership visibility have their PRs incorrectly closed. Consider either: (1) documenting that Shopify members must make their membership public, (2) using a different authentication method with org:read permissions, or (3) checking team membership instead if the repository has a specific team with access.

Suggested change
try {
// Check if the author is a member of the Shopify organization
await github.rest.orgs.checkMembershipForUser({
org: 'Shopify',
username: author
});
console.log(`${author} is a Shopify member`);
core.setOutput('is-shopify', 'true');
} catch (error) {
console.log(`${author} is not a Shopify member or the check failed: ${error.message}`);
const authorAssociation = context.payload.pull_request.author_association;
// Treat PR authors with OWNER or MEMBER association as internal Shopify contributors.
const internalAssociations = ['OWNER', 'MEMBER'];
if (internalAssociations.includes(authorAssociation)) {
console.log(`${author} is considered an internal Shopify member (association: ${authorAssociation})`);
core.setOutput('is-shopify', 'true');
} else {
console.log(`${author} is not considered an internal Shopify member (association: ${authorAssociation})`);

Copilot uses AI. Check for mistakes.
core.setOutput('is-shopify', 'false');
Comment on lines +31 to +32

Copilot AI Jan 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error handling catches all exceptions and treats them as "not a Shopify member", which could include legitimate API failures, rate limiting errors, or permission issues. This means that during an API outage or rate limit scenario, all PRs (including those from Shopify members) would be closed. Consider checking the error type and only treating 404 (Not Found) as "not a member", while handling other errors (403, 500, rate limits) differently or failing the workflow.

Suggested change
console.log(`${author} is not a Shopify member or the check failed: ${error.message}`);
core.setOutput('is-shopify', 'false');
// Only treat 404 (Not Found) as "not a Shopify member".
// For other errors (e.g., 403, 429, 500), fail the workflow to
// avoid incorrectly closing PRs during API issues.
if (error && error.status === 404) {
console.log(`${author} is not a Shopify member (404): ${error.message}`);
core.setOutput('is-shopify', 'false');
} else {
core.setFailed(`Failed to check Shopify membership for ${author}: ${error && error.message ? error.message : error}`);
}

Copilot uses AI. Check for mistakes.
}

- name: Close PR and comment
if: steps.check-author.outputs.is-shopify == 'false'
uses: actions/github-script@v7
with:
script: |
const prNumber = context.payload.pull_request.number;

// Add comment to PR
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: `Thanks for your interest. This repository does not accept contributions, so we've closed this PR.

To report a bug, request a feature, or share feedback, please post in the [Shopify dev community forums](https://community.shopify.dev/c/shopify-cli-libraries/14)

We triage in the forums, not in this repo. PRs and issues here are closed without review.

For more details see CONTRIBUTING.md`

Copilot AI Jan 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reference to CONTRIBUTING.md should be formatted as a proper markdown link for consistency with the Pull Request Template, which uses a full URL. Consider changing this to a hyperlink format like: CONTRIBUTING.md or at minimum use a relative link format.

Suggested change
For more details see CONTRIBUTING.md`
For more details see [CONTRIBUTING.md](https://github.com/Shopify/shopify-app-python/blob/main/CONTRIBUTING.md)`

Copilot uses AI. Check for mistakes.
});

// Close the PR
await github.rest.pulls.update({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber,
state: 'closed'
});

console.log(`Closed PR #${prNumber} from external contributor`);