{"record":{"id":"20e00a38387237d2","repo":"siyuan-note/siyuan","slug":"key-s-not-found","errorCode":null,"errorMessage":"key [%s] not found","messagePattern":"key \\[(.+?)\\] not found","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/attribute_view.go","lineNumber":1981,"sourceCode":"func AppendAttributeViewDetachedBlocksWithValues(avID string, blocksValues [][]*av.Value) (err error) {\n\tattrView, err := av.ParseAttributeView(avID)\n\tif err != nil {\n\t\tlogging.LogErrorf(\"parse attribute view [%s] failed: %s\", avID, err)\n\t\treturn\n\t}\n\n\tnow := util.CurrentTimeMillis()\n\tvar blockIDs []string\n\tfor _, blockValues := range blocksValues {\n\t\tblockID := ast.NewNodeID()\n\t\tif v := blockValues[0]; \"\" != v.BlockID {\n\t\t\tblockID = v.BlockID\n\t\t}\n\t\tblockIDs = append(blockIDs, blockID)\n\t\tfor _, v := range blockValues {\n\t\t\tkeyValues, _ := attrView.GetKeyValues(v.KeyID)\n\t\t\tif nil == keyValues {\n\t\t\t\terr = fmt.Errorf(\"key [%s] not found\", v.KeyID)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tv.ID = ast.NewNodeID()\n\t\t\tv.BlockID = blockID\n\t\t\tv.Type = keyValues.Key.Type\n\t\t\tif av.KeyTypeBlock == v.Type {\n\t\t\t\tv.Block.Created = now\n\t\t\t\tv.Block.Updated = now\n\t\t\t\tv.Block.ID = \"\"\n\t\t\t}\n\t\t\tv.IsDetached = true\n\t\t\tv.CreatedAt = now\n\t\t\tv.UpdatedAt = now\n\t\t\tv.IsRenderAutoFill = false\n\t\t\tkeyValues.Values = append(keyValues.Values, v)\n\n\t\t\tif av.KeyTypeSelect == v.Type || av.KeyTypeMSelect == v.Type {","sourceCodeStart":1963,"sourceCodeEnd":1999,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/attribute_view.go#L1963-L1999","documentation":"Thrown by AppendAttributeViewDetachedBlocksWithValues when one of the supplied block value sets references a KeyID that GetKeyValues cannot find in the AttributeView. Each value must be attached to an existing key, so an unknown key aborts the whole append.","triggerScenarios":"Calling AppendAttributeViewDetachedBlocksWithValues with a KeyID from a different AV, a deleted key, or a key ID that was never created. Also triggered by a frontend that fabricates key IDs client-side instead of using IDs returned by addAttrViewKey.","commonSituations":"Bulk import / sync code that pre-cached key IDs and then ran after the keys were removed; template instantiation where the template's key IDs are mistakenly used against a fresh AV.","solutions":["Before calling AppendAttributeViewDetachedBlocksWithValues, fetch the AV's current keys and filter the supplied values to only those whose KeyID is present.","If you are creating keys programmatically, first call AddAttributeViewKey and use the returned key IDs in the value sets.","Log the offending KeyID at the call site so missing references are visible during development."],"exampleFix":"// before\nAppendAttributeViewDetachedBlocksWithValues(avID, rowsWithArbitraryKeyIDs)\n// after\nkeys := attrView.GetIDOfKeys() // pseudocode\nfiltered := filterRowsToKeys(rowsWithArbitraryKeyIDs, keys)\nAppendAttributeViewDetachedBlocksWithValues(avID, filtered)","handlingStrategy":"validation","validationCode":"const liveKeyIDs = new Set((await getAttrViewKeys(avID)).map(k => k.id))\nconst safeRows = blocksValues.map(row => row.filter(v => liveKeyIDs.has(v.keyID))).filter(r => r.length > 0)\nif (safeRows.length !== blocksValues.length) { /* log missing keys; do not send */ }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always create keys via AddAttributeViewKey first and use the returned IDs.","Filter bulk-import rows against the AV's current key set before append."],"tags":["attribute-view","key-not-found","detached-block","bulk-import"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}