Existing issues
I searched the open issues for Space deletion, directory deletion, and data loss and did not find the same problem.
What happened?
Deleting a nonexistent Space id can recursively delete a different, valid Space when the requested id matches that Space's title-derived directory name.
Example: a Space has stable id canvas-123 and title/directory AliasVictim/. DELETE /api/canvas/AliasVictim succeeds even though AliasVictim is not a Space id, then GET /api/canvas/canvas-123 returns 404 and the directory is gone.
The same fallback can resolve reserved workspace directories such as setting/, so this is a data-loss boundary rather than only an incorrect status code.
I reproduced the behavior on current PR #83 HEAD and again on its exact merge base 973c22ce, so the bug predates that PR.
Expected behavior
Deleting an absent stable Space id must return a not-found outcome and leave every existing Space and workspace-owned directory unchanged. Any orphan-cleanup path must operate only on a directory whose persisted record proves it belongs to the requested stable id.
Steps to reproduce
- Start Huabu with a disposable workspace.
- Create a Space titled
AliasVictim; record the returned stable canvas-* id.
- Confirm the Space is readable through its stable id.
- Send
DELETE /api/canvas/AliasVictim even though no Space has that stable id.
- Observe a 200 response.
- Fetch the real stable id and inspect the workspace: the request now returns 404 and
AliasVictim/ has been recursively removed.
This occurred deterministically in both tested revisions.
Root cause
DiskSpaceRepository.beginDelete() creates a CanvasStore for the unverified requested id because absent ids are allowed to enter orphan cleanup. finish() calls CanvasStore.destroy(). Its canvasRoot(requestedId) lookup calls canvasDirName(), which falls back to the requested id itself when no stable-id index entry exists. destroy() then recursively removes that fallback path.
Relevant locations:
apps/server/src/modules/storage/backends/disk/space-repository.ts (beginDelete / finish)
apps/server/src/modules/storage/backends/disk/legacy/canvas-store.ts (destroy)
apps/server/src/modules/workspace/disk/canvas-dirs.ts (canvasDirName fallback)
Regression acceptance criteria
- Deleting an absent id that matches another Space's directory/title reports not found and preserves that Space byte-for-byte.
- A request targeting
setting cannot remove workspace settings.
- Legitimate stable-id deletion still removes structured state and composed blobs.
- Any supported orphan cleanup resolves ownership from persisted stable identity rather than an arbitrary fallback directory name.
- Coverage exercises a cold directory index as well as a warm one.
System information
Source checkout on Linux 6.17.0-35-generic x86_64, Node.js v24.16.0, pnpm 10.34.3. Reproduced against PR #83 HEAD 69a6a4e4 and merge base 973c22ce using isolated temporary workspaces.
Sensitive information
This report contains only synthetic Space ids, titles, and temporary-workspace observations; no credentials or private documents are included.
— posted by OpenAI Codex (via the issue-tracker skill)
Existing issues
I searched the open issues for Space deletion, directory deletion, and data loss and did not find the same problem.
What happened?
Deleting a nonexistent Space id can recursively delete a different, valid Space when the requested id matches that Space's title-derived directory name.
Example: a Space has stable id
canvas-123and title/directoryAliasVictim/.DELETE /api/canvas/AliasVictimsucceeds even thoughAliasVictimis not a Space id, thenGET /api/canvas/canvas-123returns 404 and the directory is gone.The same fallback can resolve reserved workspace directories such as
setting/, so this is a data-loss boundary rather than only an incorrect status code.I reproduced the behavior on current PR #83 HEAD and again on its exact merge base
973c22ce, so the bug predates that PR.Expected behavior
Deleting an absent stable Space id must return a not-found outcome and leave every existing Space and workspace-owned directory unchanged. Any orphan-cleanup path must operate only on a directory whose persisted record proves it belongs to the requested stable id.
Steps to reproduce
AliasVictim; record the returned stablecanvas-*id.DELETE /api/canvas/AliasVictimeven though no Space has that stable id.AliasVictim/has been recursively removed.This occurred deterministically in both tested revisions.
Root cause
DiskSpaceRepository.beginDelete()creates aCanvasStorefor the unverified requested id because absent ids are allowed to enter orphan cleanup.finish()callsCanvasStore.destroy(). ItscanvasRoot(requestedId)lookup callscanvasDirName(), which falls back to the requested id itself when no stable-id index entry exists.destroy()then recursively removes that fallback path.Relevant locations:
apps/server/src/modules/storage/backends/disk/space-repository.ts(beginDelete/finish)apps/server/src/modules/storage/backends/disk/legacy/canvas-store.ts(destroy)apps/server/src/modules/workspace/disk/canvas-dirs.ts(canvasDirNamefallback)Regression acceptance criteria
settingcannot remove workspace settings.System information
Source checkout on Linux 6.17.0-35-generic x86_64, Node.js v24.16.0, pnpm 10.34.3. Reproduced against PR #83 HEAD
69a6a4e4and merge base973c22ceusing isolated temporary workspaces.Sensitive information
This report contains only synthetic Space ids, titles, and temporary-workspace observations; no credentials or private documents are included.
— posted by OpenAI Codex (via the issue-tracker skill)