From 16d20b817833e75b29e77a54eb4a95614c140b1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=89=E6=BE=9C?= Date: Fri, 14 Aug 2026 20:07:33 +0800 Subject: [PATCH] fix(skills): compare Windows publication identity stably --- internal/upgrade/skill_publication.go | 9 +++------ internal/upgrade/skill_publication_identity_darwin.go | 4 ++++ internal/upgrade/skill_publication_identity_linux.go | 4 ++++ internal/upgrade/skill_publication_identity_other.go | 4 ++++ internal/upgrade/skill_publication_identity_windows.go | 4 ++++ 5 files changed, 19 insertions(+), 6 deletions(-) diff --git a/internal/upgrade/skill_publication.go b/internal/upgrade/skill_publication.go index d2fc7921..0bd9ecdb 100644 --- a/internal/upgrade/skill_publication.go +++ b/internal/upgrade/skill_publication.go @@ -42,12 +42,9 @@ func PublishSkillPathNoReplace(staged, destination string) (SkillPathPublication if err != nil { return SkillPathPublication{}, fmt.Errorf("确认已发布 Skill 内容失败 %s(对象保留): %w", destination, err) } - // Force os.SameFile to resolve and cache any path-backed identity (notably - // on Windows) while the just-published object is known to occupy the path. - if !os.SameFile(identity, publishedIdentity) || publishedFingerprint != fingerprint { + if !skillPathSameFileIdentity(identity, publishedIdentity) || publishedFingerprint != fingerprint { return SkillPathPublication{}, fmt.Errorf("确认已发布 Skill 身份失败 %s(对象保留): staging 身份已变化", destination) } - _ = os.SameFile(publishedIdentity, publishedIdentity) return SkillPathPublication{ Destination: destination, fingerprint: fingerprint, @@ -96,7 +93,7 @@ func rollbackSkillPathPublication(publication SkillPathPublication) (err error) cleanupRoot(), ) } - if publication.identity == nil || !os.SameFile(publication.identity, liveIdentity) || + if publication.identity == nil || !skillPathSameFileIdentity(publication.identity, liveIdentity) || publication.incarnation != skillPathFileIncarnation(liveIdentity) || liveFingerprint != publication.fingerprint { return errors.Join( @@ -116,7 +113,7 @@ func rollbackSkillPathPublication(publication SkillPathPublication) (err error) actualIdentity, identityErr := skillPathLstat(quarantine) actual, fingerprintErr := fingerprintSkillPath(quarantine) if identityErr == nil && fingerprintErr == nil && - os.SameFile(publication.identity, actualIdentity) && + skillPathSameFileIdentity(publication.identity, actualIdentity) && actual == publication.fingerprint { if removeErr := removePublishedSkillSource(quarantine); removeErr != nil { return fmt.Errorf("移除事务发布的 Skill 失败 %s(隔离于 %s): %w", destination, quarantine, removeErr) diff --git a/internal/upgrade/skill_publication_identity_darwin.go b/internal/upgrade/skill_publication_identity_darwin.go index b436ac6e..a3547e4c 100644 --- a/internal/upgrade/skill_publication_identity_darwin.go +++ b/internal/upgrade/skill_publication_identity_darwin.go @@ -16,3 +16,7 @@ func skillPathFileIncarnation(info os.FileInfo) string { return fmt.Sprintf("%d:%d:%d:%d:%d:%d:%d", stat.Dev, stat.Ino, stat.Gen, stat.Ctimespec.Sec, stat.Ctimespec.Nsec, stat.Birthtimespec.Sec, stat.Birthtimespec.Nsec) } + +func skillPathSameFileIdentity(left, right os.FileInfo) bool { + return os.SameFile(left, right) +} diff --git a/internal/upgrade/skill_publication_identity_linux.go b/internal/upgrade/skill_publication_identity_linux.go index 0f8a52fb..cb62e1e1 100644 --- a/internal/upgrade/skill_publication_identity_linux.go +++ b/internal/upgrade/skill_publication_identity_linux.go @@ -15,3 +15,7 @@ func skillPathFileIncarnation(info os.FileInfo) string { } return fmt.Sprintf("%d:%d:%d:%d", stat.Dev, stat.Ino, stat.Ctim.Sec, stat.Ctim.Nsec) } + +func skillPathSameFileIdentity(left, right os.FileInfo) bool { + return os.SameFile(left, right) +} diff --git a/internal/upgrade/skill_publication_identity_other.go b/internal/upgrade/skill_publication_identity_other.go index fcdee1a2..53c0bc86 100644 --- a/internal/upgrade/skill_publication_identity_other.go +++ b/internal/upgrade/skill_publication_identity_other.go @@ -10,3 +10,7 @@ import ( func skillPathFileIncarnation(info os.FileInfo) string { return fmt.Sprintf("%d:%d:%s", info.ModTime().UnixNano(), info.Size(), info.Mode()) } + +func skillPathSameFileIdentity(left, right os.FileInfo) bool { + return os.SameFile(left, right) +} diff --git a/internal/upgrade/skill_publication_identity_windows.go b/internal/upgrade/skill_publication_identity_windows.go index 046327ef..518771c5 100644 --- a/internal/upgrade/skill_publication_identity_windows.go +++ b/internal/upgrade/skill_publication_identity_windows.go @@ -15,3 +15,7 @@ func skillPathFileIncarnation(info os.FileInfo) string { } return fmt.Sprintf("%d:%d", stat.CreationTime.HighDateTime, stat.CreationTime.LowDateTime) } + +func skillPathSameFileIdentity(left, right os.FileInfo) bool { + return skillPathFileIncarnation(left) == skillPathFileIncarnation(right) +}