siyuan-note/siyuan · error

unmarshal master password migration failed: %w

Error message

unmarshal master password migration failed: %w

What it means

The migration file was read successfully but gulu.JSON.UnmarshalJSON could not decode it into masterPasswordMigration. This means the file content is not valid JSON or does not match the expected schema, and the kernel refuses to guess at partially valid migration state.

Source

Thrown at kernel/model/crypto.go:566

func readMasterPasswordMigration() (*masterPasswordMigration, error) {
	masterPasswordMigrationMu.Lock()
	defer masterPasswordMigrationMu.Unlock()
	return readMasterPasswordMigrationUnlocked()
}

func readMasterPasswordMigrationUnlocked() (*masterPasswordMigration, error) {
	p := masterPasswordMigrationPath()
	if !filelock.IsExist(p) {
		return nil, nil
	}
	data, err := filelock.ReadFile(p)
	if err != nil {
		return nil, fmt.Errorf("read master password migration failed: %w", err)
	}
	var m masterPasswordMigration
	if err = gulu.JSON.UnmarshalJSON(data, &m); err != nil {
		return nil, fmt.Errorf("unmarshal master password migration failed: %w", err)
	}
	return &m, nil
}

func removeMasterPasswordMigration() {
	masterPasswordMigrationMu.Lock()
	defer masterPasswordMigrationMu.Unlock()
	removeMasterPasswordMigrationUnlocked()
}

func removeMasterPasswordMigrationUnlocked() {
	p := masterPasswordMigrationPath()
	if err := filelock.Remove(p); err != nil && !os.IsNotExist(err) {
		logging.LogErrorf("remove master password migration failed: %s", err)
	}
}

func removeMasterPasswordMigrationBox(boxID string) {

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Inspect the file content and fix or remove the malformed migration JSON
  2. Delete the corrupt file and re-run the master password migration from a clean state
  3. Restore the migration file from a known-good backup of the workspace data directory
  4. Check the wrapped unmarshal error message for the exact offset/syntax problem before editing

Example fix

// before: truncated file content
{"masterSalt": "abc" // cut off
// after: valid complete JSON or remove and re-migrate
{"masterSalt":"abc","wrappedDek":"...","unlocked":true}
m, err := readMasterPasswordMigration()
Defensive patterns

Strategy: fallback

Validate before calling

var probe map[string]any
if err := json.Unmarshal(raw, &probe); err != nil {
    return fmt.Errorf("migration file corrupt: %w", err)
}
for _, k := range []string{"masterSalt", "wrappedDek"} {
    if _, ok := probe[k]; !ok {
        return fmt.Errorf("migration file missing key %q", k)
    }
}

Try / catch

m, err := readMasterPasswordMigration()
if err != nil {
    // fall back: quarantine the corrupt file and re-run migration
    os.Rename(masterPasswordMigrationPath(), masterPasswordMigrationPath()+".corrupt")
    return fmt.Errorf("migration state unreadable, re-run migration: %w", err)
}

Prevention

When it happens

Trigger: readMasterPasswordMigrationUnlocked parses the file bytes and UnmarshalJSON returns an error — empty file, truncated write from a previous crash, hand-edited JSON, or a file written by an incompatible schema.

Common situations: A crash or power loss during a previous write left a partial JSON file, manual editing broke the structure, or restoring a migration file from a different SiYuan version with a different schema.

Understand the failure class

Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.

Related errors


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