git-ecosystem/git-credential-manager · error · InteropException
-1
-1
Error message
Unknown keychain search result type CFTypeID: {typeId}. What it means
Thrown by MacOSKeychain.GetAccounts after SecItemCopyMatching returns success but the result object is not a CFArray, which is the only type the account-listing path can process. The message embeds the unexpected CoreFoundation type ID. Because the format string uses interpolation of the raw typeId the message may literally read 'CFTypeID: {typeId}' — the code indicates an unexpected/unknown result shape from the Security framework.
Solutions
- Update Git Credential Manager to a version that handles single-dictionary (CFDictionary) results in GetAccounts, not just CFArray
- Check the macOS version; on macOS versions with changed SecItemCopyMatching semantics, wrap the call and normalize a returned CFDictionary into a single-element list
- Reproduce with a minimal SecItemCopyMatching query to log CFGetTypeID(result) and confirm what the framework returns on your OS
- Report the CFTypeID value to the library maintainers with macOS version so the type can be mapped
Example fix
// before
if (typeId == CFArrayGetTypeID()) { ... }
throw new InteropException($"Unknown keychain search result type CFTypeID: {typeId}.", -1);
// after
if (typeId == CFArrayGetTypeID()) { ... }
else if (typeId == CFDictionaryGetTypeID())
{
// single result returned as a dictionary, not wrapped in an array
string account = GetStringAttribute(resultPtr, kSecAttrAccount);
return string.IsNullOrEmpty(account) ? new List<string>() : new List<string> { account };
}
throw new InteropException($"Unknown keychain search result type CFTypeID: {typeId}.", -1); Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-check the macOS version against known-supported range before using keychain accounts
using System;
static bool KeychainAccountsSupported()
{
// SecItemCopyMatching result-shape changed on newer macOS; require tested version
var v = Environment.OSVersion.Version;
return v.Major >= 10 && v.Minor >= 12;
} Try / catch
try
{
IList<string> accounts = keychain.GetAccounts(service);
}
catch (InteropException ex) when (ex.Message.Contains("Unknown keychain search result type CFTypeID"))
{
// Unexpected CFType returned; treat as empty account list or upgrade GCM
accounts = Array.Empty<string>();
}
Prevention
- Keep Git Credential Manager updated for current macOS SecItemCopyMatching semantics
- Test keychain flows after every macOS upgrade (result shapes can change)
- Avoid third-party tools that create anomalous keychain items for your service name
- Log the CFTypeID in catch blocks to report actionable bug reports
When it happens
Trigger: SecItemCopyMatching with kSecMatchLimitAll + kSecReturnAttributes returns OK but hands back a non-CFArray CFType (e.g. a CFDictionary on some macOS versions that return a single item instead of an array).
Common situations: macOS version differences in Security.framework result shapes, newer macOS deprecating legacy keychain APIs and changing SecItemCopyMatching return semantics, or corrupted/unusual keychain items causing a single-result return where an array is expected.
Related errors
- ErrorSecNoSuchKeychain
- ErrorSecInvalidKeychain
- ErrorSecAuthFailed
- ErrorSecDuplicateItem
- ErrorSecItemNotFound
AI-assisted analysis of git-ecosystem/git-credential-manager@e8ce762cd0 (2026-09-11).
Data as JSON: /api/errors/084433a1c95cbbf3.
Report an issue: GitHub.
Appendix: source
Thrown at src/Core/Interop/MacOS/MacOSKeychain.cs:82
{
case OK:
int typeId = CFGetTypeID(resultPtr);
Debug.Assert(typeId == CFArrayGetTypeID(), "Returned unknown item from account query");
if (typeId == CFArrayGetTypeID())
{
int len = (int)CFArrayGetCount(resultPtr);
var accounts = new HashSet<string>(len);
for (int i = 0; i < len; i++)
{
IntPtr dict = CFArrayGetValueAtIndex(resultPtr, i);
string account = GetStringAttribute(dict, kSecAttrAccount);
accounts.Add(account);
}
return accounts.ToList();
}
throw new InteropException($"Unknown keychain search result type CFTypeID: {typeId}.", -1);
case ErrorSecItemNotFound:
return Array.Empty<string>();
default:
ThrowIfError(searchResult);
return null;
}
}
finally
{
if (query != IntPtr.Zero) CFRelease(query);
if (servicePtr != IntPtr.Zero) CFRelease(servicePtr);
if (accountPtr != IntPtr.Zero) CFRelease(accountPtr);
if (resultPtr != IntPtr.Zero) CFRelease(resultPtr);
}
}
View on GitHub (pinned to e8ce762cd0)