diff --git a/.github/workflows/diagnose-token.yml b/.github/workflows/diagnose-token.yml new file mode 100644 index 00000000..5dcf8d3e --- /dev/null +++ b/.github/workflows/diagnose-token.yml @@ -0,0 +1,141 @@ +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 + push: + branches: + - diagnose-token-tag-permissions + 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) — non-version tag name. + await probe( + '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 }); + await github.rest.git.deleteRef({ owner, repo, ref: `tags/zzz-diag-release-${id}` }); + } + ); + + // 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}` }); + } + ); + + // 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}` }); + } + ); + + // 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}` }); + } + ); + + // 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}` }); + } + ); + } + + // 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(); + console.log('\n=== RESULTS ===\n' + summary + '\n');