ComposioHQ/composio · error · KeyringError

security add-generic-password: delete-then-add produced dupl

Error message

security add-generic-password: delete-then-add produced duplicate. The existing item may have an ACL that blocks deletion — run `security delete-generic-password -s com.composio.cli -a default` manually.

What it means

The store implements upsert as delete-then-add via the `security` CLI. If the add exits with the duplicate-item status (EXIT_DUPLICATE_ITEM), the preceding delete silently failed — typically because the existing keychain item's ACL denies modification — so the new secret could not be written. The error is a PlatformFailure KeyringError with recovery instructions embedded.

Source

Thrown at ts/packages/cli-keyring/src/stores/macos-security-subprocess.ts:176

      service,
      '-l',
      label,
      '-w',
      encoded,
      ...domainArgs,
    ];

    let result: SpawnResult;
    try {
      result = await runCommand({ command: SECURITY_BIN, args });
    } catch (err) {
      throw new KeyringError({ kind: 'NoStorageAccess', cause: err });
    }

    if (result.code === 0) return;

    if (result.code === EXIT_DUPLICATE_ITEM) {
      throw new KeyringError({
        kind: 'PlatformFailure',
        cause: new Error(
          'security add-generic-password: delete-then-add produced duplicate. ' +
            'The existing item may have an ACL that blocks deletion — ' +
            'run `security delete-generic-password -s com.composio.cli -a default` manually.'
        ),
      });
    }
    throw classifyExitCode(result, 'add-generic-password');
  }

  async getSecret(service: string, user: string, modifiers: EntryModifiers): Promise<Uint8Array> {
    validateSpecifier(service, user);
    const domainArgs = domainFlag(modifiers.keychain);

    // -w prints the password to stdout and nothing else. Without -w,
    // `security` dumps the item's attributes in a human-readable form
    // that's painful to parse.

View on GitHub (pinned to 64b1b85502)

Solutions

  1. Run `security delete-generic-password -s com.composio.cli -a default` manually (may require clicking Allow on the keychain prompt) and retry setSecret.
  2. If deletion is denied, open Keychain Access, find the item, and adjust its Access Control to allow your terminal/app, or delete it via the GUI.
  3. As a last resort, delete the whole item from Keychain Access and let setSecret recreate it.
Defensive patterns

Strategy: fallback

Try / catch

try {
  await store.setSecret(service, user, secret);
} catch (e) {
  if (e instanceof KeyringError && e.kind === 'PlatformFailure' && /duplicate/.test(String(e.cause))) {
    // surface the manual `security delete-generic-password` instruction to the user
  }
}

Prevention

When it happens

Trigger: setSecret on macOS when the existing com.composio.cli/default generic-password item exists, the `security delete-generic-password` call fails due to keychain ACL/permission restrictions, and the subsequent `security add-generic-password` returns EXIT_DUPLICATE_ITEM.

Common situations: Keychain items created by another agent/user or with custom access control; keychain locked or partition IDs missing after macOS upgrades; CI contexts where the security agent prompts are suppressed; secrets previously written by a different bundle identity.

Related errors


AI-assisted analysis of ComposioHQ/composio@64b1b85502 (2026-08-28). Data as JSON: /api/errors/92560d8d904f5fc0. Report an issue: GitHub.