Skip to content

gh auth login creates duplicates entries in Keychain Access #12953

@hectorpal

Description

@hectorpal

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

  1. Run:
gh auth logout
  1. 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.
  1. 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
  1. 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

  1. 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 USERNAME

Additional context

This may be related to existing issues:

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingneeds-triageneeds to be reviewed

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions