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 working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions