From 0aa7134a51167c21179f037a8f9eee3c9dc2d6d4 Mon Sep 17 00:00:00 2001 From: Masahiro Nagano Date: Thu, 8 Oct 2026 23:23:16 +0900 Subject: [PATCH 1/2] fix: query merge methods with read-only token via GraphQL --- .github/workflows/dependabot-auto-merge.yml | 19 +++++++++-- .github/workflows/test.yml | 2 ++ tests/dependabot-auto-merge.test.cjs | 38 +++++++++++++++++++-- 3 files changed, 53 insertions(+), 6 deletions(-) diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml index 13c2356..2ad1224 100644 --- a/.github/workflows/dependabot-auto-merge.yml +++ b/.github/workflows/dependabot-auto-merge.yml @@ -91,9 +91,22 @@ jobs: if (!latest || latest.id !== run.id || latest.status !== 'completed' || latest.conclusion !== 'success' || latest.run_attempt !== run.run_attempt) continue; - const {data: repository} = await github.rest.repos.get({owner, repo}); - const method = repository.allow_merge_commit ? 'merge' : - repository.allow_squash_merge ? 'squash' : repository.allow_rebase_merge ? 'rebase' : null; + // REST omits allow_* settings for read-only tokens. GraphQL exposes them explicitly. + const {repository} = await github.graphql(` + query MergeMethods($owner: String!, $repo: String!) { + repository(owner: $owner, name: $repo) { + mergeCommitAllowed + squashMergeAllowed + rebaseMergeAllowed + } + } + `, {owner, repo}); + if (!repository || !['mergeCommitAllowed', 'squashMergeAllowed', 'rebaseMergeAllowed'] + .every(key => typeof repository[key] === 'boolean')) { + throw new Error('Could not determine enabled merge methods'); + } + const method = repository.mergeCommitAllowed ? 'merge' : + repository.squashMergeAllowed ? 'squash' : repository.rebaseMergeAllowed ? 'rebase' : null; if (!method) throw new Error('No merge method is enabled'); core.setOutput('pull-number', pr.number); core.setOutput('head-sha', pr.head.sha); diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f2ab39c..328c1d1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,3 +13,5 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - name: Test auto-merge policy run: node --test tests/*.test.cjs + env: + MERGE_SETTINGS_TOKEN: ${{ github.token }} diff --git a/tests/dependabot-auto-merge.test.cjs b/tests/dependabot-auto-merge.test.cjs index f0e7dac..15031b1 100644 --- a/tests/dependabot-auto-merge.test.cjs +++ b/tests/dependabot-auto-merge.test.cjs @@ -20,15 +20,18 @@ async function scenario(change = () => {}) { files: [{filename: '.github/workflows/tagpr.yml', status: 'modified'}], before: 'steps:\n - uses: Songmu/tagpr@old # v1\n env:\n TOKEN: example\n', after: 'steps:\n - uses: Songmu/tagpr@new # v2\n env:\n TOKEN: example\n', - repository: {allow_merge_commit: true}, merged: []}; + repository: {mergeCommitAllowed: true, squashMergeAllowed: true, rebaseMergeAllowed: true}, merged: []}; change(state); const github = {rest: { actions: {getWorkflowRun: async () => ({data: state.run}), listWorkflowRuns: 'runs'}, pulls: {list: 'prs', listFiles: 'files', get: async () => ({data: state.pr}), merge: async args => {state.merged.push(args); return {data: {merged: true, sha: 'merged'}};}}, - repos: {get: async () => ({data: state.repository}), getContent: async args => ({data: { + repos: {get: async () => ({data: {}}), getContent: async args => ({data: { type: 'file', encoding: 'base64', content: Buffer.from(args.ref === 'base' ? state.before : state.after).toString('base64'), }})}, + }, graphql: async (query, variables) => { + if (state.graphql) return state.graphql(query, variables); + return {repository: state.repository}; }, paginate: async (method) => ({runs: [state.latest], prs: [state.pr], files: state.files})[method]}; const context = {repo: {owner: 'monitoring-forge', repo: 'flagrun'}, payload: {workflow_run: state.eventRun, repository: {default_branch: 'main'}}}; @@ -50,7 +53,7 @@ test('tagpr-only grouped update merges exactly the tested SHA', async () => { }); test('pull_request CI and squash-only repositories are supported', async () => { const [merge] = await scenario(s => {s.run.event = 'pull_request'; s.run.path = '.github/workflows/ci.yml'; - s.repository = {allow_squash_merge: true};}); + s.repository = {mergeCommitAllowed: false, squashMergeAllowed: true, rebaseMergeAllowed: false};}); assert.equal(merge.merge_method, 'squash'); }); for (const [name, change] of Object.entries({ @@ -77,3 +80,32 @@ for (const [name, change] of Object.entries({ 'latest pending': s => {s.latest.status = 'in_progress';}, 'latest attempt changed': s => {s.latest.run_attempt = 2;}, })) test(`does not merge: ${name}`, async () => assert.deepEqual(await scenario(change), [])); + +test('rebase-only repositories are supported', async () => { + const [merge] = await scenario(s => {s.repository = { + mergeCommitAllowed: false, squashMergeAllowed: false, rebaseMergeAllowed: true, + };}); + assert.equal(merge.merge_method, 'rebase'); +}); +test('missing settings are not treated as disabled', async () => { + await assert.rejects(scenario(s => {s.repository = {};}), /Could not determine enabled merge methods/); +}); +test('explicitly disabled methods stop the merge', async () => { + await assert.rejects(scenario(s => {s.repository = { + mergeCommitAllowed: false, squashMergeAllowed: false, rebaseMergeAllowed: false, + };}), /No merge method is enabled/); +}); +test('read-only GITHUB_TOKEN can query merge methods', {skip: !process.env.MERGE_SETTINGS_TOKEN}, async () => { + await scenario(s => {s.graphql = async (query) => { + const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/'); + const response = await fetch('https://api.github.com/graphql', { + method: 'POST', + headers: {Authorization: `Bearer ${process.env.MERGE_SETTINGS_TOKEN}`, 'Content-Type': 'application/json'}, + body: JSON.stringify({query, variables: {owner, repo}}), + }); + assert.equal(response.status, 200); + const result = await response.json(); + assert.equal(result.errors, undefined); + return result.data; + };}); +}); From b239db35c2baae5ad5abcbfb1f68066bb018457b Mon Sep 17 00:00:00 2001 From: Masahiro Nagano Date: Thu, 8 Oct 2026 23:28:30 +0900 Subject: [PATCH 2/2] Update .github/workflows/test.yml Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 328c1d1..5f5a4b6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,4 +14,4 @@ jobs: - name: Test auto-merge policy run: node --test tests/*.test.cjs env: - MERGE_SETTINGS_TOKEN: ${{ github.token }} + MERGE_SETTINGS_TOKEN: ${{ github.event_name == 'push' && github.ref_name == github.event.repository.default_branch && github.token || '' }}