Skip to content

A quarantined avatar or banner only replaces the old one once the worker has written it out (GRYT-1664) - #274

Merged
sivert-io merged 1 commit into
mainfrom
claude/GRYT-1664-quarantine-keeps-old-avatar
Oct 5, 2026
Merged

sivert-io merged 1 commit into
mainfrom
claude/GRYT-1664-quarantine-keeps-old-avatar

Conversation

@sivert-io

Copy link
Copy Markdown
Member

Found by uploading real and broken files to a local Docker server and to a server embedded in the dev app (GRYT-1664).

Before this, a quarantined avatar or banner became the member's the moment it was uploaded. If the worker then refused it (a truncated MP4, HTML named .png, random bytes), three things went wrong:

  • The member lost the working avatar and showed a letter instead.
  • A new banner deleted the old one straight away, so there was nothing to go back to.
  • Every read of the refused file waited the full 15 seconds before answering 503.

Now:

  • applyWhenSettled waits for the worker's verdict (settleQuarantine, up to 10 minutes) and only then sets the avatar or banner. Only then is the old banner deleted. Then the member list is broadcast.
  • A refused file is deleted. The member keeps what they had.
  • Of two uploads in a row, only the newer one is applied, whichever the worker finishes first.
  • A read of a refused file answers 404 media_refused at once. getImageJobStatusForFile in src/db/sqlite/imageJobs.ts is the one database change; it's a read.
  • broadcastMembersUpdate() in socket/utils/server.ts uses the refs REST routes already use.

Tested: uploadQuarantine.test.ts has the refusal case, the newer-upload-wins case, and the existing banner and video cases updated to wait for the worker. End to end in the dev app: the bad avatar was never applied, the good one stayed, and the refused row was deleted.

What to look at:

  • The pending map is in memory. A server restart while a file is in quarantine means it's never applied, even if the worker later finishes it; the member uploads again. Setting the pointer from the worker instead would survive restarts, but then the worker writes users.
  • The upload response still says processing: true with the new id. The client shows it straight away and gets 503 until it's ready. A refusal doesn't reach the uploader yet: filed as GRYT-1667.
  • The settle loop's timers are unref'd, so they never keep a shutting-down server alive.

Review-required: src/db/sqlite/imageJobs.ts.

🤖 Generated with Claude Code

…ker has written it out (GRYT-1664)

Testing real uploads showed a refused file (a truncated MP4, HTML named .png)
became the member's avatar at once, left them with a broken picture, and made
every read wait the full 15 seconds. Now the old one stays until the worker's
copy is ready, a refused file is deleted, and a read of one answers 404 at once.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@sivert-io
sivert-io merged commit 3df2a0a into main Oct 5, 2026
6 checks passed
@sivert-io
sivert-io deleted the claude/GRYT-1664-quarantine-keeps-old-avatar branch October 5, 2026 12:14
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