library: keep edited skill copies when hashing fails (#916)
Both folders can hash to an empty string after read failures. Treating those hashes as equal makes relink delete the agent's copy without a backup. Require a nonempty matching hash so an unreadable copy takes the existing backup path. Cover switching from copies to links with unreadable files in both folders and edited notes in the agent's copy.
This commit is contained in:
1 parent
c5b57b9077
commit
20bdf1e992
2 files changed
+46
-2
No files matched your search
@@ -4,6 +4,7 @@ import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -140,3 +141,42 @@ func TestSkillHow(t *testing.T) {
|
||||
t.Error("the user's own pdf went with the library's")
|
||||
}
|
||||
}
|
||||
|
||||
// Two failed hashes don't prove a copy is unchanged: switching back to a
|
||||
// link must keep the agent's edits even when neither folder can be read.
|
||||
func TestSkillRelinkKeepsUnreadableCopy(t *testing.T) {
|
||||
if runtime.GOOS == "windows" || os.Geteuid() == 0 {
|
||||
t.Skip("requires Unix read permissions without root")
|
||||
}
|
||||
h := sandbox(t)
|
||||
src := filepath.Join(h, "src/skills")
|
||||
skill(t, filepath.Join(src, "pdf"), "pdf", "Read PDFs")
|
||||
write(t, filepath.Join(src, "pdf/blocked.txt"), "unchanged\n")
|
||||
ok(t)(InstallSkills(src, []string{"pdf"}, []string{"claude"}))
|
||||
ok(t)(SetSkillHow("", HowCopy))
|
||||
p := filepath.Join(h, ".claude/skills/pdf")
|
||||
write(t, filepath.Join(p, "notes.md"), "my notes\n")
|
||||
for _, dir := range []string{filepath.Join(src, "pdf"), p} {
|
||||
if err := os.Chmod(filepath.Join(dir, "blocked.txt"), 0); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if hashDir(dir) != "" {
|
||||
t.Fatal("the unreadable file didn't prevent hashing", dir)
|
||||
}
|
||||
}
|
||||
|
||||
ok(t)(SetSkillHow("", HowLink))
|
||||
if !isLink(t, p) {
|
||||
t.Fatal("claude's copy wasn't replaced by a link")
|
||||
}
|
||||
var kept []string
|
||||
filepath.WalkDir(BackupDir(), func(p string, d fs.DirEntry, err error) error {
|
||||
if err == nil && d.Name() == "notes.md" {
|
||||
kept = append(kept, p)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if len(kept) != 1 || read(t, kept[0]) != "my notes\n" {
|
||||
t.Fatalf("the unreadable copy's edits weren't kept: %v", kept)
|
||||
}
|
||||
}
|
||||
@@ -254,8 +254,12 @@ func isDigits(s string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// fresh is whether the copy at p holds what the library's skill does.
|
||||
func fresh(p, name string) bool { return hashDir(p) == hashDir(realDir(skillDir(name))) }
|
||||
// fresh is whether both folders could be read and the copy at p holds
|
||||
// what the library's skill does. Failed hashes don't prove it can be discarded.
|
||||
func fresh(p, name string) bool {
|
||||
h := hashDir(p)
|
||||
return h != "" && h == hashDir(realDir(skillDir(name)))
|
||||
}
|
||||
|
||||
func unlink(p string) error {
|
||||
fi, err := os.Lstat(p)
|
||||
|
||||
Reference in new issue
Block a user