{"record":{"id":"50b5a643760c1da4","repo":"siyuan-note/siyuan","slug":"the-obsidian-vault-import-task-can-no-longer-be-ca","errorCode":null,"errorMessage":"The Obsidian Vault import task can no longer be cancelled","messagePattern":"The Obsidian Vault import task can no longer be cancelled","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","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/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/import_obsidian.go#L277-L313","documentation":"Returned by CancelObsidianVaultTask (i18n key 331) when the task exists but is not in a cancellable state. isObsidianCancellableState returns true only for queued, analyzing, ready, revalidating, and staging; once the task reaches creating, writing, or indexing (the committed-write phase), cancellation is refused because partial writes would corrupt the notebook. The endpoint still returns the task snapshot in ret.Data so the caller can see the current state.","triggerScenarios":"POST /api/import/cancelObsidianVaultTask when task.State is creating, writing, or indexing (line 292-295). The task is past the staging point and is actively writing documents to disk.","commonSituations":"User clicks cancel after the import has begun writing notes to the notebook; a long import is in the indexing phase and the user tries to abort; the UI cancel button was not disabled when the task transitioned to writing.","solutions":["Wait for the write/index phase to complete — the notebook will be fully written and can be deleted afterward if unwanted.","Cancel earlier: issue cancel during analyzing, ready, revalidating, or staging, before the committed-write phase begins.","If the resulting notebook is unwanted, delete it via the notebook management API after the task completes.","Disable the cancel control in the UI once the task enters a non-cancellable state."],"exampleFix":"// before: cancelling during 'writing' state\npost(\"/api/import/cancelObsidianVaultTask\", {taskID})\n// -> { code: -1, msg: \"... can no longer be cancelled\", data: {state:\"writing\"} }\n\n// after: wait for completion, then remove the notebook if not wanted\nwaitForTerminal(taskID)\nif task.result && task.result.notebookID {\n    removeNotebook(task.result.notebookID)\n}","handlingStrategy":"type-guard","validationCode":"// Check the task state is cancellable before issuing cancel\nfunc isCancellableState(state string) bool {\n    switch state {\n    case \"queued\", \"analyzing\", \"ready\", \"revalidating\", \"staging\":\n        return true\n    }\n    return false\n}","typeGuard":"func isCancellable(task *ObsidianVaultTask) bool {\n    if task == nil { return false }\n    switch task.State {\n    case \"queued\", \"analyzing\", \"ready\", \"revalidating\", \"staging\":\n        return true\n    }\n    return false\n}","tryCatchPattern":"task, err := model.CancelObsidianVaultTask(taskID)\nif err != nil && err.Error() == Conf.Language(331) {\n    // task is past staging — cannot cancel; wait for completion\n    return fmt.Errorf(\"import is past the cancellation point (state: %s); wait for completion\", task.State)\n}","preventionTips":["Cancel only during queued/analyzing/ready/revalidating/staging states.","Disable the cancel button once the task enters creating/writing/indexing.","If the notebook is unwanted, delete it after the task completes."],"tags":["obsidian","import","task-lifecycle","cancellation"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}