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

  1. Re-run the calendar-query REPORT; if the id no longer appears, treat the object as restored and update the UI
  2. Retry the original intent against the live calendar if the object was restored concurrently
  3. 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

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


AI-assisted analysis of nextcloud/server@ecdeb153ff (2026-08-17). Data as JSON: /api/errors/ff94f6ed2f72414a. Report an issue: GitHub.