feat(storage): disk-usage card on dashboard #15
No reviewers
Labels
No labels
area:agents
area:dashboard
area:database
area:design
area:design-review
area:flows
area:infra
area:meta
area:security
area:sessions
area:webhook
area:workdir
security
type:bug
type:chore
type:meta
type:user-story
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
charles/claude-hooks!15
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "dev/8"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Adds a
GET /storageendpoint and a Storage card to the dashboard.Closes #8
Changes
src/main.ts:treeSize()recursive walk,getStorageStats()with 60 s cache,GET /storageroutesrc/dashboard.html: Storage card (counts + human-formatted sizes, refreshes every 60 s)src/main.test.ts: unit tests fortreeSizeandGET /storagesrc/main.ts—treeSizeusesstatinstead oflstat(line ~53)stat(child)follows symlinks. If a project repo or worktree contains a git-tracked symlink that points to a directory,treeSizewill recurse into it and count that directory’s contents as part of the tree. This inflates sizes, may traverse out-of-tree paths, and in a pathological cycle would loop forever.Git worktrees do check out symlinks faithfully from the object store, so this is a realistic code path (e.g. a monorepo that symlinks
packages/shared→../shared).Fix — replace
statwithlstatintreeSize:With
lstat, a symlink entry hasinfo.isSymbolicLink() === trueandinfo.isDirectory() === false, so it falls into theelsebranch and contributesinfo.size(the link itself, typically 0 bytes on most filesystems for the stat size of a short path). That’s the correctdu-like behaviour.src/dashboard.html—loadStorage()catch block leaves card in "loading…" state forever (line ~416)The
catchblock is empty:If the very first call to
/storagefails (service restart, network blip), the card’s inner HTML is never replaced from the initialloading…placeholder — so the user sees no data and no indication of a problem.Fix — show a brief error state on failure:
(Subsequent failures after a successful render can stay silent, since the last-good data is still displayed.)
Review: Request Changes
Acceptance criteria
All acceptance criteria from #8 are met:
GET /storagereturns the correct JSON shape (cache_clones,worktrees,sessionswithcount/bytes)treeSize)STORAGE_CACHE_TTL_MS)setInterval(loadStorage, 60_000))treeSize(known-content tempdir) andGET /storageshapeIssues
Must fix (correctness bug)
statvslstatintreeSize— see comment #4733. Usingstatfollows symlinks into directories, inflating sizes and potentially escaping the tree. Drop-in fix: changestat→lstatin the import and the call site.Should fix (UX)
loadStorageerror — see comment #4734. First-call failure leaves the card in a permanent “loading…” state with no indication of a problem.Everything else looks good
countDepth2for worktrees is correct:worktrees/<agent>/<owner>__<name>__<branch>— depth-2 children are individual worktrees, verified againstworkdir.ts.fmtBytesKB/MB/GB thresholds are correct (powers of 1024).treeSizeis solid.dbe4a2adcd625239ed27