From 902bfb633cfd6ebf5f2899a8e8761fb66d955363 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 17 Jun 2026 15:44:02 -0700 Subject: [PATCH 1/6] chore: add temporary GITHUB_TOKEN write-permission diagnostic Probes branch-ref, tag-ref, and release creation in isolation with the default GITHUB_TOKEN to determine which write the token is denied. Each probe is independent and self-cleaning. Temporary; remove after diagnosis. --- .github/workflows/diagnose-token.yml | 70 ++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 .github/workflows/diagnose-token.yml diff --git a/.github/workflows/diagnose-token.yml b/.github/workflows/diagnose-token.yml new file mode 100644 index 00000000..e1d06167 --- /dev/null +++ b/.github/workflows/diagnose-token.yml @@ -0,0 +1,70 @@ +name: diagnose-token + +# Diagnostic: isolate whether the default GITHUB_TOKEN can create a branch ref, +# a tag ref, and a release in this repo. Mirrors the token context that the +# release-please job uses (push/PR event, same repo, contents: write). +# Temporary — delete once we've identified what release creation is blocked on. + +on: + pull_request: + branches: + - master + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +jobs: + diagnose: + runs-on: ubuntu-latest + steps: + - uses: actions/github-script@v7 + with: + script: | + const sha = context.sha; + const id = context.runId; + const owner = context.repo.owner; + const repo = context.repo.repo; + const results = []; + + async function probe(label, fn, cleanup) { + try { + const res = await fn(); + results.push(`✅ ${label}: OK`); + if (cleanup) { + try { await cleanup(res); } catch (e) { core.warning(`cleanup failed for ${label}: ${e.status} ${e.message}`); } + } + } catch (e) { + results.push(`❌ ${label}: ${e.status} ${e.message}`); + } + } + + // 1. Control: create a branch ref (refs/heads/...) — PRs already prove this works. + await probe( + 'create branch ref (refs/heads)', + () => github.rest.git.createRef({ owner, repo, ref: `refs/heads/zzz-diag-branch-${id}`, sha }), + () => github.rest.git.deleteRef({ owner, repo, ref: `heads/zzz-diag-branch-${id}` }) + ); + + // 2. Create a tag ref (refs/tags/...) directly. + await probe( + 'create tag ref (refs/tags)', + () => github.rest.git.createRef({ owner, repo, ref: `refs/tags/zzz-diag-tag-${id}`, sha }), + () => github.rest.git.deleteRef({ owner, repo, ref: `tags/zzz-diag-tag-${id}` }) + ); + + // 3. Create a release (auto-creates its tag) — this is the exact call release-please makes. + await probe( + 'create release (repos.createRelease)', + () => github.rest.repos.createRelease({ owner, repo, tag_name: `zzz-diag-release-${id}`, target_commitish: sha, name: 'diagnostic', body: 'diagnostic', draft: false, prerelease: true }), + async (res) => { + await github.rest.repos.deleteRelease({ owner, repo, release_id: res.data.id }); + await github.rest.git.deleteRef({ owner, repo, ref: `tags/zzz-diag-release-${id}` }); + } + ); + + const summary = results.join('\n'); + core.notice(`Token write probes:\n${summary}`); + await core.summary.addHeading('GITHUB_TOKEN write probes').addRaw('\n```\n' + summary + '\n```\n').write(); + console.log('\n=== RESULTS ===\n' + summary + '\n'); From ef4e8d9fdeb2cf04fca4bf3e2c002c93488f6c4b Mon Sep 17 00:00:00 2001 From: David Date: Wed, 17 Jun 2026 15:46:12 -0700 Subject: [PATCH 2/6] chore: add version-style tag probes to token diagnostic --- .github/workflows/diagnose-token.yml | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/.github/workflows/diagnose-token.yml b/.github/workflows/diagnose-token.yml index e1d06167..cd8e587c 100644 --- a/.github/workflows/diagnose-token.yml +++ b/.github/workflows/diagnose-token.yml @@ -54,9 +54,9 @@ jobs: () => github.rest.git.deleteRef({ owner, repo, ref: `tags/zzz-diag-tag-${id}` }) ); - // 3. Create a release (auto-creates its tag) — this is the exact call release-please makes. + // 3. Create a release (auto-creates its tag) — non-version tag name. await probe( - 'create release (repos.createRelease)', + 'create release, non-version tag (zzz-diag-release)', () => github.rest.repos.createRelease({ owner, repo, tag_name: `zzz-diag-release-${id}`, target_commitish: sha, name: 'diagnostic', body: 'diagnostic', draft: false, prerelease: true }), async (res) => { await github.rest.repos.deleteRelease({ owner, repo, release_id: res.data.id }); @@ -64,6 +64,23 @@ jobs: } ); + // 4. Create a VERSION-style tag ref (v*) — does a ruleset target release tags? + await probe( + 'create version tag ref (refs/tags/v0.0.0-diag)', + () => github.rest.git.createRef({ owner, repo, ref: `refs/tags/v0.0.0-diag-${id}`, sha }), + () => github.rest.git.deleteRef({ owner, repo, ref: `tags/v0.0.0-diag-${id}` }) + ); + + // 5. Create a release with a VERSION-style tag — the shape release-please uses. + await probe( + 'create release, version tag (v0.0.0-diag)', + () => github.rest.repos.createRelease({ owner, repo, tag_name: `v0.0.0-diag-${id}`, target_commitish: sha, name: 'diagnostic', body: 'diagnostic', draft: false, prerelease: true, make_latest: 'false' }), + async (res) => { + await github.rest.repos.deleteRelease({ owner, repo, release_id: res.data.id }); + await github.rest.git.deleteRef({ owner, repo, ref: `tags/v0.0.0-diag-${id}` }); + } + ); + const summary = results.join('\n'); core.notice(`Token write probes:\n${summary}`); await core.summary.addHeading('GITHUB_TOKEN write probes').addRaw('\n```\n' + summary + '\n```\n').write(); From ee01f194d480a461dbf3343073050435fd48c056 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 17 Jun 2026 15:47:43 -0700 Subject: [PATCH 3/6] chore: test push-event context and exact release-please release shape --- .github/workflows/diagnose-token.yml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/.github/workflows/diagnose-token.yml b/.github/workflows/diagnose-token.yml index cd8e587c..f2fb97ff 100644 --- a/.github/workflows/diagnose-token.yml +++ b/.github/workflows/diagnose-token.yml @@ -9,6 +9,9 @@ on: pull_request: branches: - master + push: + branches: + - diagnose-token-tag-permissions workflow_dispatch: permissions: @@ -81,7 +84,17 @@ jobs: } ); - const summary = results.join('\n'); + // 6. Exact release-please mirror: non-prerelease, make_latest=true. + await probe( + 'create release, EXACT release-please shape (make_latest=true)', + () => github.rest.repos.createRelease({ owner, repo, tag_name: `v0.0.0-diaglatest-${id}`, target_commitish: sha, name: 'diagnostic', body: 'diagnostic', draft: false, prerelease: false, make_latest: 'true' }), + async (res) => { + await github.rest.repos.deleteRelease({ owner, repo, release_id: res.data.id }); + await github.rest.git.deleteRef({ owner, repo, ref: `tags/v0.0.0-diaglatest-${id}` }); + } + ); + + const summary = `event=${context.eventName}\n` + results.join('\n'); core.notice(`Token write probes:\n${summary}`); await core.summary.addHeading('GITHUB_TOKEN write probes').addRaw('\n```\n' + summary + '\n```\n').write(); console.log('\n=== RESULTS ===\n' + summary + '\n'); From d051a6f5b8e46df744bd43719e9bdfa04160bd42 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 17 Jun 2026 15:51:28 -0700 Subject: [PATCH 4/6] chore: probe release creation at #1318 merge SHA --- .github/workflows/diagnose-token.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/diagnose-token.yml b/.github/workflows/diagnose-token.yml index f2fb97ff..0a4ec1c5 100644 --- a/.github/workflows/diagnose-token.yml +++ b/.github/workflows/diagnose-token.yml @@ -94,6 +94,17 @@ jobs: } ); + // 7. EXACT release-please target: #1318 merge SHA as target_commitish (throwaway tag). + const prSha = 'aee8ca60d93729893070c4d71fc22305cadefcb6'; + await probe( + 'create release @ #1318 merge SHA (throwaway tag)', + () => github.rest.repos.createRelease({ owner, repo, tag_name: `v0.0.0-diagpr-${id}`, target_commitish: prSha, name: 'diagnostic', body: 'diagnostic', draft: false, prerelease: false, make_latest: 'false' }), + async (res) => { + await github.rest.repos.deleteRelease({ owner, repo, release_id: res.data.id }); + await github.rest.git.deleteRef({ owner, repo, ref: `tags/v0.0.0-diagpr-${id}` }); + } + ); + const summary = `event=${context.eventName}\n` + results.join('\n'); core.notice(`Token write probes:\n${summary}`); await core.summary.addHeading('GITHUB_TOKEN write probes').addRaw('\n```\n' + summary + '\n```\n').write(); From 27e5f504e5ee534191e52e77716cf205b3e9a1b4 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 17 Jun 2026 15:53:20 -0700 Subject: [PATCH 5/6] chore: control probes for user-authored non-tip commits --- .github/workflows/diagnose-token.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.github/workflows/diagnose-token.yml b/.github/workflows/diagnose-token.yml index 0a4ec1c5..42531533 100644 --- a/.github/workflows/diagnose-token.yml +++ b/.github/workflows/diagnose-token.yml @@ -105,6 +105,21 @@ jobs: } ); + // 8/9. Releases at USER-authored non-tip master commits (control for "bot-authored commit" theory). + for (const c of [ + { label: 'user commit 330e8397 (David)', sha: '330e8397926aca76adc4fceabbd5a9be6c5021c8', tag: 'diaguser1' }, + { label: 'user commit 008dc2f4 (gregbaroni)', sha: '008dc2f4', tag: 'diaguser2' }, + ]) { + await probe( + `create release @ ${c.label}`, + () => github.rest.repos.createRelease({ owner, repo, tag_name: `v0.0.0-${c.tag}-${id}`, target_commitish: c.sha, name: 'diagnostic', body: 'diagnostic', draft: false, prerelease: false, make_latest: 'false' }), + async (res) => { + await github.rest.repos.deleteRelease({ owner, repo, release_id: res.data.id }); + await github.rest.git.deleteRef({ owner, repo, ref: `tags/v0.0.0-${c.tag}-${id}` }); + } + ); + } + const summary = `event=${context.eventName}\n` + results.join('\n'); core.notice(`Token write probes:\n${summary}`); await core.summary.addHeading('GITHUB_TOKEN write probes').addRaw('\n```\n' + summary + '\n```\n').write(); From 73e2e22b9658208540aad68a6ee8636f85fd1837 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 17 Jun 2026 15:55:15 -0700 Subject: [PATCH 6/6] chore: disambiguate non-tip vs protected-master --- .github/workflows/diagnose-token.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.github/workflows/diagnose-token.yml b/.github/workflows/diagnose-token.yml index 42531533..5dcf8d3e 100644 --- a/.github/workflows/diagnose-token.yml +++ b/.github/workflows/diagnose-token.yml @@ -120,6 +120,21 @@ jobs: ); } + // 10/11. Disambiguate "non-tip" vs "protected master". + for (const c of [ + { label: 'master HEAD (tip of protected branch)', sha: 'bdc4670e86a1a0408b5953a882285cbaaa9d3fae', tag: 'diagmastertip' }, + { label: 'non-tip commit on MY feature branch', sha: 'ef4e8d9fdeb2cf04fca4bf3e2c002c93488f6c4b', tag: 'diagbranchnontip' }, + ]) { + await probe( + `create release @ ${c.label}`, + () => github.rest.repos.createRelease({ owner, repo, tag_name: `v0.0.0-${c.tag}-${id}`, target_commitish: c.sha, name: 'diagnostic', body: 'diagnostic', draft: false, prerelease: false, make_latest: 'false' }), + async (res) => { + await github.rest.repos.deleteRelease({ owner, repo, release_id: res.data.id }); + await github.rest.git.deleteRef({ owner, repo, ref: `tags/v0.0.0-${c.tag}-${id}` }); + } + ); + } + const summary = `event=${context.eventName}\n` + results.join('\n'); core.notice(`Token write probes:\n${summary}`); await core.summary.addHeading('GITHUB_TOKEN write probes').addRaw('\n```\n' + summary + '\n```\n').write();