siyuan-note/siyuan · warning

Conf.Language(329)

Error message

Conf.Language(329)

What it means

Only one Obsidian Vault import task may run at a time. `ImportObsidianVault` checks `obsidianActive`; if another task is active and already past its pre-import phase (i.e. actually importing), it returns the localized message 'An Obsidian Vault import task is already running' (Conf.Language(329)). This serializes the heavyweight vault conversion.

Source

Thrown at kernel/model/import_obsidian.go:250

	errObsidianVaultUnsafePath      = errors.New("Obsidian Vault path is unsafe")
	errObsidianVaultConfigMissing   = errors.New("Obsidian Vault config directory is missing")
	errObsidianVaultMarkdownMissing = errors.New("Obsidian Vault has no readable Markdown")
	errObsidianSourceChanged        = errors.New("Obsidian source file changed")

	obsidianBlockIDPattern  = regexp.MustCompile(`(?m)(?:^|[ \t])\^([A-Za-z0-9-]+)[ \t]*$`)
	obsidianQuotePattern    = regexp.MustCompile(`^((?:[ \t]*>[ \t]?)+)(.*)$`)
	obsidianListItemPattern = regexp.MustCompile(`^([ \t]*(?:[-+*]|\d+[.)])[ \t]+)(.*)$`)
	obsidianFootnotePattern = regexp.MustCompile(`(?m)\[\^[^\]\r\n]+\]`)
)

func StartObsidianVaultAnalysis(localPath string) (*ObsidianVaultTask, error) {
	var replacedTaskID string
	obsidianTasksMu.Lock()
	if obsidianActive != "" {
		if active := obsidianTasks[obsidianActive]; active != nil && !isObsidianTerminalState(active.State) {
			if !isObsidianPreImportState(active.State) {
				obsidianTasksMu.Unlock()
				return nil, errors.New(Conf.Language(329))
			}
			if active.Cancel != nil {
				active.Cancel()
			}
			replacedTaskID = active.TaskID
			finishObsidianTaskLocked(active, ObsidianTaskStateCancelled, "Import cancelled", "")
		}
	}

	taskID := ast.NewNodeID()
	ctx, cancel := context.WithCancel(context.Background())
	task := &obsidianTask{TaskID: taskID, State: ObsidianTaskStateQueued, Message: "Waiting to analyze", Cancel: cancel}
	obsidianTasks[taskID] = task
	obsidianActive = taskID
	ret := snapshotObsidianTask(task)
	obsidianTasksMu.Unlock()

	if replacedTaskID != "" {

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Poll `GetObsidianVaultTask(activeTaskID)` and wait until the running task reaches a terminal state (finished/cancelled/failed) before starting a new import
  2. Call `CancelObsidianVaultTask(taskID)` on the active task, then start the new import
  3. Avoid duplicate submissions in the UI (disable the button while a task is running)
  4. If a task appears permanently stuck, restart the kernel to clear the in-memory task registry

Example fix

// before
startImport() // second call -> Conf.Language(329)
// after
task, err := GetObsidianVaultTask(activeTaskID)
if err == nil && !isObsidianTerminalState(task.State) {
    CancelObsidianVaultTask(activeTaskID) // wait for cancellation
}
startImport()
Defensive patterns

Strategy: try-catch

Try / catch

try {
  const task = await importObsidianVault(p)
} catch (e) {
  if (e.code === 329) {
    await waitForTerminalState(activeTaskID) // poll GetObsidianVaultTask
    await importObsidianVault(p) // retry once the active task finished/cancelled
  }
}

Prevention

When it happens

Trigger: Calling the Obsidian import API while another import task is in a non-terminal, non-pre-import state — e.g. double-clicking the import button, or a plugin/script starting a second import before the first finishes or is cancelled.

Common situations: User retries impatiently during a long vault conversion; two clients (desktop + API call) start imports concurrently; a previous task is hung in the importing state and blocks new ones.

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/ef0c0c814404ad19. Report an issue: GitHub.