fix(workspace): tolerate read-only files on delete
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user