siyuan-note/siyuan · error

skill changed; reload it before saving, renaming or deleting

Error message

skill changed; reload it before saving, renaming or deleting

What it means

writeManagedSkillSource uses optimistic concurrency: the caller supplies a revision hash obtained from a previous read. After writing the temp file and before the atomic rename, it recomputes the revision of the on-disk skill and fails with this error if the supplied revision is empty or stale, i.e. the skill changed on disk since it was read. This prevents silently clobbering external edits.

Solutions

  1. Re-read the skill (action "read") to get the current content and revision, re-apply your edits, then write again with the fresh revision
  2. Verify which change you want to keep before overwriting the newer on-disk content
  3. Avoid editing skill files both in SiYuan and externally at the same time

Example fix

// before
ManageSkillFiles(SkillFileRequest{Action: "write", Path: "my-skill/SKILL.md", Content: new, Revision: oldRev})
// after
cur, _ := ManageSkillFiles(SkillFileRequest{Action: "read", Path: "my-skill/SKILL.md"})
ManageSkillFiles(SkillFileRequest{Action: "write", Path: "my-skill/SKILL.md", Content: new, Revision: cur.Revision})
Defensive patterns

Strategy: retry

Validate before calling

data, err := ManageSkillFiles(SkillFileRequest{Action: "read", Path: p})
// use data.Revision for the subsequent write

Type guard

func hasRevision(req SkillFileRequest) bool { return req.Revision != "" }

Try / catch

if err := ManageSkillFiles(writeReq); err != nil && strings.Contains(err.Error(), "skill changed") {
  cur, rerr := ManageSkillFiles(SkillFileRequest{Action: "read", Path: p})
  if rerr == nil { writeReq.Revision = cur.Revision; retry... }
}

Prevention

When it happens

Trigger: ManageSkillFiles action "write" with a non-empty request.Revision (or an empty one) whose hash no longer matches managedSkillRevision of the file/directory at request.Path at replace time; also the same error raised by the pre-check in ManageSkillFiles at line 398 for write/move/remove. Any out-of-band modification between read and write triggers it.

Common situations: The user edited the skill in an external editor while the UI had it open; two browser tabs saved different versions; a sync service updated files between the client's read and write.

Understand the failure class

Background: Conflicting config options: "cannot be used together" — configuration validation errors across open-source libraries — this error's family across 162 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/3d7098bac0067a0b. Report an issue: GitHub.

Appendix: source

Thrown at kernel/util/skill_manage.go:281

	}
	closeErr := file.Close()
	if err != nil {
		return err
	}
	if closeErr != nil {
		return closeErr
	}
	if !create {
		// 临时文件落盘后重新校验,避免覆盖此期间由外部编辑器保存的内容。
		if err = checkManagedSkillPath(root, p); err != nil {
			return err
		}
		currentRevision, revisionErr := managedSkillRevision(root, p)
		if revisionErr != nil {
			return revisionErr
		}
		if revision == "" || revision != currentRevision {
			return errors.New("skill changed; reload it before saving, renaming or deleting")
		}
		if err = root.Rename(target, p); err != nil {
			return err
		}
	}
	complete = true
	return nil
}

func ManageSkillFiles(request SkillFileRequest) (SkillFileData, error) {
	skillManagementLock.Lock()
	defer skillManagementLock.Unlock()
	ret := SkillFileData{}
	switch request.Action {
	case "list", "read", "write", "create", "mkdir", "move", "remove":
	default:
		return ret, errors.New("unsupported skill operation")
	}

View on GitHub (pinned to 9f775e8a12)