git-ecosystem/git-credential-manager · error · InteropException
ErrorSecDuplicateItem
ErrorSecDuplicateItem
Error message
The item already exists.
What it means
InteropException thrown by SecurityFramework.ThrowIfError when the macOS Security framework returns errSecDuplicateItem (-25299). It means an add/create operation (e.g. SecKeychainAddGenericPassword or SecKeychainItemCreateFromContent) found an item with the same primary attributes (service+account) already in the keychain. The library refuses to silently overwrite the existing secret.
Solutions
- Check existence first with SecKeychainFindGenericPassword; if found, update the item's data (SecKeychainItemModifyAttributesAndData) instead of adding.
- Delete the existing item (SecKeychainItemDelete) before adding, only if overwriting is intentional.
- Wrap the add call in a catch of InteropException with error -25299 and treat it as 'already stored'.
- Use a unique service or account string when you genuinely need multiple distinct items.
Example fix
// before: unconditional add throws on second run
err = SecKeychainAddGenericPassword(null, serviceLength, service, accountLength, account, (uint)password.Length, password, out itemRef);
SecurityFramework.ThrowIfError(err);
// after: look up first, modify if present, add only if missing
err = SecKeychainFindGenericPassword(null, serviceLength, service, accountLength, account, out _, out IntPtr data, out itemRef);
if (err == ErrorSecItemNotFound)
{
err = SecKeychainAddGenericPassword(null, serviceLength, service, accountLength, account, (uint)password.Length, password, out itemRef);
}
else
{
err = SecKeychainItemModifyAttributesAndData(itemRef, IntPtr.Zero, (uint)password.Length, password);
}
SecurityFramework.ThrowIfError(err); Defensive patterns
Strategy: try-catch
Validate before calling
// Check for an existing item before adding int err = SecKeychainFindGenericPassword(null, service.Length, service, account.Length, account, out _, out IntPtr _, out IntPtr _); bool itemExists = err != ErrorSecItemNotFound && err == OK; // OK => exists; -25299 otherwise add throws
Type guard
static bool IsSecDuplicateItem(InteropException ex) => ex.ErrorCode == -25299; // errSecDuplicateItem
Try / catch
try
{
AddCredentialToKeychain(service, account, secret);
}
catch (InteropException ex) when (ex.ErrorCode == -25299)
{
// Item with same service/account already present.
UpdateExistingCredential(service, account, secret); // SecKeychainItemModifyAttributesAndData
} Prevention
- Always find-before-add: call SecKeychainFindGenericPassword and update instead of adding when the item exists.
- Make credential-storage routines idempotent so re-running setup scripts doesn't duplicate items.
- Use a stable, unique (service, account) pair; avoid generating names with timestamps unless duplicates are intended.
- In tests, use a dedicated throwaway keychain and clear it between runs.
When it happens
Trigger: Calling SecKeychainAddGenericPassword or SecKeychainAddInternetPassword for a (service, account) pair that already has an entry in the target keychain; re-running an initialization routine that stores a credential unconditionally without checking for an existing item.
Common situations: Idempotency bugs in setup/first-run code that stores a token on every launch; running the same provisioning script twice; migrating items between keychains where the destination already holds the entry; a test suite storing the same fixture credential repeatedly.
Understand the failure class
Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.
Related errors
- ErrorSecNoSuchKeychain
- ErrorSecInvalidKeychain
- ErrorSecAuthFailed
- ErrorSecItemNotFound
- ErrorSecInteractionNotAllowed
AI-assisted analysis of git-ecosystem/git-credential-manager@e8ce762cd0 (2026-09-11).
Data as JSON: /api/errors/8dc890ba5697f618.
Report an issue: GitHub.
Appendix: source
Thrown at src/Core/Interop/MacOS/Native/SecurityFramework.cs:148
public const int ErrorSecItemNotFound = -25300;
public const int ErrorSecInteractionNotAllowed = -25308;
public const int ErrorSecInteractionRequired = -25315;
public const int ErrorSecNoSuchAttr = -25303;
public static void ThrowIfError(int error, string defaultErrorMessage = "Unknown error.")
{
switch (error)
{
case OK:
return;
case ErrorSecNoSuchKeychain:
throw new InteropException("The keychain does not exist.", error);
case ErrorSecInvalidKeychain:
throw new InteropException("The keychain is not valid.", error);
case ErrorSecAuthFailed:
throw new InteropException("Authorization/Authentication failed.", error);
case ErrorSecDuplicateItem:
throw new InteropException("The item already exists.", error);
case ErrorSecItemNotFound:
throw new InteropException("The item cannot be found.", error);
case ErrorSecInteractionNotAllowed:
throw new InteropException("Interaction with the Security Server is not allowed.", error);
case ErrorSecInteractionRequired:
throw new InteropException("User interaction is required.", error);
case ErrorSecNoSuchAttr:
throw new InteropException("The attribute does not exist.", error);
default:
throw new InteropException(defaultErrorMessage, error);
}
}
}
[Flags]
public enum SessionAttributeBits
{
SessionIsRoot = 0x0001,View on GitHub (pinned to e8ce762cd0)