You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[BUG] CalDAV push to a remote calendar always fails: If-Match is sent unquoted #1778
Pushing local changes to a remote CalDAV calendar never succeeds against Radicale. Every attempt is answered with 412 Precondition Failed, tududi records it as a conflict, and _findLocalChanges skips that task from then on, so the task silently stops syncing for good, even though nothing on the server ever changed.
backend/modules/caldav/sync/push-phase.js:174 sends the stored ETag verbatim:
headers['If-Match'] = syncState.etag;
The value is stored without the surrounding quotes. RFC 9110 §13.1.1 defines the field as If-Match = "*" / 1#entity-tag, and §8.8.3 defines opaque-tag = DQUOTE *etagc DQUOTE, the quotes are part of the entity-tag, not decoration around it. Radicale compares strictly and rejects the unquoted form. The stored ETag is otherwise correct: it matches what Radicale returns, only the quotes are missing.
Steps to Reproduce
Self-hosted tududi 1.7.0 with SQLite.
Add a remote calendar pointing at a Radicale collection (POST /api/caldav/remote-calendars, plain HTTP with CALDAV_ALLOW_PRIVATE_HOSTS=true).
Sync once (POST /api/caldav/sync/calendars/:id) so the tasks are pulled and their ETags are stored.
Change a task, e.g. rename it.
Sync again.
Expected Behavior
The changed task is written to the remote collection and the response reports pushedCount: 1.
Actual Behavior
pushedCount: 0 with errors: [{ "error": "Conflict detected: task was modified on server" }], and Radicale logs Precondition failed on PUT request. Nothing on the server had changed.
The task is left with sync_status = 'conflict', and because _findLocalChanges skips tasks in that state, it is never pushed again — subsequent syncs report no error at all, which makes the failure look like it went away.
Isolated with two identical PUTs of the same body to the same URL, differing only in the quoting:
No browser is involved — the push runs server-side in the sync engine, so the Browser field is only filled because it is required.
Suggested fix: keep the quotes when storing the ETag, or add them when building the header. sync/pull-phase.js stores the value and push-phase.js reads it back, so whichever side is normalised has to match the other.
Activity
fix(caldav): send the stored etag as a quoted entity-tag in If-Match (c…
chore: sync upstream v1.7.12-v1.9.1 (#134)