{"record":{"id":"31a2a7615db3a3ec","repo":"nextcloud/server","slug":"card-not-found","errorCode":null,"errorMessage":"Card not found","messagePattern":"Card not found","errorType":"exception","errorClass":"NotFound","httpStatus":404,"severity":"error","filePath":"apps/dav/lib/CardDAV/AddressBook.php","lineNumber":157,"sourceCode":"\t\t}\n\n\t\t$acl = $this->carddavBackend->applyShareAcl($this->getResourceId(), $acl);\n\t\t$allowedPrincipals = [$this->getOwner(), parent::getOwner(), 'principals/system/system', '{DAV:}authenticated'];\n\t\treturn array_filter($acl, function ($rule) use ($allowedPrincipals) {\n\t\t\treturn \\in_array($rule['principal'], $allowedPrincipals, true);\n\t\t});\n\t}\n\n\t#[\\Override]\n\tpublic function getChildACL() {\n\t\treturn $this->getACL();\n\t}\n\n\t#[\\Override]\n\tpublic function getChild($name) {\n\t\t$obj = $this->carddavBackend->getCard($this->addressBookInfo['id'], $name);\n\t\tif (!$obj) {\n\t\t\tthrow new NotFound('Card not found');\n\t\t}\n\t\t$obj['acl'] = $this->getChildACL();\n\t\treturn new Card($this->carddavBackend, $this->addressBookInfo, $obj);\n\t}\n\n\t#[\\Override]\n\tpublic function getChildren() {\n\t\t$objs = $this->carddavBackend->getCards($this->addressBookInfo['id']);\n\t\t$children = [];\n\t\tforeach ($objs as $obj) {\n\t\t\t$obj['acl'] = $this->getChildACL();\n\t\t\t$children[] = new Card($this->carddavBackend, $this->addressBookInfo, $obj);\n\t\t}\n\n\t\treturn $children;\n\t}\n\n\t#[\\Override]","sourceCodeStart":139,"sourceCodeEnd":175,"githubUrl":"https://github.com/nextcloud/server/blob/ecdeb153ffdf227235c9a7e2d13dbe0f9c817bc3/apps/dav/lib/CardDAV/AddressBook.php#L139-L175","documentation":"AddressBook::getChild($name) resolves a card by URI through CardDavBackend::getCard(addressBookId, name); when no row matches it throws NotFound('Card not found'), which the DAV server returns as HTTP 404. This is the standard 'no such resource' answer for GET/PROPFIND on /remote.php/dav/addressbooks/<...>/<book>/<card>.vcf.","triggerScenarios":"GET on a card URI that was never created, was deleted by another client, or was purged after trashbin retention; syncing from a stale local cache that still references removed card URIs and etags.","commonSituations":"Two clients racing where one deletes while the other fetches; a client database restored from an old backup; the addressbook having been recreated so all URIs changed.","solutions":["Treat the 404 as 'card gone': remove it from the local cache and continue the sync instead of retrying","After repeated 404s, reconcile with a fresh sync-token via the WebDAV sync-collection REPORT","When creating a card, PUT it directly rather than GET-then-create","If the card should exist, verify you are addressing the right addressbook URI"],"exampleFix":"// before\ntry {\n    $card = $book->getChild('old-uuid.vcard');\n} catch (\\Exception $e) {\n    abortSync();\n}\n\n// after\nuse Sabre\\DAV\\Exception\\NotFound;\ntry {\n    $card = $book->getChild('old-uuid.vcard');\n} catch (NotFound $e) {\n    $localCache->forget('old-uuid.vcard');\n}","handlingStrategy":"validation","validationCode":"if (!$addressBook->childExists($name)) {\n    return null; // card absent: prune local cache instead of issuing a GET\n}","typeGuard":null,"tryCatchPattern":"use Sabre\\DAV\\Exception\\NotFound;\n\ntry {\n    $card = $addressBook->getChild($name);\n} catch (NotFound $e) {\n    $cache->remove($name);\n    return null;\n}","preventionTips":["Use childExists()/PROPFIND before GETting cached card URIs","Reconcile state with sync-token REPORTs instead of blind GETs","On 404, delete local state rather than retrying the fetch"],"tags":["carddav","dav","http-404","sync"],"backgroundTag":"dav-404-not-found","analyzedSha":"ecdeb153ffdf227235c9a7e2d13dbe0f9c817bc3","analyzedAt":"2026-08-17T01:36:13.386Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}