fix: preserve delegated sharing conflicts#4852
Open
shepilov wants to merge 3 commits into
Open
Conversation
shepilov
force-pushed
the
fix/delegated-sharing-conflict
branch
2 times, most recently
from
July 16, 2026 09:19
e6ae3ca to
23e4a15
Compare
shepilov
force-pushed
the
fix/delegated-sharing-conflict
branch
from
July 16, 2026 09:20
23e4a15 to
67cd898
Compare
shepilov
marked this pull request as ready for review
July 16, 2026 09:24
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bug
When a recipient of an open shared drive adds several members, the request can
partially succeed on the owner while returning a
500to the initiatingrecipient.
Observed staging sequence:
POST /sharings/:id/recipientsPOST /recipients/delegated201)201)200)/answercallback returns404(The member was not found) becausethe owner cannot yet match Petr's invitation state to a persisted
credential/member
409 Document update conflictfailure, refreshes the token, and replays the complete delegated request
409 Sharing already accepted by this recipient500 Unqualified errorcredential exchange did not complete
This leaves the sharing inconsistent between instances: the owner can show Petr
as pending while the initiating recipient shows him as revoked or does not
display him.
Root cause
The owner added members and credentials only in memory, then sent Cozy shortcut
requests before saving the sharing. A trusted recipient can auto-accept
synchronously while its shortcut request is still in progress and call the
owner's
/answerendpoint. At that point, the new invitation state was not yetvisible in CouchDB. The state existed in Petr's shortcut and was sent back by
his Cozy, but
ProcessAnswercould not find the matching entry in the owner'spersisted
Sharing.Credentials, returnedErrMemberNotFound, and the HTTPhandler mapped it to
404.The auto-accept callback could also update the sharing concurrently. The final
owner-side save then used a stale CouchDB revision and failed with a conflict.
When status persistence retried a conflict, it reloaded the latest document only
into a local pointer, leaving the caller-owned sharing stale.
Finally,
SendDelegatedretried every4xxresponse after refreshing the token.That is unsafe for this non-idempotent request:
404and409are businesserrors, not authentication errors, and replaying the batch can duplicate side
effects or replace the original failure with
Sharing already accepted.Fix
before sending any shortcut
states only from the successfully persisted attempt
processing the rest of the batch
that already reached
ready, apply the desired statuses, and synchronize thecaller-owned sharing before retrying
refreshing routing/authentication information:
401,403, and4104xxerrors instead of wrapping them in ageneric
500while keeping member synchronization independent from that state map
mail-not-sentThe invitation-state response is keyed by the recipient's Cozy instance URL
(or email when no instance is available). It contains only the states created
and persisted by this request; it is not a complete state for every member of
the sharing.
API compatibility
No Drive client API change is required. The existing
POST /sharings/:sharing-id/recipientsrequest and delegated response format areunchanged. The change makes mixed batches idempotent and ensures the response is
returned only after the owner has durably stored the states needed by immediate
auto-accept callbacks.
Tests
Coverage includes:
verifying that the owner can resolve the persisted state, the new member
reaches
ready, and the shared file is accessible401,403,410,404, and409The sharing API documentation now also states that adding an already-active
recipient is idempotent and that delegated credentials are persisted before
shortcuts are sent.