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

  1. Check existence first with SecKeychainFindGenericPassword; if found, update the item's data (SecKeychainItemModifyAttributesAndData) instead of adding.
  2. Delete the existing item (SecKeychainItemDelete) before adding, only if overwriting is intentional.
  3. Wrap the add call in a catch of InteropException with error -25299 and treat it as 'already stored'.
  4. 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

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


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)