-
Notifications
You must be signed in to change notification settings - Fork 8.1k
Description
Describe the bug
gh auth login creates duplicate macOS Keychain entries for the same service (gh:github.com), where one entry has a valid account and another has an empty account field.
Both entries contain the same token.
This results in:
- multiple Keychain entries for the same credential
- one malformed entry (
account = "") - potential ambiguity during credential lookup
This appears related to how gh interacts with the macOS Keychain backend.
Affected version
$ gh version
gh version 2.88.1 (2026-03-12)
https://github.com/cli/cli/releases/tag/v2.88.1
MacOS: 15.7.4 (24G517)
Steps to reproduce the behavior
- Run:
gh auth logout- No credentials in Keychain Access:
$ security delete-generic-password -s gh:github.com
security: SecKeychainSearchCopyNext: The specified item could not be found in the keychain.- Login
$ gh auth login
? Where do you use GitHub? GitHub.com
? What is your preferred protocol for Git operations on this host? SSH
? Upload your SSH public key to your GitHub account? Skip
? How would you like to authenticate GitHub CLI? Login with a web browser
! First copy your one-time code: XXXX-XXX
Press Enter to open https://github.com/login/device in your browser...
✓ Authentication complete.
- gh config set -h github.com git_protocol ssh
✓ Configured git protocol
✓ Logged in as USERNAME
! You were already logged in to this account
- Inspect Keychain:
$ security find-generic-password -s gh:github.com
keychain: "/Users/USERNAME/Library/Keychains/login.keychain-db"
version: 512
class: "genp"
attributes:
0x00000007 <blob>="gh:github.com"
0x00000008 <blob>=<NULL>
"acct"<blob>="USERNAME"
"cdat"<timedate>=0x... "20260317172227Z\000"
"crtr"<uint32>=<NULL>
"cusi"<sint32>=<NULL>
"desc"<blob>=<NULL>
"gena"<blob>=<NULL>
"icmt"<blob>=<NULL>
"invi"<sint32>=<NULL>
"mdat"<timedate>=0x... "20260317172227Z\000"
"nega"<sint32>=<NULL>
"prot"<blob>=<NULL>
"scrp"<sint32>=<NULL>
"svce"<blob>="gh:github.com"
"type"<uint32>=<NULL>I’m not sure how that handles duplicates
- Open Keychain Access and search for
github
See expected vs actual behaviour
Expected vs actual behavior
Expected:
- A single Keychain entry:
Name: gh:github.com
Kind: application password
Account: USERNAME
Where: gh:github.com
Actual:
- Two entries are created:
Name: gh:github.com
Kind: application password
Account: USERNAME
Where: gh:github.com
Name: gh:github.com
Kind: application password
Account: <empty>
Where: gh:github.com
- Both entries contain the same application password
Logs
$ export GH_DEBUG=api
$ gh auth login
? Where do you use GitHub? GitHub.com
? What is your preferred protocol for Git operations on this host? SSH
? Upload your SSH public key to your GitHub account? Skip
? How would you like to authenticate GitHub CLI? Login with a web browser
* Request at 2026-03-17 13:35:04.114693 -0400 EDT m=+5.288716141
* Request to https://github.com/login/device/code
> POST /login/device/code HTTP/1.1
> Host: github.com
> Content-Length: 57
> Content-Type: application/x-www-form-urlencoded
> User-Agent: GitHub CLI 2.88.1
> X-Github-Api-Version: 2022-11-28
client_id=178c6fc778ccc68e1d6a&scope=repo+read%3Aorg+gist
< HTTP/2.0 200 OK
< Cache-Control: max-age=0, private, must-revalidate
< Content-Length: 157
< Content-Security-Policy: default-src 'none'; base-uri 'self'; child-src github.githubassets.com github.com/assets-cdn/worker/ github.com/assets/ gist.github.com/assets-cdn/worker/; connect-src 'self' uploads.github.com www.githubstatus.com collector.github.com raw.githubusercontent.com api.github.com github-cloud.s3.amazonaws.com github-production-repository-file-5c1aeb.s3.amazonaws.com github-production-upload-manifest-file-7fdce7.s3.amazonaws.com github-production-user-asset-6210df.s3.amazonaws.com *.rel.tunnels.api.visualstudio.com wss://*.rel.tunnels.api.visualstudio.com github.githubassets.com objects-origin.githubusercontent.com copilot-proxy.githubusercontent.com proxy.individual.githubcopilot.com proxy.business.githubcopilot.com proxy.enterprise.githubcopilot.com *.actions.githubusercontent.com wss://*.actions.githubusercontent.com productionresultssa0.blob.core.windows.net productionresultssa1.blob.core.windows.net productionresultssa2.blob.core.windows.net productionresultssa3.blob.core.windows.net productionresultssa4.blob.core.windows.net productionresultssa5.blob.core.windows.net productionresultssa6.blob.core.windows.net productionresultssa7.blob.core.windows.net productionresultssa8.blob.core.windows.net productionresultssa9.blob.core.windows.net productionresultssa10.blob.core.windows.net productionresultssa11.blob.core.windows.net productionresultssa12.blob.core.windows.net productionresultssa13.blob.core.windows.net productionresultssa14.blob.core.windows.net productionresultssa15.blob.core.windows.net productionresultssa16.blob.core.windows.net productionresultssa17.blob.core.windows.net productionresultssa18.blob.core.windows.net productionresultssa19.blob.core.windows.net github-production-repository-image-32fea6.s3.amazonaws.com github-production-release-asset-2e65be.s3.amazonaws.com insights.github.com wss://alive.github.com wss://alive-staging.github.com api.githubcopilot.com api.individual.githubcopilot.com api.business.githubcopilot.com api.enterprise.githubcopilot.com; font-src github.githubassets.com; form-action 'self' github.com gist.github.com copilot-workspace.githubnext.com objects-origin.githubusercontent.com; frame-ancestors 'none'; frame-src viewscreen.githubusercontent.com notebooks.githubusercontent.com; img-src 'self' data: blob: github.githubassets.com media.githubusercontent.com camo.githubusercontent.com identicons.github.com avatars.githubusercontent.com private-avatars.githubusercontent.com github-cloud.s3.amazonaws.com objects.githubusercontent.com release-assets.githubusercontent.com secured-user-images.githubusercontent.com user-images.githubusercontent.com private-user-images.githubusercontent.com opengraph.githubassets.com marketplace-screenshots.githubusercontent.com copilotprodattachments.blob.core.windows.net/github-production-copilot-attachments/ github-production-user-asset-6210df.s3.amazonaws.com customer-stories-feed.github.com spotlights-feed.github.com objects-origin.githubusercontent.com *.githubusercontent.com; manifest-src 'self'; media-src github.com user-images.githubusercontent.com secured-user-images.githubusercontent.com private-user-images.githubusercontent.com github-production-user-asset-6210df.s3.amazonaws.com gist.github.com github.githubassets.com; script-src github.githubassets.com; style-src 'unsafe-inline' github.githubassets.com; upgrade-insecure-requests; worker-src github.githubassets.com github.com/assets-cdn/worker/ github.com/assets/ gist.github.com/assets-cdn/worker/
< Content-Type: application/x-www-form-urlencoded; charset=utf-8
< Date: Tue, 17 Mar 2026 17:35:04 GMT
< Etag: W/"e776940ed98a3444464c9f3f88737095"
< Referrer-Policy: origin-when-cross-origin, strict-origin-when-cross-origin
< Server: github.com
< Set-Cookie: _octo=...; domain=github.com; path=/; expires=Wed, 17 Mar 2027 17:35:04 GMT; secure; SameSite=Lax
< Set-Cookie: last_write_ms=████████████████████; path=/; secure; HttpOnly; SameSite=Lax
< Strict-Transport-Security: max-age=31536000; includeSubdomains; preload
< Vary: X-PJAX, X-PJAX-Container, Turbo-Visit, Turbo-Frame, X-Requested-With,Accept-Encoding, Accept, X-Requested-With
< X-Content-Type-Options: nosniff
< X-Frame-Options: deny
< X-Github-Request-Id: D52E:216594:78BFEF8:A86B130:69B990C8
< X-Xss-Protection: 0
device_code=6bdbe4816c5f0b5994bfef8411720286d52b2412&expires_in=899&interval=5&user_code=25F9-FB54&verification_uri=https%3A%2F%2Fgithub.com%2Flogin%2Fdevice
* Request took 193.467883ms
! First copy your one-time code: 25F9-FB54
Press Enter to open https://github.com/login/device in your browser...
* Request at 2026-03-17 13:35:20.733342 -0400 EDT m=+21.907307912
* Request to https://github.com/login/oauth/access_token
> POST /login/oauth/access_token HTTP/1.1
> Host: github.com
> Content-Length: 149
> Content-Type: application/x-www-form-urlencoded
> User-Agent: GitHub CLI 2.88.1
> X-Github-Api-Version: 2022-11-28
client_id=178c6fc778ccc68e1d6a&device_code=6bdbe4816c5f0b5994bfef8411720286d52b2412&grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code
< HTTP/2.0 200 OK
< Cache-Control: max-age=0, private, must-revalidate
< Content-Length: 208
< Content-Security-Policy: default-src 'none'; base-uri 'self'; child-src github.githubassets.com github.com/assets-cdn/worker/ github.com/assets/ gist.github.com/assets-cdn/worker/; connect-src 'self' uploads.github.com www.githubstatus.com collector.github.com raw.githubusercontent.com api.github.com github-cloud.s3.amazonaws.com github-production-repository-file-5c1aeb.s3.amazonaws.com github-production-upload-manifest-file-7fdce7.s3.amazonaws.com github-production-user-asset-6210df.s3.amazonaws.com *.rel.tunnels.api.visualstudio.com wss://*.rel.tunnels.api.visualstudio.com github.githubassets.com objects-origin.githubusercontent.com copilot-proxy.githubusercontent.com proxy.individual.githubcopilot.com proxy.business.githubcopilot.com proxy.enterprise.githubcopilot.com *.actions.githubusercontent.com wss://*.actions.githubusercontent.com productionresultssa0.blob.core.windows.net productionresultssa1.blob.core.windows.net productionresultssa2.blob.core.windows.net productionresultssa3.blob.core.windows.net productionresultssa4.blob.core.windows.net productionresultssa5.blob.core.windows.net productionresultssa6.blob.core.windows.net productionresultssa7.blob.core.windows.net productionresultssa8.blob.core.windows.net productionresultssa9.blob.core.windows.net productionresultssa10.blob.core.windows.net productionresultssa11.blob.core.windows.net productionresultssa12.blob.core.windows.net productionresultssa13.blob.core.windows.net productionresultssa14.blob.core.windows.net productionresultssa15.blob.core.windows.net productionresultssa16.blob.core.windows.net productionresultssa17.blob.core.windows.net productionresultssa18.blob.core.windows.net productionresultssa19.blob.core.windows.net github-production-repository-image-32fea6.s3.amazonaws.com github-production-release-asset-2e65be.s3.amazonaws.com insights.github.com wss://alive.github.com wss://alive-staging.github.com api.githubcopilot.com api.individual.githubcopilot.com api.business.githubcopilot.com api.enterprise.githubcopilot.com; font-src github.githubassets.com; form-action 'self' github.com gist.github.com copilot-workspace.githubnext.com objects-origin.githubusercontent.com; frame-ancestors 'none'; frame-src viewscreen.githubusercontent.com notebooks.githubusercontent.com; img-src 'self' data: blob: github.githubassets.com media.githubusercontent.com camo.githubusercontent.com identicons.github.com avatars.githubusercontent.com private-avatars.githubusercontent.com github-cloud.s3.amazonaws.com objects.githubusercontent.com release-assets.githubusercontent.com secured-user-images.githubusercontent.com user-images.githubusercontent.com private-user-images.githubusercontent.com opengraph.githubassets.com marketplace-screenshots.githubusercontent.com copilotprodattachments.blob.core.windows.net/github-production-copilot-attachments/ github-production-user-asset-6210df.s3.amazonaws.com customer-stories-feed.github.com spotlights-feed.github.com objects-origin.githubusercontent.com *.githubusercontent.com; manifest-src 'self'; media-src github.com user-images.githubusercontent.com secured-user-images.githubusercontent.com private-user-images.githubusercontent.com github-production-user-asset-6210df.s3.amazonaws.com gist.github.com github.githubassets.com; script-src github.githubassets.com; style-src 'unsafe-inline' github.githubassets.com; upgrade-insecure-requests; worker-src github.githubassets.com github.com/assets-cdn/worker/ github.com/assets/ gist.github.com/assets-cdn/worker/
< Content-Type: application/x-www-form-urlencoded; charset=utf-8
< Date: Tue, 17 Mar 2026 17:35:20 GMT
< Etag: W/"ddc6411a4610fbc6848540d812a15c5a"
< Referrer-Policy: origin-when-cross-origin, strict-origin-when-cross-origin
< Server: github.com
< Set-Cookie: _octo=...; domain=github.com; path=/; expires=Wed, 17 Mar 2027 17:35:20 GMT; secure; SameSite=Lax
< Set-Cookie: last_write_ms=████████████████████; path=/; secure; HttpOnly; SameSite=Lax
< Strict-Transport-Security: max-age=31536000; includeSubdomains; preload
< Vary: X-PJAX, X-PJAX-Container, Turbo-Visit, Turbo-Frame, X-Requested-With,Accept-Encoding, Accept, X-Requested-With
< X-Content-Type-Options: nosniff
< X-Frame-Options: deny
< X-Github-Request-Id: D52E:216594:78C86AF:A876E15:69B990C8
< X-Xss-Protection: 0
error=authorization_pending&error_description=The+authorization+request+is+still+pending.&error_uri=https%3A%2F%2Fdocs.github.com%2Fdevelopers%2Fapps%2Fauthorizing-oauth-apps%23error-codes-for-the-device-flow
* Request took 69.520484ms
* Request at 2026-03-17 13:35:26.804616 -0400 EDT m=+27.978561534
* Request to https://github.com/login/oauth/access_token
> POST /login/oauth/access_token HTTP/1.1
> Host: github.com
> Content-Length: 149
> Content-Type: application/x-www-form-urlencoded
> User-Agent: GitHub CLI 2.88.1
> X-Github-Api-Version: 2022-11-28
client_id=178c6fc778ccc68e1d6a&device_code=6bdbe4816c5f0b5994bfef8411720286d52b2412&grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code
< HTTP/2.0 200 OK
< Cache-Control: max-age=0, private, must-revalidate
< Content-Length: 102
< Content-Security-Policy: default-src 'none'; base-uri 'self'; child-src github.githubassets.com github.com/assets-cdn/worker/ github.com/assets/ gist.github.com/assets-cdn/worker/; connect-src 'self' uploads.github.com www.githubstatus.com collector.github.com raw.githubusercontent.com api.github.com github-cloud.s3.amazonaws.com github-production-repository-file-5c1aeb.s3.amazonaws.com github-production-upload-manifest-file-7fdce7.s3.amazonaws.com github-production-user-asset-6210df.s3.amazonaws.com *.rel.tunnels.api.visualstudio.com wss://*.rel.tunnels.api.visualstudio.com github.githubassets.com objects-origin.githubusercontent.com copilot-proxy.githubusercontent.com proxy.individual.githubcopilot.com proxy.business.githubcopilot.com proxy.enterprise.githubcopilot.com *.actions.githubusercontent.com wss://*.actions.githubusercontent.com productionresultssa0.blob.core.windows.net productionresultssa1.blob.core.windows.net productionresultssa2.blob.core.windows.net productionresultssa3.blob.core.windows.net productionresultssa4.blob.core.windows.net productionresultssa5.blob.core.windows.net productionresultssa6.blob.core.windows.net productionresultssa7.blob.core.windows.net productionresultssa8.blob.core.windows.net productionresultssa9.blob.core.windows.net productionresultssa10.blob.core.windows.net productionresultssa11.blob.core.windows.net productionresultssa12.blob.core.windows.net productionresultssa13.blob.core.windows.net productionresultssa14.blob.core.windows.net productionresultssa15.blob.core.windows.net productionresultssa16.blob.core.windows.net productionresultssa17.blob.core.windows.net productionresultssa18.blob.core.windows.net productionresultssa19.blob.core.windows.net github-production-repository-image-32fea6.s3.amazonaws.com github-production-release-asset-2e65be.s3.amazonaws.com insights.github.com wss://alive.github.com wss://alive-staging.github.com api.githubcopilot.com api.individual.githubcopilot.com api.business.githubcopilot.com api.enterprise.githubcopilot.com; font-src github.githubassets.com; form-action 'self' github.com gist.github.com copilot-workspace.githubnext.com objects-origin.githubusercontent.com; frame-ancestors 'none'; frame-src viewscreen.githubusercontent.com notebooks.githubusercontent.com; img-src 'self' data: blob: github.githubassets.com media.githubusercontent.com camo.githubusercontent.com identicons.github.com avatars.githubusercontent.com private-avatars.githubusercontent.com github-cloud.s3.amazonaws.com objects.githubusercontent.com release-assets.githubusercontent.com secured-user-images.githubusercontent.com user-images.githubusercontent.com private-user-images.githubusercontent.com opengraph.githubassets.com marketplace-screenshots.githubusercontent.com copilotprodattachments.blob.core.windows.net/github-production-copilot-attachments/ github-production-user-asset-6210df.s3.amazonaws.com customer-stories-feed.github.com spotlights-feed.github.com objects-origin.githubusercontent.com *.githubusercontent.com; manifest-src 'self'; media-src github.com user-images.githubusercontent.com secured-user-images.githubusercontent.com private-user-images.githubusercontent.com github-production-user-asset-6210df.s3.amazonaws.com gist.github.com github.githubassets.com; script-src github.githubassets.com; style-src 'unsafe-inline' github.githubassets.com; upgrade-insecure-requests; worker-src github.githubassets.com github.com/assets-cdn/worker/ github.com/assets/ gist.github.com/assets-cdn/worker/
< Content-Type: application/x-www-form-urlencoded; charset=utf-8
< Date: Tue, 17 Mar 2026 17:35:27 GMT
< Etag: W/"82d516605d82892c0efd2219ab0ae09d"
< Referrer-Policy: origin-when-cross-origin, strict-origin-when-cross-origin
< Server: github.com
< Set-Cookie: _octo=....; domain=github.com; path=/; expires=Wed, 17 Mar 2027 17:35:26 GMT; secure; SameSite=Lax
< Set-Cookie: last_write_ms=████████████████████; path=/; secure; HttpOnly; SameSite=Lax
< Strict-Transport-Security: max-age=31536000; includeSubdomains; preload
< Vary: X-PJAX, X-PJAX-Container, Turbo-Visit, Turbo-Frame, X-Requested-With,Accept-Encoding, Accept, X-Requested-With
< X-Content-Type-Options: nosniff
< X-Frame-Options: deny
< X-Github-Request-Id: D52E:216594:78CB600:...:69B990D8
< X-Xss-Protection: 0
access_token=<REDACTED>&scope=gist%2Cread%3Aorg%2Crepo&token_type=bearer
* Request took 114.915907ms
* Request at 2026-03-17 13:35:26.921448 -0400 EDT m=+28.095393037
* Request to https://api.github.com/graphql
> POST /graphql HTTP/1.1
> Host: api.github.com
> Accept: application/vnd.github.merge-info-preview+json, application/vnd.github.nebula-preview
> Authorization: token ....
> Content-Length: 45
> Content-Type: application/json
> Graphql-Features: merge_queue
> Time-Zone: America/Toronto
> User-Agent: GitHub CLI
> X-Github-Api-Version: 2022-11-28
{
"query": "query UserCurrent{viewer{login}}"
}
< HTTP/2.0 200 OK
< 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-Security-Policy: default-src 'none'
< Content-Type: application/json; charset=utf-8
< Date: Tue, 17 Mar 2026 17:35:27 GMT
< Referrer-Policy: origin-when-cross-origin, strict-origin-when-cross-origin
< Server: github.com
< Strict-Transport-Security: max-age=31536000; includeSubdomains; preload
< Vary: Accept-Encoding, Accept, X-Requested-With
< X-Accepted-Oauth-Scopes: repo
< X-Content-Type-Options: nosniff
< X-Frame-Options: deny
< X-Github-Media-Type: github.v4; param=merge-info-preview.nebula-preview; format=json
< X-Github-Request-Id: D531:6DD42:4879A6C:132B88EE:69B990DF
< X-Oauth-Client-Id: 178c6fc778ccc68e1d6a
< X-Oauth-Scopes: gist, read:org, repo
< X-Ratelimit-Limit: 5000
< X-Ratelimit-Remaining: 4997
< X-Ratelimit-Reset: 1773771747
< X-Ratelimit-Resource: graphql
< X-Ratelimit-Used: 3
< X-Xss-Protection: 0
{
"data": {
"viewer": {
"login": "USERNAME"
}
}
}
* Request took 251.100998ms
✓ Authentication complete.
- gh config set -h github.com git_protocol ssh
✓ Configured git protocol
✓ Logged in as USERNAMEAdditional context
This may be related to existing issues:
-
Keychain lookup ignores account field
Keychain lookup ignores account field, breaks multi-account setups #12885
→ token retrieval may be ambiguous when multiple entries exist -
Keychain access control via
securityCLI
Switch to native bindings for macOS Keychain #7123
→ highlights how credentials are accessed
I wonder if this could lead to
- non-deterministic credential selection
- subtle authentication failures if entries diverge
Notes
- The duplicate entries appear immediately after
gh auth login - The entry with empty account persists unless manually deleted
- Deleting the empty entry does not prevent it from being recreated on subsequent login