fix(workspace): tolerate read-only files on delete

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
tao.chen
2026-07-03 14:50:37 +08:00
co-authored by Claude
parent 09150c2a7d
commit 61e5ede650
2 changed files with 41 additions and 1 deletions
+20 -1
View File
@@ -8,6 +8,25 @@ import (
"codespace/internal/util"
)
// forceRemove chmods every file and directory under root to writable before
// removing the tree. It ignores chmod and walk errors so that partial or
// concurrent removals do not abort the delete.
func forceRemove(root string) error {
_ = filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error {
if err != nil {
return nil
}
mode := os.FileMode(0o644)
if d.IsDir() {
mode = 0o755
}
_ = os.Chmod(path, mode)
return nil
})
return os.RemoveAll(root)
}
// Manager manages workspace lifecycle.
type Manager interface {
Create(id string) (*Workspace, error)
@@ -68,7 +87,7 @@ func (m *LocalManager) Delete(id string) error {
return util.New(util.CodeBadRequest, "invalid workspace id")
}
root := RootFor(m.root, id)
if err := os.RemoveAll(root); err != nil {
if err := forceRemove(root); err != nil {
return util.Wrap(util.CodeInternal, "failed to delete workspace", err)
}
return nil
+21
View File
@@ -6,6 +6,27 @@ import (
"testing"
)
func TestLocalManagerDeleteRemovesReadOnlyFiles(t *testing.T) {
mgr := NewLocalManager(t.TempDir())
ws, err := mgr.Create("readonly")
if err != nil {
t.Fatal(err)
}
readonlyFile := filepath.Join(ws.Root, "readonly.txt")
if err := os.WriteFile(readonlyFile, []byte("x"), 0o444); err != nil {
t.Fatal(err)
}
if err := mgr.Delete("readonly"); err != nil {
t.Fatalf("Delete: %v", err)
}
if _, err := os.Stat(ws.Root); !os.IsNotExist(err) {
t.Fatalf("workspace root still exists: %v", err)
}
}
func TestLocalManagerListReturnsSortedWorkspaces(t *testing.T) {
mgr := NewLocalManager(t.TempDir())
if _, err := mgr.Create("beta"); err != nil {