{"record":{"id":"d4f27f5450713ff1","repo":"nextcloud/server","slug":"this-addressbook-is-immutable","errorCode":null,"errorMessage":"This addressbook is immutable","messagePattern":"This addressbook is immutable","errorType":"exception","errorClass":"Exception","httpStatus":500,"severity":"warning","filePath":"apps/contactsinteraction/lib/AddressBook.php","lineNumber":44,"sourceCode":"\tuse ACLTrait;\n\n\tpublic const URI = 'recent';\n\n\tpublic function __construct(\n\t\tprivate RecentContactMapper $mapper,\n\t\tprivate IL10N $l10n,\n\t\tprivate string $principalUri,\n\t) {\n\t\tparent::__construct(Application::APP_ID, self::URI);\n\t}\n\n\t/**\n\t * @inheritDoc\n\t * @throws Exception\n\t */\n\t#[\\Override]\n\tpublic function delete(): void {\n\t\tthrow new Exception('This addressbook is immutable');\n\t}\n\n\t/**\n\t * @inheritDoc\n\t * @throws Exception\n\t */\n\t#[\\Override]\n\tpublic function createFile($name, $data = null) {\n\t\tthrow new Exception('This addressbook is immutable');\n\t}\n\n\t/**\n\t * @inheritDoc\n\t * @throws NotFound\n\t */\n\t#[\\Override]\n\tpublic function getChild($name): Card {\n\t\ttry {","sourceCodeStart":26,"sourceCodeEnd":62,"githubUrl":"https://github.com/nextcloud/server/blob/ecdeb153ffdf227235c9a7e2d13dbe0f9c817bc3/apps/contactsinteraction/lib/AddressBook.php#L26-L62","documentation":"Generic PHP Exception thrown by the contactsinteraction 'Recently contacted' address book (an ExternalAddressBook exposed at .../contactsinteraction_recent) from delete(), createFile() and propPatch(). This address book is a read-only, auto-generated view over the recent-contacts table, so every mutating CardDAV operation is deliberately refused; getProperties() even advertises {owncloud}read-only = true.","triggerScenarios":"A CardDAV client issuing DELETE on the address book collection, PUT/POST to create a card inside it, or PROPPATCH on its properties (rename, color) — i.e. any write attempt on the 'recent' address book. Reads (getChildren, getChild, getProperties) succeed; only mutations throw.","commonSituations":"Sync clients (DAVx5, iOS/macOS Contacts) that try to write cache metadata or reconcile deletions into every synced book; users attempting to delete 'Recently contacted' from the Contacts app; clients that ignore the read-only property and attempt renames.","solutions":["In CardDAV clients, honor the {http://owncloud.org/ns}read-only property and exclude the book from writes/sync","Direct users to create contacts in a regular (writable) address book — recently-contacted entries are managed by the server","If the error recurs from a sync client, report it as a client bug (writing to an advertised read-only collection)","Do not attempt server-side cleanup by deleting this address book; the table is pruned by the contactsinteraction background job"],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":null,"typeGuard":"// CardDAV client: check the read-only flag before any write\nfunction isReadOnlyAddressBook(\\Sabre\\CardDAV\\AddressBook $book): bool {\n\t$props = $book->getProperties(['{http://owncloud.org/ns}read-only']);\n\treturn (($props['{http://owncloud.org/ns}read-only'] ?? false) === true)\n\t\t|| (($props['{http://owncloud.org/ns}read-only'] ?? 0) === 1);\n}","tryCatchPattern":"try {\n\t$book->createFile($name, $cardData);\n} catch (\\Exception $e) {\n\tif (str_contains($e->getMessage(), 'immutable')) {\n\t\t// read-only 'Recently contacted' book — route the write to a user address book instead\n\t} else {\n\t\tthrow $e;\n\t}\n}","preventionTips":["Sync clients must honor the advertised read-only property and skip writes to such collections","Create contacts in regular address books; the recent list is server-managed","Do not offer delete/rename UI actions on collections flagged read-only"],"tags":["carddav","contactsinteraction","read-only","addressbook","php"],"backgroundTag":"read-only-resource-write","analyzedSha":"ecdeb153ffdf227235c9a7e2d13dbe0f9c817bc3","analyzedAt":"2026-08-17T01:36:13.386Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}