git
git copied to clipboard
semantic-release(/git) not using GH_TOKEN
We're trying to push a commit to the protected master-test branch with Github Actions after a pull request is merged (on push to master-test). I generated a personal token with appropriate access to our repo (having an admin role) but noticed that the token is probably not being used (in Personal access token settings on GitHub).

I went through the documentation, followed each step, but still can't make it work. I've also tried to use GIT_CREDENTIALS env variable, without success.
We're using:
- "semantic-release": "^17.0.4"
- "@semantic-release/git": "^9.0.0"
release.config.js
/* eslint-disable @typescript-eslint/no-var-requires */
module.exports = {
branches: [
'master-test'
],
plugins: [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/npm",
["@semantic-release/github", {
"assets": ["dist/**"]
}],
["@semantic-release/git", {
"assets": ["dist/**/*.{js,css}", "docs", "package.json"],
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}]
],
}
action setup
- name: Semantic Release
run: yarn semantic-release #runs "semantic-release --debug"
env:
NODE_ENV: production
GH_TOKEN: ${{ secrets.GH_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
NPM_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}
output from the job
shortMessage: 'Command failed with exit code 1: git push --tags https://github.com/xxx/xxx.git HEAD:master-test', command: 'git push --tags https://github.com/xxx/xxx.git HEAD:master-test', exitCode: 1, signal: undefined, signalDescription: undefined, stdout: '', stderr: 'remote: error: GH006: Protected branch update failed for refs/heads/master-test. \n' + "remote: error: You're not authorized to push to this branch. Visit https://help.github.com/articles/about-protected-branches/ for more information. \n" + 'To https://github.com/xxx/xxx.git\n' + ' ! [remote rejected] HEAD -> master-test (protected branch hook declined)\n' + "error: failed to push some refs to 'https://github.com/xxx/xxx.git'", failed: true, timedOut: false, isCanceled: false, killed: false,
What make you think it's related to not using GH_TOKEN?
The error mention the branch is protected, so I guess it's related to a verification configured in the protected branch that make the commit failed.
Have you tried to push a commit to a protected branch with the same settings outside of semantic-release?
@pvdlg We were trying to bypass the protection with a personal access token and couldn't achieve it.
Now we managed to do it by setting a token in actions/checkout. This has fixed the problem for us. However, I'm a little bit concerned about this 'fix' as it seems we give elevated permissions to every step running in the job.
- name: Checkout project
uses: actions/checkout@v2
with:
token: ${{ secrets.GH_TOKEN }}
Also, we no more need the GH_TOKEN: ${{ secrets.GH_TOKEN }} in our semantic-release step to make it work with the above-mentioned fix. The bellow example works without any problem now.
- name: Semantic Release
run: yarn semantic-release
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
we use these plugins
const plugins = [
'@semantic-release/commit-analyzer',
'@semantic-release/release-notes-generator',
'@semantic-release/changelog',
'@semantic-release/npm',
'@semantic-release/git',
]
This makes me think that semantic-release wasn't using our GH_TOKEN before.
Any ideas of what we could oversee?
It seems the error happens based on how the repo was cloned... Not sure why.
If that helps here is how semantic-release interact handle repo authentication:
- Try to push to the repo with the
repository.urlconfigured inpackage.json- If that works use that URL and ignore
GH_TOKEN - if that fails, transform the
repository.urlconfigured inpackage.jsonand insert theGH_TOKENas basic auth
- If that works use that URL and ignore
I don't know what GitHub is doing exactly when verifying permissions for protected branches nor what actions/checkout@v2 does that would change how the protected branches permissions are verified.
It seems the action set the token in .git/config: https://github.com/actions/checkout/blob/f858c22e963bc60bc9d01c3d105c52a45a7deb6e/src/git-source-provider.ts#L264
In semantic-release we don't do that as we don't want to leave a token behind in a file on the CI.
We do git push --tags https://x-access-token:<GH_TOKEN>@github.com/owner/repo.git
I don't know why the branch protection behave differently in those 2 cases. They should be equivalent.
In semantic-release we don't do that as we don't want to leave a token behind in a file on the CI. We do git push --tags https://x-access-token:<GH_TOKEN>@github.com/owner/repo.git
@pvdlg I just noticed that in our logs semantic-release outputs command: 'git push --tags https://github.com/{org}/{repo}.git HEAD:master-test' instead.
Might that be a problem? I can't see x-access-token:<GH_TOKEN> in our logs at all.
//EDIT:
That makes me think, we configuring repository.url, should the format be https://x-access-token:<GH_TOKEN>@github.com/owner/repo.git?
I just noticed that in our logs semantic-release outputs command: 'git push --tags https://github.com/{org}/{repo}.git HEAD:master-test' instead.
That's expected. As I explained earlier:
If that helps here is how semantic-release interact handle repo authentication:
- Try to push to the repo with the
repository.urlconfigured inpackage.json
- If that works use that URL and ignore
GH_TOKEN- if that fails, transform the
repository.urlconfigured inpackage.jsonand insert theGH_TOKENas basic auth
Because actions/checkout@v2 sets the token directly in .git/config semantic-release can push without having to add the token to the URL.
That makes me think, we configuring repository.url, should the format be https://x-access-token:<GH_TOKEN>@github.com/owner/repo.git?
No. As explained earlier semantic-release already adds the token to the URL when necessary.
@pvdlg I see myself having the same problem.
I can 't understand it:
if that fails, transform the repository.url configured in package.json and insert the GH_TOKEN as basic auth
Isn't basic auth this https://x-access-token: < GH_TOKEN>@github.com/owner/repo.git?
Isn't basic auth this https://x-access-token: < GH_TOKEN>@github.com/owner/repo.git?
yes it is. So? I don't understand what you are asking.
@Duchynko provided a workaround, which also works in my repositories with protected branches:
Now we managed to do it by setting a token in
actions/checkout. This has fixed the problem for us. However, I'm a little bit concerned about this 'fix' as it seems we give elevated permissions to every step running in the job.- name: Checkout project uses: actions/checkout@v2 with: token: ${{ secrets.GH_TOKEN }}Also, we no more need the
GH_TOKEN: ${{ secrets.GH_TOKEN }}in our semantic-release step to make it work with the above-mentioned fix. The bellow example works without any problem now.
@pvdlg: any chance to look into this behaviour or at least add a documentation for it?
Setting persist-credentials: false in the checkout step seems to work as well:
- name: Checkout
uses: actions/checkout@v2
with:
persist-credentials: false
- name: Release
run: yarn semantic-release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
According to the actions/checkout@v2 docs:
The auth token is persisted in the local git config. This enables your scripts to run authenticated git commands. The token is removed during post-job cleanup. Set persist-credentials: false to opt-out.
@anpa your answer really saved a lot of time! Huge thanks!
This should be documented in the Github Actions recipes.
Setting persist-credentials: false in the checkout step seems to work as well
I've had the same issue and can confirm that @anpa 's solution works a treat. Additionally that solution doesn't poses a security risk in the steps between checkout and release.
I tried to use @anpa's solution but it did not work quite well for me. Because:
- GitHub does not allow you to replace the
GITHUB_TOKENsecret with another value, so I usedGH_TOKEN: ${{ secrets.GH_TOKEN_SEMANTIC_RELEASE }}instead. semantic-releasethen started to create releases on behalf of me instead of the usualgithub-actionsbot:
when the right would be:

So I ended up with:
-
Create a GitHub PAT with the full repo scope:

-
Add this token as a secret named
GH_TOKEN_SEMANTIC_RELEASE(I prefer a more strict name since I don't want this token to be used anywhere else. -
Use the following workflow:
jobs:
release:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v2
with:
token: ${{ secrets.GH_TOKEN_SEMANTIC_RELEASE }}
- name: Use Node.js 12
uses: actions/setup-node@v1
with:
node-version: 12
- run: npm ci
- run: npm run release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
Summary:
- Use your PAT in the
checkoutstep - Let it persist the credentials by not setting
persist-credentials: false - Do not replace
GITHUB_TOKENforsemantic-release
This way, @semantic-release/git uses the PAT while semantic-release keeps using the default GITHUB_TOKEN from GitHub Actions.
I hope this helps some of you. :-)
@cycjimmy you might want to update your documentation, since, I believe, it's the most "official" place we have this workaround documented so far.
@felipecrs Hope this helps with your setup.
- GitHub does not allow you to replace the
GITHUB_TOKENsecret with another value, so I usedGH_TOKEN: ${{ secrets.GH_TOKEN_SEMANTIC_RELEASE }}instead.
It does though. I'm using the following snippet in my release and the "default" GITHUB_TOKEN won't have the appropriate permissions to make releases so I'm sure it is using the replacement GITHUB_TOKEN. I haven't looked it up in the GitHub actions documentation but I imagine this behaviour, being overriding GITHUB_TOKEN, to be documented somewhere there.
- semantic-release then started to create releases on behalf of me instead of the usual github-actions bot:
Hopefully this helps, this is a snippet from the release steps I'm using in multiple projects. I'm using the environment variables to have the GitHub release and commits as a specific user, @vidavidorra-release in this case. This behaviour is described in semantic-release/git documentation. FYI you can get the ID for an "anonymous" email via the GitHub API, e.g. https://api.github.com/users/vidavidorra-release.
steps:
- name: Checkout
uses: actions/[email protected]
with:
# Make sure the release step uses its own credentials.
persist-credentials: false
- name: Setup node
uses: actions/[email protected]
with:
node-version: 14
- name: Install project
run: npm ci --ignore-scripts
- name: Build
run: npm run build
- name: Release
env:
GITHUB_TOKEN: ${{ secrets.SEMANTIC_RELEASE_GITHUB_TOKEN }}
GIT_AUTHOR_NAME: vidavidorra-release
GIT_AUTHOR_EMAIL: [email protected]
GIT_COMMITTER_NAME: vidavidorra-release
GIT_COMMITTER_EMAIL: [email protected]
NPM_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
run: npx --no-install semantic-release
Edit: I see you wanted to have the release created as the @github-actions user, for which you can use the following environment variables.
GIT_AUTHOR_NAME: github-actions
GIT_AUTHOR_EMAIL: [email protected]
GIT_COMMITTER_NAME: github-actions
GIT_COMMITTER_EMAIL: [email protected]
It does though. I'm using the following snippet in my release and the "default"
GITHUB_TOKENwon't have the appropriate permissions to make releases so I'm sure it is using the replacementGITHUB_TOKEN. I haven't looked it up in the GitHub actions documentation but I imagine this behaviour, being overridingGITHUB_TOKEN, to be documented somewhere there.
Actually, that was not what I meant. I meant this:

Because in @anpa's example, it uses GITHUB_TOKEN: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}.
FYI you can get the ID for an "anonymous" email via the GitHub API, e.g. https://api.github.com/users/vidavidorra-release.
TYVM for the tip, very nice.
However, I don't understand. Your solution is more complex than mine, what benefit it brings?
Actually, that was not what I meant. I meant this:
I misunderstood it first, but got it now. It would make sense that GitHub actions doesn't allow you to make a GITHUB_TOKEN secret as that will mess with their automatically distributed one. Thanks for the explanation on what you ment.
However, I don't understand. Your solution is more complex than mine, what benefit it brings?
The benefit, for me at least, is that semantic release uses the PAT for creating commits with a specific user which it couldn't do with the default GITHUB_TOKEN due to branch protection rules. And I like the commits, and the GitHub release to be from the @vidavidorra-release user so this is the perfect solution for me. It might not be so for every case of course as it depends on your specific desires and setup etc. One other benefit IMHO is that the PAT is only 'exposed' in the one release step, so can't be misused (as persisted credentials) in any step between checkout and the release.
The benefit, for me at least, is that semantic release uses the PAT for creating commits with a specific user which it couldn't do with the default GITHUB_TOKEN due to branch protection rules.
Yes, that's the main problem we're trying to solve here. The solution I proposed works just fine.
and the GitHub release to be from the @vidavidorra-release user so this is the perfect solution for me.
Got it.
One other benefit IMHO is that the PAT is only 'exposed' in the one release step, so can't be misused (as persisted credentials) in any step between checkout and the release.
That's a point. However, as per semantic-release, they recommend having a unique job for the release, which should run after the test job passes, but in a clean environment. So, as long as you follow this, your job will only perform the release. So I think there is no need to worry.
Setting
persist-credentials: falsein the checkout step seems to work as well:- name: Checkout uses: actions/checkout@v2 with: persist-credentials: false - name: Release run: yarn semantic-release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}According to the actions/checkout@v2 docs:
The auth token is persisted in the local git config. This enables your scripts to run authenticated git commands. The token is removed during post-job cleanup. Set persist-credentials: false to opt-out.
this should really be documented somewhere, as github actions is a very common tool to use this with, and also because branch protection rules are common practice, and this is not obivous and you usually only find out about it on these threads.
should I make a PR?
First of all, thanks @anpa @felipecrs @jdbruijn , your solutions worked like a charm 🎉
Summarising here the core issue and some solutions. It will help myself think what to choose, so maybe it helps someone too :)
Issue
As @anpa mentioned, the core issue is that actions/checkout GitHub Action (at time of writing v4) configures git CLI to checkout the repo with the provided GITHUB_TOKEN by default:
The auth token is persisted in the local git config. This enables your scripts to run authenticated git commands. The token is removed during post-job cleanup. Set
persist-credentials: falseto opt-out.
And given @semantic-release/git uses the git CLI for pushing the generated commit without any extra authentication override, the default token will be used. Despite you set GH_TOKEN or GITHUB_TOKEN properly in GitHub Actions env on the step running semantic release.
Workarounds
Checkout without persisting credentials
Then semantic release actually configures the authentication. Enabling you to use the provided GitHub Personal Access Token for all operations when specifying GITHUB_TOKEN / GH_TOKEN for semantic release environment as docs state.
Checkout with GitHub Personal Access Token (PAT)
Then PAT is used for git CLI operations, but default GITHUB_TOKEN is used for other GitHub operations. This way GitHub release, PR comments, issues... are authored by the GitHub Actions bot user. But the tag and the generated commit are authored by the configured git user.
Tips
Use GitHub no reply email as author / committer email
When configuring semantic release, so that commits & tags are related to your account. Instead of semantic release user, which is this plugin's default
Updated docs link about no reply email: https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-personal-account-on-github/managing-email-preferences/setting-your-commit-email-address
Extra findings
Fine grained tokens
Can be used to assign less privileges to the Personal Access Token.
If using the token for all operations (no persist credentials workaround), you'll need to grant contents, issues and pull requests read/write access for the repository you're configuring.
If using the Personal Access Token only for commit / tag operations (checkout with PAT token), read/write access for contents of the repository will be enough.
Verified tags
If not using the default provided GITHUB_TOKEN, commits and tags will appear under the configured user with GIT_AUTHOR|COMMITTER_NAME|EMAIL. But will not appear as "Verified"
As opposed when using default provided GITHUB_TOKEN
More about verification: https://docs.github.com/en/authentication/managing-commit-signature-verification
Not even if using the tip to set the git user to your GitHub account's email.
This is because semantic release uses the git CLI to create those. And if pushing those with another authentication other than the default provided GITHUB_TOKEN, GitHub doesn't know they were generated in a workflow, so can't trust those ref pushes are verified. Unless you configure another way of verifying those like setting GPG yourself.
Hope it helps 😊
I have tried like this
- name: Create a Release
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
run: |
export GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}
npx semantic-release
and like this
- name: Release
run: |
npm i yarn
yarn semantic-release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
And both of the times I receive the same output. I have also tried more things
[8:19:41 PM] [semantic-release] › ✔ Run automated release from branch main on repository https://github.com/cabiamdos/github-actions-test
[8:19:41 PM] [semantic-release] › ✘ The command "git push --dry-run --no-verify https://x-access-token:[secure]@github.com/cabiamdos/github-actions-test HEAD:main" failed with the error message remote: Permission to cabiamdos/github-actions-test.git denied to github-actions[bot].
fatal: unable to access 'https://github.com/cabiamdos/github-actions-test/': The requested URL returned error: 403.
[8:19:41 PM] [semantic-release] › ℹ Start step "fail" of plugin "@semantic-release/github"
[8:19:41 PM] [semantic-release] [@semantic-release/github] › ℹ Verify GitHub authentication (https://api.github.com/)
[8:19:42 PM] [semantic-release] [@semantic-release/github] › ℹ Found existing semantic-release issue #27.
[8:19:42 PM] [semantic-release] › ✘ Failed step "fail" of plugin "@semantic-release/github"
[8:19:42 PM] [semantic-release] › ✘ An error occurred while running semantic-release: RequestError [HttpError]: Resource not accessible by integration - https://docs.github.com/rest/issues/comments#create-an-issue-comment
at file:///home/runner/work/github-actions-test/github-actions-test/react-app/node_modules/@octokit/request/dist-bundle/index.js:105:21
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async requestWithGraphqlErrorHandling (file:///home/runner/work/github-actions-test/github-actions-test/react-app/node_modules/@octokit/plugin-retry/dist-bundle/index.js:36:20)
at async Job.doExecute (/home/runner/work/github-actions-test/github-actions-test/react-app/node_modules/bottleneck/light.js:405:18) {
status: 403,
request: {
method: 'POST',
url: 'https://api.github.com/repos/cabiamdos/github-actions-test/issues/27/comments',
headers: {
accept: 'application/vnd.github.v3+json',
'user-agent': '@semantic-release/github v10.0.2 octokit-core.js/6.0.1 Node.js/20.8.1 (linux; x64)',
authorization: 'token [REDACTED]',
'content-type': 'application/json; charset=utf-8'
},
body: '{"body":"## :rotating_light: The automated release from the `main` branch failed. :rotating_light:\\n\\nI recommend you give this issue a high priority, so other packages depending on you can benefit from your bug fixes and new features again.\\n\\nYou can find below the list of errors reported by **semantic-release**. Each one of them has to be resolved in order to automatically publish your package. I’m sure you can fix this 💪.\\n\\nErrors are usually caused by a misconfiguration or an authentication problem. With each error reported below you will find explanation and guidance to help you to resolve it.\\n\\nOnce all the errors are resolved, **semantic-release** will release your package the next time you push a commit to the `main` branch. You can also manually restart the failed CI job that runs **semantic-release**.\\n\\nIf you are not sure how to resolve this, here are some links that can help you:\\n- [Usage documentation](https://github.com/semantic-release/semantic-release/blob/caribou/d.
request: { agent: undefined, hook: [Function: bound bound register] }
},
response: {
url: 'https://api.github.com/repos/cabiamdos/github-actions-test/issues/27/comments',
status: 403,
headers: {
'access-control-allow-origin': '*',
'access-control-expose-headers': 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset',
'content-encoding': 'gzip',
'content-security-policy': "default-src 'none'",
'content-type': 'application/json; charset=utf-8',
date: 'Sat, 30 Mar 2024 20:19:42 GMT',
'referrer-policy': 'origin-when-cross-origin, strict-origin-when-cross-origin',
server: 'GitHub.com',
'strict-transport-security': 'max-age=31536000; includeSubdomains; preload',
'transfer-encoding': 'chunked',
vary: 'Accept-Encoding, Accept, X-Requested-With',
'x-accepted-github-permissions': 'issues=write; pull_requests=write',
'x-content-type-options': 'nosniff',
'x-frame-options': 'deny',
'x-github-api-version-selected': '2022-11-28',
'x-github-media-type': 'github.v3; format=json',
'x-github-request-id': '7C40:3158:BC734:11D95C:660873DE',
'x-ratelimit-limit': '5000',
'x-ratelimit-remaining': '4998',
'x-ratelimit-reset': '1711833582',
'x-ratelimit-resource': 'core',
'x-ratelimit-used': '2',
'x-xss-protection': '0'
},
data: {
message: 'Resource not accessible by integration',
documentation_url: 'https://docs.github.com/rest/issues/comments#create-an-issue-comment'
}
},
pluginName: '@semantic-release/github'
}
[8:19:42 PM] [semantic-release] › ✘ EGITNOPERMISSION Cannot push to the Git repository.
semantic-release cannot push the version tag to the branch main on the remote Git repository with URL https://x-access-token:[secure]@github.com/cabiamdos/github-actions-test.
This can be caused by:
* a misconfiguration of the repositoryUrl (https://github.com/semantic-release/semantic-release/blob/master/docs/usage/configuration.md#repositoryurl) option
* the repository being unavailable
* or missing push permission for the user configured via the Git credentials on your CI environment (https://github.com/semantic-release/semantic-release/blob/master/docs/usage/ci-configuration.md#authentication)
SemanticReleaseError: Cannot push to the Git repository.
at default (file:///home/runner/work/github-actions-test/github-actions-test/react-app/node_modules/semantic-release/lib/get-error.js:6:10)
at run (file:///home/runner/work/github-actions-test/github-actions-test/react-app/node_modules/semantic-release/index.js:101:11)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async Module.default (file:///home/runner/work/github-actions-test/github-actions-test/react-app/node_modules/semantic-release/index.js:278:22)
at async default (file:///home/runner/work/github-actions-test/github-actions-test/react-app/node_modules/semantic-release/cli.js:55:5) {
code: 'EGITNOPERMISSION',
details: '**semantic-release** cannot push the version tag to the branch `main` on the remote Git repository with URL
'\n' +
'This can be caused by:\n' +
' - a misconfiguration of the [repositoryUrl](https://github.com/semantic-release/semantic-release/blob/master/docs/usage/configuration.md#repositoryurl) option\n' +
' - the repository being unavailable\n' +
' - or missing push permission for the user configured via the [Git credentials on your CI environment](https://github.com/semantic-release/semantic-release/blob/master/docs/usage/ci-configuration.md#authentication)',
semanticRelease: true
}error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
Error: Process completed with exit code 1.