When deleted, the repo still shows in explore,... #200

Closed
opened 2026-09-08 15:19:36 +00:00 by crueber · 3 comments
Owner

https://hub.packden.us/crueber/walhub

Check it out and see why that is still there with a loading label. It needs to be completely removed when deletions happen, such that I can import it again if I want.

https://hub.packden.us/crueber/walhub Check it out and see why that is still there with a loading label. It needs to be completely removed when deletions happen, such that I can import it again if I want.
Author
Owner

Fixed by PR #202 (branch fix/issue-200, not merged). Root cause is THREE stacked layers: (1) backend: raw prefix listing resurrected deletes — filesystem CAS .lock sidecars persist by design and keep the name behind ListPrefixes (reproduced live; your crueber/walhub ghost matches this exactly); listings are now manifest-gated. (2) browser HTTP cache: SWR resurrected deletes up to 60s — SDK listings now bypass it. (3) app cache: delete/import invalidate listing keys; repo shell 404s render not-found. Verified end to end on a scratch server (UI delete → explore empty immediately, re-create → 201 + row back, both themes). Note: your :8080 stack's existing ghosts (crueber/walhub, e2e/demo2-fork, o/r-fork) will vanish on upgrade with no migration needed.

Fixed by PR #202 (branch fix/issue-200, not merged). Root cause is THREE stacked layers: (1) backend: raw prefix listing resurrected deletes — filesystem CAS .lock sidecars persist by design and keep the name behind ListPrefixes (reproduced live; your crueber/walhub ghost matches this exactly); listings are now manifest-gated. (2) browser HTTP cache: SWR resurrected deletes up to 60s — SDK listings now bypass it. (3) app cache: delete/import invalidate listing keys; repo shell 404s render not-found. Verified end to end on a scratch server (UI delete → explore empty immediately, re-create → 201 + row back, both themes). Note: your :8080 stack's existing ghosts (crueber/walhub, e2e/demo2-fork, o/r-fork) will vanish on upgrade with no migration needed.
Author
Owner

REVIEW PR #202 (fix/issue-200, commit 4e76fe7) — verified in scratch worktree /tmp/walhub-200 (reused, already at 4e76fe7; no new worktree created). Main worktree untouched (still clean on main apart from pre-existing untracked .opencode/). No browser drive per instructions — tests + reasoning only.

ROOT CAUSE — CONFIRMED. internal/store/filesystem.go:698-771 List skips .lock sidecars, but ListPrefixes (773-800) is a pure ReadDir subdirectory listing with no sidecar filtering. After wal.Registry.Delete (internal/wal/registry.go:271-318) sweeps every listed object, the per-key .lock flock files persist by design (filesystem_test.go:197-202 pins this), so the repo dir survives and ListPrefixes keeps returning the ghost. Memory/S3 never ghost (no sidecars), which is why this only bit filesystem users. Manifest-gating (cmd/walhub/serve.go:530-558 liveRepos) fixes it on the read path on every backend, mirroring the existing wal.refreshList precedent (registry.go:381-396, same limit-8 + fail-closed drop). Test only simulates litter with note.json (can't Put .lock via API — rejected at filesystem.go:87), acceptable stand-in since the gate only cares about manifest absence.

COST / LAW 4+6 — ACCEPTABLE, with one note. Limit 8 is a concurrency cap, not a result cap: total Heads = repos under owner, served 8-at-a-time, full sorted list returned, no truncation, so pagination semantics unchanged (endpoints were and are unpaginated full arrays; client caps MAX_OWNERS=50/MAX_REPOS_PER_OWNER=10 in web/src/lib/owners.js). Explore render pays ~2x Heads (Owners() gates per owner, then each owners.repos() re-gates) — but explore is a cold human-driven path, not a Law-6 hot path (push/refs/checkpoint untouched), and wal already pays the same scan every 30s in listingRefresher. No sequential store round trip added to any hot path; no budget assertion touched.

NO-STORE — SAFE, correctly scoped. owners/ownerRepos use writeCached with empty etag (internal/api/discovery.go:128,141) → server never emits ETag/304 here, so fetch cache:no-store (web/sdk/src/core.js:245-256) cannot break conditional flows; _dispatch 304 path untouched. Server still answers SWR; change is client-side only. Data-layer 5s TTL claim checks out (useData default DEFAULT_TTL=5000, web/src/lib/data.js:154; Owners.jsx:28,72 pass no TTL). No other callers affected.

INVALIDATION — COMPLETE for the issue path. Settings.jsx:582-586 drops owners + repos:{owner} + repo/social/activity:{full} then navigates / — explore refetches fresh. Import.jsx:66-68 drops owners + repos:{owner} on landed import (incl. 200 no-op). Minor non-blocking note: back-navigating to the deleted repo URL may briefly serve stale resolve:/tags:/latest:/policy: entries (TTL-bounded, self-heals); out of scope. Repo.jsx:470,486-503: 404 summary → null → 'repository not found' shell; loading… only while undefined — no infinite spinner; non-404 errors keep the pre-existing tray contract.

RE-IMPORT / 409 — SOUND. Exists is manifest-gated (serve.go:560-566); Delete linearizes on manifest removal first (registry.go:288), so re-create/re-import sees a clean name. Covered by TestRepoRegistryDeleteVanishesAndRecreates (serve_registry_test.go:93).

UPGRADE WITHOUT MIGRATION — TRUE. Read-path gate; old .lock litter stays on disk but is invisible to List AND ListPrefixes-gated results. Residue: ~1 tiny flock file per deleted object, no cleaner (nothing ever removes .lock sidecars) — negligible, flagging only so it's a conscious accept.

GATES — gofmt clean, go vet clean, no go.mod/go.sum/package.json changes (stdlib sort only). go test -race ./cmd/walhub/ PASS incl. 3 new TestRepoRegistry* tests. cmd/walhub cover 15.8% is pre-existing (CLI package, outside the internal/... ≥95% gate); no internal/ package touched. JS: all 48 web/test/unit/*.test.js files pass, 0 failures (run per-file; single-process glob run stalls in this env — pre-existing harness slowness, not this PR; new sdk-nostore-304.test.js 6/6 incl. the #200 pin). Docs: 07_api.md + 12_web_ui.md decision entries present and accurate.

No fixes pushed — nothing blocking found; only the two non-blocking notes above.

MERGE RECOMMENDATION: ready to merge.

REVIEW PR #202 (fix/issue-200, commit 4e76fe7) — verified in scratch worktree /tmp/walhub-200 (reused, already at 4e76fe7; no new worktree created). Main worktree untouched (still clean on main apart from pre-existing untracked .opencode/). No browser drive per instructions — tests + reasoning only. ROOT CAUSE — CONFIRMED. internal/store/filesystem.go:698-771 List skips .lock sidecars, but ListPrefixes (773-800) is a pure ReadDir subdirectory listing with no sidecar filtering. After wal.Registry.Delete (internal/wal/registry.go:271-318) sweeps every listed object, the per-key <path>.lock flock files persist by design (filesystem_test.go:197-202 pins this), so the repo dir survives and ListPrefixes keeps returning the ghost. Memory/S3 never ghost (no sidecars), which is why this only bit filesystem users. Manifest-gating (cmd/walhub/serve.go:530-558 liveRepos) fixes it on the read path on every backend, mirroring the existing wal.refreshList precedent (registry.go:381-396, same limit-8 + fail-closed drop). Test only simulates litter with note.json (can't Put .lock via API — rejected at filesystem.go:87), acceptable stand-in since the gate only cares about manifest absence. COST / LAW 4+6 — ACCEPTABLE, with one note. Limit 8 is a concurrency cap, not a result cap: total Heads = repos under owner, served 8-at-a-time, full sorted list returned, no truncation, so pagination semantics unchanged (endpoints were and are unpaginated full arrays; client caps MAX_OWNERS=50/MAX_REPOS_PER_OWNER=10 in web/src/lib/owners.js). Explore render pays ~2x Heads (Owners() gates per owner, then each owners.repos() re-gates) — but explore is a cold human-driven path, not a Law-6 hot path (push/refs/checkpoint untouched), and wal already pays the same scan every 30s in listingRefresher. No sequential store round trip added to any hot path; no budget assertion touched. NO-STORE — SAFE, correctly scoped. owners/ownerRepos use writeCached with empty etag (internal/api/discovery.go:128,141) → server never emits ETag/304 here, so fetch cache:no-store (web/sdk/src/core.js:245-256) cannot break conditional flows; _dispatch 304 path untouched. Server still answers SWR; change is client-side only. Data-layer 5s TTL claim checks out (useData default DEFAULT_TTL=5000, web/src/lib/data.js:154; Owners.jsx:28,72 pass no TTL). No other callers affected. INVALIDATION — COMPLETE for the issue path. Settings.jsx:582-586 drops owners + repos:{owner} + repo/social/activity:{full} then navigates / — explore refetches fresh. Import.jsx:66-68 drops owners + repos:{owner} on landed import (incl. 200 no-op). Minor non-blocking note: back-navigating to the deleted repo URL may briefly serve stale resolve:/tags:/latest:/policy: entries (TTL-bounded, self-heals); out of scope. Repo.jsx:470,486-503: 404 summary → null → 'repository not found' shell; loading… only while undefined — no infinite spinner; non-404 errors keep the pre-existing tray contract. RE-IMPORT / 409 — SOUND. Exists is manifest-gated (serve.go:560-566); Delete linearizes on manifest removal first (registry.go:288), so re-create/re-import sees a clean name. Covered by TestRepoRegistryDeleteVanishesAndRecreates (serve_registry_test.go:93). UPGRADE WITHOUT MIGRATION — TRUE. Read-path gate; old .lock litter stays on disk but is invisible to List AND ListPrefixes-gated results. Residue: ~1 tiny flock file per deleted object, no cleaner (nothing ever removes .lock sidecars) — negligible, flagging only so it's a conscious accept. GATES — gofmt clean, go vet clean, no go.mod/go.sum/package.json changes (stdlib sort only). go test -race ./cmd/walhub/ PASS incl. 3 new TestRepoRegistry* tests. cmd/walhub cover 15.8% is pre-existing (CLI package, outside the internal/... ≥95% gate); no internal/ package touched. JS: all 48 web/test/unit/*.test.js files pass, 0 failures (run per-file; single-process glob run stalls in this env — pre-existing harness slowness, not this PR; new sdk-nostore-304.test.js 6/6 incl. the #200 pin). Docs: 07_api.md + 12_web_ui.md decision entries present and accurate. No fixes pushed — nothing blocking found; only the two non-blocking notes above. MERGE RECOMMENDATION: ready to merge.
Author
Owner

Fixed by PR #202 (review: lock-sidecar mechanism confirmed, cost/pagination/invalidation verified; all green), merged. Closing.

Fixed by PR #202 (review: lock-sidecar mechanism confirmed, cost/pagination/invalidation verified; all green), merged. Closing.
crueber added this to the v1 milestone 2026-09-10 22:27:14 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
crueber/walhub#200
No description provided.