fix(workspace): delete cached content folder on workspace delete - #830
Conversation
devsy workspace delete removed the container and the workspace's config/metadata folder, but left the cloned repo content in contexts/<ctx>/contents/<id> untouched — kept there intentionally so a later `up` on the same workspace could skip re-cloning. That's a reasonable optimization for `up --reset`, but "delete" silently leaving a full repo checkout (with potential embedded git credentials) on disk forever, with no cleanup path, is not. Have delete also remove ContentFolder, unless it's the user's own local folder mounted directly rather than a devsy-managed copy.
✅ Deploy Preview for devsydev canceled.
|
📝 WalkthroughWalkthroughWorkspace deletion now removes the workspace origin and provider-managed cached content after daemon and container cleanup. Cleanup errors are logged without failing the command. User-owned local source folders and empty content paths are preserved. ChangesWorkspace deletion cleanup
Estimated code review effort: 2 (Simple) | ~10 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
✅ Deploy Preview for images-devsy-sh canceled.
|
1 similar comment
✅ Deploy Preview for images-devsy-sh canceled.
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@cmd/internal/agentworkspace/delete.go`:
- Around line 113-118: Update removeContentFolder to resolve the filesystem
identity of both ContentFolder and Source.LocalFolder before comparing them,
treating equivalent paths and symlink aliases as the same local folder. Before
calling forceRemoveAll, validate that the resolved content path remains within
the allowed Devsy content area, and skip or reject deletion when it falls
outside that boundary.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 14a61e0d-f703-424c-ae3b-b699bbc50146
📒 Files selected for processing (2)
cmd/internal/agentworkspace/delete.gocmd/internal/agentworkspace/delete_test.go
| func removeContentFolder(workspaceInfo *provider2.AgentWorkspaceInfo) error { | ||
| if workspaceInfo.ContentFolder == "" || | ||
| workspaceInfo.ContentFolder == workspaceInfo.Workspace.Source.LocalFolder { | ||
| return nil | ||
| } | ||
| return forceRemoveAll(workspaceInfo.ContentFolder) |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Trace all assignments and consumers of ContentFolder.
rg -n -C 5 --glob '*.go' 'ContentFolder\s*:|\.ContentFolder\b|func\s+WorkspaceInfo\b' .
# Find cache-root construction and path normalization logic.
rg -n -i -C 5 --glob '*.go' 'content.*(cache|folder)|cache.*content|EvalSymlinks|SameFile|filepath\.Abs' cmd pkgRepository: devsy-org/devsy
Length of output: 50371
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== files =="
git ls-files | rg '(^cmd/internal/agentworkspace/(delete|.*workspace).*\.go$|^pkg/provider/workspace\.go$|^cmd/internal/agentworkspace/delete_test\.go$)' || true
echo
echo "== delete.go outline/source =="
ast-grep outline cmd/internal/agentworkspace/delete.go --view compact || true
sed -n '1,180p' cmd/internal/agentworkspace/delete.go | cat -n
echo
echo "== delete_test relevant source =="
sed -n '1,210p' cmd/internal/agentworkspace/delete_test.go | cat -n
echo
echo "== provider workspace relevant source =="
sed -n '130,210p' pkg/provider/workspace.go | cat -n
echo
echo "== agent resolveContentFolder full =="
sed -n '268,300p' pkg/agent/agent.go | cat -n
echo
echo "== content directory helpers =="
rg -n -C 4 --glob '*.go' 'func GetWorkspaceContentDir|GetAgentWorkspaceContentDir|AgentWorkspaceContent|contents.*workspace|workspace.*contents' pkg cmd/internal/agentworkspaceRepository: devsy-org/devsy
Length of output: 20287
Resolve filesystem ownership before deleting content folders.
removeContentFolder removes any non-empty ContentFolder that is not byte-for-byte equal to Source.LocalFolder, so equivalent paths such as /src/project/../project or symlink aliases still bypass the local-folder guard. Resolve both ContentFolder and Source.LocalFolder filesystem identity before skipping, and add a boundary check before recursive deletion so a wrong metadata value cannot delete a directory outside the Devsy content area.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@cmd/internal/agentworkspace/delete.go` around lines 113 - 118, Update
removeContentFolder to resolve the filesystem identity of both ContentFolder and
Source.LocalFolder before comparing them, treating equivalent paths and symlink
aliases as the same local folder. Before calling forceRemoveAll, validate that
the resolved content path remains within the allowed Devsy content area, and
skip or reject deletion when it falls outside that boundary.
Summary
devsy workspace deleteremoved the container and the workspace's config/metadata folder (contexts/<ctx>/workspaces/<id>), but left the cloned repo content incontexts/<ctx>/contents/<id>untouched. That's intentional caching (so a laterup --resetcan reuse it, perpkg/agent/agent.go:287-288), but plaindeletesilently leaving a full repo checkout — potentially with embedded git credentials — on disk forever, with no cleanup path, is a bug: it violates what a command named "delete" is expected to do, and there's no equivalent ofSweepOrphanWorkspaceDirsfor this directory.DeleteCmd.Runnow also removesworkspaceInfo.ContentFolder, guarded so it never touches the user's own local folder when the workspace source isLocalFolder(mirroring the existingContentFolder == Source.LocalFoldercheck inup.go'sprepareLocalWorkspace).up --reset/--recreatebehavior is unchanged — that's the existing, correct escape hatch for forcing a fresh clone onup; this PR only fixesdeletenot finishing the job.TestRemoveContentFolder_*covering: devsy-managed content gets removed, a local-folder-sourced workspace's own directory is left alone, and an emptyContentFolderis a no-op.Summary by CodeRabbit
Bug Fixes
Tests