{"record":{"id":"eda3e84af58f2e0e","repo":"siyuan-note/siyuan","slug":"attribute-view-is-not-unused-s","errorCode":null,"errorMessage":"attribute view is not unused: %s","messagePattern":"attribute view is not unused: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/attribute_view.go","lineNumber":64,"sourceCode":"\t\"github.com/siyuan-note/siyuan/kernel/cache\"\n\t\"github.com/siyuan-note/siyuan/kernel/filesys\"\n\t\"github.com/siyuan-note/siyuan/kernel/search\"\n\t\"github.com/siyuan-note/siyuan/kernel/sql\"\n\t\"github.com/siyuan-note/siyuan/kernel/treenode\"\n\t\"github.com/siyuan-note/siyuan/kernel/util\"\n\t\"github.com/xrash/smetrics\"\n)\n\nfunc ValidateUnusedAttributeView(id string) error {\n\tif !ast.IsNodeIDPattern(id) {\n\t\treturn fmt.Errorf(\"invalid attribute view ID: %s\", id)\n\t}\n\tfor _, item := range UnusedAttributeViews(false) {\n\t\tif item.Item == id {\n\t\t\treturn nil\n\t\t}\n\t}\n\treturn fmt.Errorf(\"attribute view is not unused: %s\", id)\n}\n\nfunc RemoveUnusedAttributeView(id string) (err error) {\n\t// 防御性校验：ID 必须是合法的节点 ID 格式，防止通过路径穿越读取或删除任意文件\n\tif err = ValidateUnusedAttributeView(id); err != nil {\n\t\treturn\n\t}\n\n\tbase := filepath.Join(util.DataDir, \"storage\", \"av\")\n\tabsPath := filepath.Join(base, id+\".json\")\n\tif !filelock.IsExist(absPath) {\n\t\treturn fmt.Errorf(\"attribute view not found: %s\", id)\n\t}\n\n\thistoryDir, err := getHistoryDir(HistoryOpClean)\n\tif err != nil {\n\t\tlogging.LogErrorf(\"get history dir failed: %s\", err)\n\t\treturn","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/attribute_view.go#L46-L82","documentation":"The supplied attribute-view ID is syntactically valid but is not present in the current list of unused attribute views (UnusedAttributeViews). The kernel refuses to remove an AV that is still referenced by documents, preventing deletion of databases in use.","triggerScenarios":"Calling RemoveUnusedAttributeView with an AV ID that is still bound to a document's attribute view, was already removed (stale ID from a cached list), or the unused list was refreshed since the ID was obtained.","commonSituations":"Stale UI/plugin cache showing an AV as unused after it was re-attached; racing with another client that removed or re-used the AV; typo'd ID coincidentally matching a valid-format ID of an in-use AV.","solutions":["Re-fetch UnusedAttributeViews(false) and pick a currently listed unused ID before removing","Refresh the database panel/UI so the stale entry disappears","Verify the AV is no longer referenced by any document (remove the database block first) before cleanup"],"exampleFix":"// before: using stale id\nremoveUnusedAttributeView(staleId)\n// after: re-validate from fresh list\nconst unused = await getUnusedAttributeViews();\nif (unused.some(i => i.Item === id)) removeUnusedAttributeView(id);","handlingStrategy":"validation","validationCode":"const unused = await getUnusedAttributeViews();\nif (!unused.some(i => i.Item === id)) throw new Error('AV is still in use or stale');","typeGuard":null,"tryCatchPattern":"try { await removeUnusedAttributeView(id) } catch (e) {\n  if (e.message.includes('not unused')) {\n    await refreshUnusedList();  // id is stale or AV got re-attached\n  }\n}","preventionTips":["Re-fetch the unused list immediately before each removal","Detach the database from its document before cleanup","Avoid caching unused-AV IDs across sessions"],"tags":["validation","state","attribute-view"],"backgroundTag":"invalid-state-transition","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}