Skip to content

CloudKit subscription uniqueness is content-based, not subscriptionID-based (empirical findings) #387

@leogdion

Description

@leogdion

Summary

Empirical findings from a one-off mistdemo probe-duplicate-subscription
run against a real CloudKit container (public DB) confirm that
CloudKit Web Services enforces subscription uniqueness on the
(recordType, firesOn) tuple, not on subscriptionID.

This matters because the failure surfaces as the generic
INTERNAL_ERROR with the misleading reason
"could not find subscription we just created", with no formal
CONFLICT/EXISTS code — exactly the case #379 added per-item
surfacing for, but the misleading reason text doesn't tell the caller
why the duplicate fires.

MistKit now surfaces this via:

  • SubscriptionOperationFailure.isLikelyDuplicate: Bool — opt-in
    hint, exact-match on the reason string.
  • CloudKitError.subscriptionLikelyDuplicate(SubscriptionOperationFailure)
    — thrown from the createSubscription convenience wrapper when the
    hint matches. Batch modifySubscriptions is unchanged; raw failures
    still flow through SubscriptionResult.failure.

Both are intentionally hedged (isLikely…, "likely cause: …") because
the wire-level code is just INTERNAL_ERROR — the duplicate
interpretation is MistKit's inference from the reason string.

Probe matrix (2026-05-25, public DB)

Seed creates one subscription, then the probe creates a second with
one or more axes varied:

# Variation Result
1 different ID, same recordType, same firesOn FAIL — INTERNAL_ERROR / marker / isLikelyDuplicate=true
2 same ID, same recordType, same firesOn SUCCESS — CloudKit accepted the same ID twice
3 different ID, same recordType, different firesOn ([.create] vs [.update]) SUCCESS
4 different ID, same recordType, superset firesOn ([.create] vs [.create, .update]) SUCCESS — uniqueness is exact-set match, not overlap
5 different ID, different recordType (NoteArticle) 404 (Article absent from public schema — not a clean control)

Surprises

  1. Same subscriptionID twice succeeds silently (Fix "method_lines" issue in Sources/MistKit/MKDatabase.swift #2). The
    intuitive "IDs must be unique on create" mental model is wrong —
    CloudKit is idempotent on the ID and quietly returns the existing
    record.
  2. Uniqueness on firesOn is exact-set match, not overlap (Support ARM in GitHub Actions #4).
    Two subscriptions with disjoint or partly-overlapping fire-event
    sets coexist; only an identical set collides.

Reproduction

cd Examples/MistDemo
swift run mistdemo probe-duplicate-subscription \
  --database public --verbose

The command seeds a subscription, probes with five variations,
cleans up after itself, and prints serverErrorCode + raw reason
for each probe. It is not part of test-public/test-private;
run it manually when investigating subscription failures.

Suggested follow-ups

  • Apple Feedback Assistant report: ask Apple to either
    (a) add a SUBSCRIPTION_EXISTS / CONFLICT server error code for
    this case, or (b) document the (recordType, firesOn) uniqueness
    constraint and the misleading INTERNAL_ERROR reason in
    CloudKit Web Services docs.
  • Revisit the exact-match detection in isLikelyDuplicate if the
    probe surfaces wording variants in future runs.
  • Consider documenting client-side cleanup pattern: clean by
    (recordType, firesOn) identity via listSubscriptions, not by
    subscriptionID alone (an old subscription under a different ID
    can still collide).

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentationlibrary-apiLibrary API design

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions