siyuan-note/siyuan · info

Conf.Language(331)

Error message

Conf.Language(331)

What it means

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.

Source

Thrown at kernel/model/import_obsidian.go:295

	defer obsidianTasksMu.Unlock()
	task := obsidianTasks[taskID]
	if task == nil {
		return nil, errors.New(Conf.Language(330))
	}
	return snapshotObsidianTask(task), nil
}

func CancelObsidianVaultTask(taskID string) (*ObsidianVaultTask, error) {
	obsidianTasksMu.Lock()
	task := obsidianTasks[taskID]
	if task == nil {
		obsidianTasksMu.Unlock()
		return nil, errors.New(Conf.Language(330))
	}
	if !isObsidianCancellableState(task.State) {
		ret := snapshotObsidianTask(task)
		obsidianTasksMu.Unlock()
		return ret, errors.New(Conf.Language(331))
	}
	if task.Cancel != nil {
		task.Cancel()
	}
	finishObsidianTaskLocked(task, ObsidianTaskStateCancelled, "Import cancelled", "")
	ret := snapshotObsidianTask(task)
	obsidianTasksMu.Unlock()
	removeObsidianTemp(taskID)
	return ret, nil
}

func StartObsidianVaultImport(taskID, notebookName string) (*ObsidianVaultTask, error) {
	obsidianTasksMu.Lock()
	task := obsidianTasks[taskID]
	if task == nil || task.State != ObsidianTaskStateReady || task.Context == nil {
		obsidianTasksMu.Unlock()
		return nil, errors.New(Conf.Language(332))
	}

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Check the task snapshot returned with the error: if the task already finished, the state is final and no cancel is needed
  2. Call GetObsidianVaultTask first and only cancel while the state is pending/analyzing/importing
  3. Treat the error as benign in the UI (show 'already finished') instead of surfacing it as a failure
  4. Avoid issuing cancel after observing a terminal state via polling

Example fix

// before
cancelObsidianTask(taskID) // errors if already finished
// after
task, _ := getObsidianVaultTask(taskID)
if isCancellableState(task.State) {
    cancelObsidianTask(taskID)
}
Defensive patterns

Strategy: type-guard

Validate before calling

task, err := GetObsidianVaultTask(taskID)
if err == nil && isCancellableState(task.State) { CancelObsidianVaultTask(taskID) }

Type guard

func cancellable(t *ObsidianVaultTask) bool {
    switch t.State {
    case "pending", "analyzing", "importing":
        return true
    }
    return false
}

Try / catch

ret, err := CancelObsidianVaultTask(taskID)
if err != nil {
    // ret still carries the final snapshot; treat as benign
    log.Printf("task already finished: %v", err)
}

Prevention

When it happens

Trigger: Calling CancelObsidianVaultTask(taskID) when the task.State is completed/failed/cancelled (any state outside isObsidianCancellableState) at kernel/model/import_obsidian.go:295.

Common situations: 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.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/773afa2d70305ac4. Report an issue: GitHub.