{"record":{"id":"773afa2d70305ac4","repo":"siyuan-note/siyuan","slug":"conf-language-331","errorCode":null,"errorMessage":"Conf.Language(331)","messagePattern":"Conf\\.Language\\(331\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"info","filePath":"kernel/model/import_obsidian.go","lineNumber":295,"sourceCode":"\tdefer obsidianTasksMu.Unlock()\n\ttask := obsidianTasks[taskID]\n\tif task == nil {\n\t\treturn nil, errors.New(Conf.Language(330))\n\t}\n\treturn snapshotObsidianTask(task), nil\n}\n\nfunc CancelObsidianVaultTask(taskID string) (*ObsidianVaultTask, error) {\n\tobsidianTasksMu.Lock()\n\ttask := obsidianTasks[taskID]\n\tif task == nil {\n\t\tobsidianTasksMu.Unlock()\n\t\treturn nil, errors.New(Conf.Language(330))\n\t}\n\tif !isObsidianCancellableState(task.State) {\n\t\tret := snapshotObsidianTask(task)\n\t\tobsidianTasksMu.Unlock()\n\t\treturn ret, errors.New(Conf.Language(331))\n\t}\n\tif task.Cancel != nil {\n\t\ttask.Cancel()\n\t}\n\tfinishObsidianTaskLocked(task, ObsidianTaskStateCancelled, \"Import cancelled\", \"\")\n\tret := snapshotObsidianTask(task)\n\tobsidianTasksMu.Unlock()\n\tremoveObsidianTemp(taskID)\n\treturn ret, nil\n}\n\nfunc StartObsidianVaultImport(taskID, notebookName string) (*ObsidianVaultTask, error) {\n\tobsidianTasksMu.Lock()\n\ttask := obsidianTasks[taskID]\n\tif task == nil || task.State != ObsidianTaskStateReady || task.Context == nil {\n\t\tobsidianTasksMu.Unlock()\n\t\treturn nil, errors.New(Conf.Language(332))\n\t}","sourceCodeStart":277,"sourceCodeEnd":313,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/import_obsidian.go#L277-L313","documentation":"CancelObsidianVaultTask found the task but its State is not in a cancellable set (isObsidianCancellableState returned false), e.g. the import already completed, failed, or was cancelled. The kernel returns a snapshot of the task along with i18n message 331: 'The Obsidian Vault import task can no longer be cancelled'. This is a state-machine guard, not a data loss situation.","triggerScenarios":"Calling CancelObsidianVaultTask(taskID) when the task.State is completed/failed/cancelled (any state outside isObsidianCancellableState) at kernel/model/import_obsidian.go:295.","commonSituations":"User double-clicks cancel; the import finished between listing the task and issuing the cancel; racing an auto-expiry that already finalized the task; automations issuing cancel without checking current state first.","solutions":["Check the task snapshot returned with the error: if the task already finished, the state is final and no cancel is needed","Call GetObsidianVaultTask first and only cancel while the state is pending/analyzing/importing","Treat the error as benign in the UI (show 'already finished') instead of surfacing it as a failure","Avoid issuing cancel after observing a terminal state via polling"],"exampleFix":"// before\ncancelObsidianTask(taskID) // errors if already finished\n// after\ntask, _ := getObsidianVaultTask(taskID)\nif isCancellableState(task.State) {\n    cancelObsidianTask(taskID)\n}","handlingStrategy":"type-guard","validationCode":"task, err := GetObsidianVaultTask(taskID)\nif err == nil && isCancellableState(task.State) { CancelObsidianVaultTask(taskID) }","typeGuard":"func cancellable(t *ObsidianVaultTask) bool {\n    switch t.State {\n    case \"pending\", \"analyzing\", \"importing\":\n        return true\n    }\n    return false\n}","tryCatchPattern":"ret, err := CancelObsidianVaultTask(taskID)\nif err != nil {\n    // ret still carries the final snapshot; treat as benign\n    log.Printf(\"task already finished: %v\", err)\n}","preventionTips":["Check current task state before issuing cancel","Disable the cancel button in the UI once the state is terminal","Expect the cancel API to return the task snapshot even when it errors"],"tags":["obsidian-import","invalid-state-transition","cancel","race-condition"],"backgroundTag":"invalid-state-transition","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}