{"record":{"id":"9a53276effacb207","repo":"nextcloud/server","slug":"failed-to-create-tag","errorCode":null,"errorMessage":"Failed to create tag","messagePattern":"Failed to create tag","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/systemtags/src/services/api.ts","lineNumber":109,"sourceCode":"\ttry {\n\t\tconst { headers } = await davClient.customRequest(path, {\n\t\t\tmethod: 'POST',\n\t\t\tdata: tagToPost,\n\t\t})\n\t\tconst contentLocation = headers.get('content-location')\n\t\tif (contentLocation) {\n\t\t\temit('systemtags:tag:created', tag)\n\t\t\treturn parseIdFromLocation(contentLocation)\n\t\t}\n\t\tlogger.error(t('systemtags', 'Missing \"Content-Location\" header'))\n\t\tthrow new Error(t('systemtags', 'Missing \"Content-Location\" header'))\n\t} catch (error) {\n\t\tif ((error as WebDAVClientError)?.response?.status === 409) {\n\t\t\tlogger.error(t('systemtags', 'A tag with the same name already exists'), { error })\n\t\t\tthrow new Error(t('systemtags', 'A tag with the same name already exists'), { cause: error })\n\t\t}\n\t\tlogger.error(t('systemtags', 'Failed to create tag'), { error })\n\t\tthrow new Error(t('systemtags', 'Failed to create tag'), { cause: error })\n\t}\n}\n\n/**\n * Update a tag on the server.\n *\n * @param tag - The tag to update\n */\nexport async function updateTag(tag: TagWithId): Promise<void> {\n\tconst path = '/systemtags/' + tag.id\n\tconst data = `<?xml version=\"1.0\"?>\n\t<d:propertyupdate xmlns:d=\"DAV:\" xmlns:oc=\"http://owncloud.org/ns\" xmlns:nc=\"http://nextcloud.org/ns\">\n\t\t<d:set>\n\t\t\t<d:prop>\n\t\t\t\t<oc:display-name>${tag.displayName}</oc:display-name>\n\t\t\t\t<oc:user-visible>${tag.userVisible}</oc:user-visible>\n\t\t\t\t<oc:user-assignable>${tag.userAssignable}</oc:user-assignable>\n\t\t\t\t<nc:color>${tag?.color || null}</nc:color>","sourceCodeStart":91,"sourceCodeEnd":127,"githubUrl":"https://github.com/nextcloud/server/blob/ecdeb153ffdf227235c9a7e2d13dbe0f9c817bc3/apps/systemtags/src/services/api.ts#L91-L127","documentation":"The catch-all wrapper thrown by createTag() when the POST /systemtags request fails for any reason other than a 409 duplicate-name conflict. The original WebDAVClientError is attached as `cause`, so the real HTTP status (401/403/5xx) or network error is only visible through the cause chain. Note that the 'Missing \"Content-Location\" header' error is thrown inside the same try block, so a reverse proxy stripping that header also surfaces as this error.","triggerScenarios":"POST /systemtags returning non-409: 401 (expired session), 403 (a non-admin creating a tag that is not user-assignable), 503 (maintenance mode), or a network failure. Also triggered when the response omits the Content-Location header (header stripped by proxy, non-conforming server version), because that inner throw is re-caught and re-wrapped here.","commonSituations":"Session expired while the tag dialog was open; user without admin rights trying to create a non-assignable/invisible tag; reverse proxies (nginx with header filtering) dropping Content-Location; server in maintenance mode during an upgrade; dev environment pointed at a server that predates the Content-Location behavior.","solutions":["Inspect (error.cause as WebDAVClientError)?.response?.status: 401 → re-authenticate / run confirmPassword(); 403 → the user lacks rights to create this tag; 5xx → check the Nextcloud server log","If the cause is the 'Missing \"Content-Location\" header' error, check whether the reverse proxy forwards the Content-Location response header","For admin tag operations, call confirmPassword() from @nextcloud/password-confirmation before createTag","Retry once after re-login when the cause status is 401"],"exampleFix":"// before\ntry {\n\tawait createTag(tag)\n} catch (e) {\n\tconsole.error(e.message) // 'Failed to create tag' — real reason hidden\n}\n\n// after\ntry {\n\tawait createTag(tag)\n} catch (e) {\n\tconst status = (e.cause as WebDAVClientError)?.response?.status\n\tif (status === 401) await confirmPassword() // then retry\n\telse if (status === 403) showError(t('systemtags', 'You are not allowed to create this tag'))\n\telse throw e\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n\tconst id = await createTag(tag)\n} catch (error) {\n\tconst cause = error.cause as WebDAVClientError | undefined\n\tconst status = cause?.response?.status\n\tif (status === 401) { /* re-authenticate / confirmPassword(), then retry once */ }\n\telse if (status === 403) { /* user lacks rights for this tag */ }\n\telse if (error.message.includes('Content-Location')) { /* proxy strips the header */ }\n\telse throw error\n}","preventionTips":["Call confirmPassword() before admin-level tag operations","Ensure the reverse proxy forwards the Content-Location response header","Handle session-expiry centrally in the DAV client so 401s re-prompt login instead of surfacing here","Log error.cause, not just error.message — the status code lives in the cause chain"],"tags":["systemtags","webdav","http-error","typescript","nextcloud"],"backgroundTag":"webdav-request-failed","analyzedSha":"ecdeb153ffdf227235c9a7e2d13dbe0f9c817bc3","analyzedAt":"2026-08-17T01:36:13.386Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}