nextcloud/server · error · BadRequest
The calendar object you're trying to restore is not marked a
Error message
The calendar object you're trying to restore is not marked as deleted
What it means
The row was found for this principal, but its deleted_at column is unset, so the object is not actually in a deleted state; getChild() refuses to wrap it in a DeletedCalendarObject and throws BadRequest (HTTP 400). The trashbin only serves objects genuinely marked deleted, and this state mismatch typically appears when the row changed (e.g. a concurrent restore) after the client obtained its listing.
Source
Thrown at apps/dav/lib/CalDAV/Trashbin/DeletedCalendarObjectsCollection.php:58
throw new NotImplemented();
}
#[\Override]
public function getChild($name) {
if (!preg_match("/(\d+)\\.ics/", $name, $matches)) {
throw new NotFound();
}
$data = $this->caldavBackend->getDeletedCalendarObjectByIdForPrincipal(
(int)$matches[1],
$this->principalInfo['uri'],
);
if ($data === null) {
throw new NotFound();
}
if (!isset($data['deleted_at'])) {
throw new BadRequest('The calendar object you\'re trying to restore is not marked as deleted');
}
return new DeletedCalendarObject(
$this->getRelativeObjectPath($data),
$data,
$this->principalInfo['uri'],
$this->caldavBackend
);
}
#[\Override]
public function createFile($name, $data = null) {
throw new Forbidden();
}
#[\Override]
public function createDirectory($name) {
throw new Forbidden();View on GitHub (pinned to ecdeb153ff)
Solutions
- Re-run the calendar-query REPORT; if the id no longer appears, treat the object as restored and update the UI
- Retry the original intent against the live calendar if the object was restored concurrently
- If it persists for an id that the REPORT still lists, inspect oc_calendar_objects.deleted_at for that row to detect a corrupted marker
Defensive patterns
Strategy: retry
Try / catch
try {
await client.get(href);
} catch (e) {
if (e.status === 400 && /not marked as deleted/.test(e.message)) {
const listing = await calendarQueryReport(trashbinObjectsUrl);
if (listing.includes(href)) return client.get(href); // transient race
return; // object was restored concurrently
}
throw e;
} Prevention
- Re-check the trashbin listing immediately before restore/delete of entries fetched earlier
- Make restore operations idempotent by handling 'already restored' outcomes
When it happens
Trigger: GET/MOVE/DELETE on trashbin/objects/<id>.ics for an object whose deleted_at is NULL — for example another session restored the event between the REPORT listing and this request, or a restore/marker write raced with the trashbin read.
Common situations: Double-clicked restore in a UI where the first request already succeeded; two devices syncing the same trashbin; retries after a timeout that actually completed server-side.
Related errors
- Read-only sharees cannot permanently delete trashbin entries
- Read-only sharees cannot restore trashbin entries
- Permission denied to create files in the trashbin
- Permission denied to create a directory in the trashbin
- Permission denied to delete the trashbin
AI-assisted analysis of nextcloud/server@ecdeb153ff (2026-08-17).
Data as JSON: /api/errors/ff94f6ed2f72414a.
Report an issue: GitHub.