{"record":{"id":"049dfb7e472c2467","repo":"siyuan-note/siyuan","slug":"attribute-view-not-found-s","errorCode":null,"errorMessage":"attribute view not found: %s","messagePattern":"attribute view not found: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/attribute_view.go","lineNumber":76,"sourceCode":"\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\n\t}\n\n\tnewP := strings.TrimPrefix(absPath, util.DataDir)\n\thistoryPath := filepath.Join(historyDir, newP)\n\tif filelock.IsExist(absPath) {\n\t\tif err = filelock.Copy(absPath, historyPath); err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif err = filelock.RemoveWithoutFatal(absPath); err != nil {\n\t\tlogging.LogErrorf(\"remove unused asset [%s] failed: %s\", absPath, err)","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/attribute_view.go#L58-L94","documentation":"After passing validation, RemoveUnusedAttributeView looks for the AV's JSON file at data/storage/av/<id>.json. If the file does not exist, the kernel returns this error instead of attempting history archiving/deletion. The database record is already gone, so there is nothing to clean.","triggerScenarios":"Calling RemoveUnusedAttributeView for an ID whose storage/av/<id>.json file is missing — the AV file was deleted externally, the ID was already removed by another client, or the storage folder was cleaned/restored from a partial backup.","commonSituations":"Manual deletion of files under data/storage/av; sync conflicts removing the JSON; repeated API calls after a successful removal (retry with stale ID); restoring a workspace backup that lacks the AV file.","solutions":["Confirm the ID is still in the fresh UnusedAttributeViews list before calling; drop stale IDs","Check whether data/storage/av/<id>.json exists; if the record is already gone, treat cleanup as done","Restore the missing AV file from history/backup if you need its data","Re-sync the workspace so the storage/av state is consistent across clients"],"exampleFix":"// before: blind removal\nawait removeUnusedAttributeView(id)\n// after: guard on existence\nconst unused = await getUnusedAttributeViews();\nif (unused.some(i => i.Item === id)) await removeUnusedAttributeView(id);","handlingStrategy":"validation","validationCode":"import fs from 'fs';\nimport path from 'path';\nconst avFile = path.join(dataDir, 'storage', 'av', id + '.json');\nif (!fs.existsSync(avFile)) { /* already removed; skip */ }","typeGuard":null,"tryCatchPattern":"try { await removeUnusedAttributeView(id) } catch (e) {\n  if (e.message.includes('attribute view not found')) {\n    // treat as already-cleaned; drop id from worklist\n    return;\n  }\n}","preventionTips":["Never manually delete files under data/storage/av while the app runs","Make removal calls idempotent: ignore not-found on retries","Restore missing AV files from history/backup before cleanup"],"tags":["attribute-view","file-not-found","cleanup"],"backgroundTag":"file-not-found","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"}