Skip to content

Stop the orphan sweep from deleting workspace logos - #161

Merged
ccfiel merged 1 commit into
mainfrom
fix/orphan-sweep-keeps-logo-blobs
Jul 30, 2026
Merged

Stop the orphan sweep from deleting workspace logos#161
ccfiel merged 1 commit into
mainfrom
fix/orphan-sweep-keeps-logo-blobs

Conversation

@ccfiel

@ccfiel ccfiel commented Jul 30, 2026

Copy link
Copy Markdown
Member

Summary

Uploaded images were disappearing and users had to re-upload them. The cause is the daily orphan sweep deleting live blobs.

sweepOrphanObjects lists objects/, attachments/ and workspaces/, then keeps anything present in a referenced set built from DocumentRevision.storageKey and Attachment.storageKey. Workspace logos are written through writeBlob, so their keys match ATTACHMENT_KEY_RE and the sweep enumerates them — but a logo is owned by Workspace.logoStorageKey, not by an Attachment row. Any logo older than the one-hour grace period was classified as crash/rollback residue and removed.

The read path then hid the evidence: on StorageNotFoundError it nulls logoStorageKey/logoContentType/logoSha, so the user sees an empty logo field rather than a broken image, and the DB retains no sign a logo was ever set. That is why this presents as "the upload sometimes doesn't stick" rather than as an error.

It is intermittent because the sweep is a setInterval(..., 24h) registered at boot and .unref()'d — it only ever fires on a process that stays up a full day. Deployments that restart frequently never trigger it; long-uptime ones lose logos every 24 hours.

  • Add Workspace.logoStorageKey to the sweep's referenced set, with a comment stating the invariant: every owner of a content-addressed key must be enumerated there.
  • Log a warning before the read path clears the columns, so a recurrence of this class of bug is visible in logs instead of looking like user error.
  • Add a regression test, and teach the facade unit test's fake Prisma client about the new model.

Note for the cloud fork: User.signatureStorageKey has the exact same defect and is the originally reported symptom. It is cloud-only (no apps/server/src/users/ here), so it is fixed in the matching pageden-cloud PR.

Numbers

  • Server unit: 171 / 171 ✓
  • Integration: 429 / 429 ✓
  • Typecheck: ✓
  • Lint: ✓

Verification that the test catches the bug

With the sweep change reverted and the new test alone:

× keeps a workspace logo blob — it is referenced by a column, not an Attachment row
  - true
  + false      // existsSync(logo blob) — the sweep had deleted it

Passes with the fix applied.

Test plan

  • pnpm -r typecheck
  • pnpm -r lint
  • pnpm --filter @pageden/server test
  • pnpm --filter @pageden/server test:integration
  • Manual: upload a workspace logo, run pnpm --dir apps/server exec tsx scripts/sweep-orphans.ts, and confirm the logo still renders afterward.
  • CI: e2e + CodeQL

Note on already-affected data

Logos already swept are unrecoverable — the blobs are deleted and the pointer columns nulled. Affected workspaces need one re-upload after this ships.

The daily orphan sweep built its "referenced" set from DocumentRevision
and Attachment rows only. Workspace logos are written through writeBlob,
so their keys match ATTACHMENT_KEY_RE and the sweep enumerates them --
but they are owned by Workspace.logoStorageKey, not by an Attachment row.
Any logo older than the one-hour grace period was therefore classified as
crash residue and deleted.

The read path then compounded it: on StorageNotFoundError it nulls the
logo columns, so the user sees an empty logo field rather than a broken
image, with nothing left in the DB to show one was ever uploaded. That is
why this reads as "the upload randomly does not stick" instead of an
error. Keep the self-healing clear, but log a warning first so the next
occurrence of this class of bug is visible in logs.

The sweep is a 24h setInterval registered at boot, which is why it only
bites long-uptime deployments -- boxes that redeploy daily never fire it.
@ccfiel
ccfiel merged commit 104c659 into main Jul 30, 2026
10 checks passed
@ccfiel
ccfiel deleted the fix/orphan-sweep-keeps-logo-blobs branch July 30, 2026 01:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant