Skip to content

Instantly share code, notes, and snippets.

@daleyjem
Last active February 10, 2026 04:44
Show Gist options
  • Select an option

  • Save daleyjem/4911688e8fff22fd16447bc40f92b83f to your computer and use it in GitHub Desktop.

Select an option

Save daleyjem/4911688e8fff22fd16447bc40f92b83f to your computer and use it in GitHub Desktop.

Github Rulesets, Codeowners, and Merge Queue

In order to bypass codeowner requirements when you have merge queue enabled, you need to jump through a few hoops. This is especially useful when you're in a repo with thousands of files, and 20+ teams, each with codeownership. I'm an admin, so I don't want to get approval just because I enabled a new eslint rule, and autofixed tons of files. However, if I simply admin merge, I don't get the benefit of having merge queue to catch race conditions... the PR goes directly into the target branch.

The following is the next best workaround. It's not the most ideal thing in the world, but what do you want from me?

  1. Create an app/bot with elevated permissions. Mine has god-ish permissions, so I'm not sure which exactly are required for this tbh.
  2. Create 2 rulesets. One with the majority of requirements (status checks, merge queue, etc.). The other exclusively for codeowner enablement.
  3. Make the app/bot your bypass actor for the codeowner ruleset, and set to "Exempt".
  4. Create a dispatchable workflow with a PR number input.
  5. Acquire a server-to-server token for the app to make some API calls
  6. First call is just a preliminary check to verify the "actor" permissions of the dispatch user are "admin":
  const { data: permission } =
    await octokit.rest.repos.getCollaboratorPermissionLevel({
      owner,
      repo,
      username,
    });

  return permission.permission === 'admin';
  1. Lastly, use this API/graphql to enqueue your PR to merge queue on behalf of the app/bot.
  const pr = await octokit.rest.pulls.get({
    owner,
    repo,
    pull_number: prNumber,
  });

  if (pr.data.state !== 'open') {
    throw new Error(`PR #${prNumber} is not open (state: ${pr.data.state})`);
  }

  // Use GraphQL to add PR to merge queue
  const query = `
    mutation EnqueuePullRequest($pullRequestId: ID!) {
      enqueuePullRequest(input: {
        pullRequestId: $pullRequestId,
        expectedHeadOid: "${pr.data.head.sha}"
      }) {
        mergeQueueEntry {
          id
          position
          state
        }
      }
    }
  `;

  await octokit
    .graphql(query, {
      pullRequestId: pr.data.node_id,
    })
    .catch((error) => {
      throw new Error(
        `Failed to send PR #${prNumber} to merge queue: ${error.message}`,
      );
    });

To be honest, this seems like something that should come out of the box with Github. They introduced all these great new features, but they simply don't work well together. With any luck, this gist will be obsolete in another 5 years.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment