{"record":{"id":"938b177cf623ddf1","repo":"siyuan-note/siyuan","slug":"invalid-deck-id","errorCode":null,"errorMessage":"invalid deck ID","messagePattern":"invalid deck ID","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/flashcard.go","lineNumber":1217,"sourceCode":"func RenameDeck(deckID, name string) (err error) {\n\tdeckLock.Lock()\n\tdefer deckLock.Unlock()\n\n\twaitForSyncingStorages()\n\n\tdeck := Decks[deckID]\n\tdeck.Name = name\n\terr = deck.Save()\n\tif err != nil {\n\t\tlogging.LogErrorf(\"save deck [%s] failed: %s\", deckID, err)\n\t\treturn\n\t}\n\treturn\n}\n\nfunc RemoveDeck(deckID string) (err error) {\n\tif !ast.IsNodeIDPattern(deckID) {\n\t\terr = errors.New(\"invalid deck ID\")\n\t\treturn\n\t}\n\n\tdeckLock.Lock()\n\tdefer deckLock.Unlock()\n\n\twaitForSyncingStorages()\n\n\triffSavePath := getRiffDir()\n\tdeckPath := filepath.Join(riffSavePath, deckID+\".deck\")\n\tif filelock.IsExist(deckPath) {\n\t\tif err = filelock.Remove(deckPath); err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tcardsPath := filepath.Join(riffSavePath, deckID+\".cards\")\n\tif filelock.IsExist(cardsPath) {","sourceCodeStart":1199,"sourceCodeEnd":1235,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/flashcard.go#L1199-L1235","documentation":"RemoveDeck validates that deckID looks like a SiYuan node ID (ast.IsNodeIDPattern: 20-char base32-ish ID) before touching the deck store. Riff decks are keyed by document/block node IDs, so any other string cannot designate a deck and the call fails immediately with 'invalid deck ID'.","triggerScenarios":"Calling RemoveDeck (HTTP API /api/riff/removeRiffDeck) with a deckID that is not a valid node ID — empty string, a numeric ID, a UUID, a truncated or mistyped ID.","commonSituations":"Hard-coded or hand-written deck IDs in scripts; using a card/block ID of wrong length copied with extra characters; passing a custom deck name from another SRS tool.","solutions":["Obtain the deck ID from getRiffDeck IDs (doc/block node IDs) rather than constructing it manually","Validate the ID shape (20-char node-ID pattern) before calling","Check for copy/paste corruption such as surrounding quotes, whitespace, or URL escaping"],"exampleFix":"// before\nawait fetchPost('/api/riff/removeRiffDeck', {deck: 'my-deck-1'});\n// after\nconst deckID = '20240101120000-abcdef1234567'; // real node ID from getRiffDeckIDs\nif (!/^[0-9a-z]{20}$/.test(deckID)) throw new Error('invalid deck ID');\nawait fetchPost('/api/riff/removeRiffDeck', {deck: deckID});","handlingStrategy":"validation","validationCode":"const isNodeID = (id) => typeof id === 'string' && /^[0-9a-v]{20}$/.test(id);\nif (!isNodeID(deckID)) throw new Error('invalid deck ID: ' + deckID);","typeGuard":"const isNodeID = (id) => typeof id === 'string' && /^[0-9a-v]{20}$/.test(id);","tryCatchPattern":"try {\n  await fetchPost('/api/riff/removeRiffDeck', {deck: deckID});\n} catch (e) {\n  if (String(e.msg).includes('invalid deck ID')) console.warn('bad deck ID', deckID);\n  else throw e;\n}","preventionTips":["Always take deck IDs from API responses (getRiffDeckIDs), never fabricate them","Trim and unescape IDs copied from URLs or logs","Centralize a node-ID validator in client code"],"tags":["flashcards","validation","identifier"],"backgroundTag":"invalid-identifier-format","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}