--- status: resolved updated: 2026-09-23T08:52:00Z --- # DEBUG: Avatar upload 500 on assembled app **Discovered:** 2026-09-23 during `$gsd-verify-work 7` **Status:** resolved **Goal:** find_root_cause_only ## Symptoms - POST `/_user/api/v1/avatar` with a JPEG multipart against `app.Handler` returns `500 {"error":true,"message":"Internal server error"}` - Profile update, marketing consent, and change-password on the same handler succeed - `TestUploadAvatar` / `TestRemoveAvatar` pass ## Reproduction 1. Boot `app.Handler` with parity `testConfig` (app.yaml + http.yaml + JWT secret) 2. Register, then `curl -F avatar=@tiny.jpg` 3. Observe 500 ## Root Cause Phase 5 shipped `attach.OpenBucket` / `attach.Publish` but never called them from serve. Phase 5 verification recorded that as info because there was no HTTP upload yet. Phase 7 added `UploadAvatar`, which does `app.Lookup[*blob.Bucket]()` and `writeOpaque500` when the lookup fails. Neither boot path publishes the bucket: - `fonoteka.go/app/app.go` `Handler` — `lagoon.Publish` then `party.Activate` then `surf.Assemble` - `surf.ServeCommand` — `lagoon.OpenFromApp` / `lagoon.Publish` then `Assemble` Unit tests call `publishAvatarBucket` (memblob) themselves. The ported parity fixture is `avatar-missing` (JSON `{}` → 422), so `TestParityCorpus` never uploads a file. ## Suggested Fix 1. Open and publish the bucket next to `lagoon.Publish` in `ServeCommand` and `app.Handler`. Empty `storage.uploads.bucket_url` already fails loud. 2. Set `storage.uploads.bucket_url=mem://` on assembled-test configs. 3. Add a Handler-level multipart upload test so this cannot regress behind the 422 fixture. ## Resolution 07-08 published the bucket on both boot paths. `TestAvatarAssembled` POSTs a JPEG through `app.Handler` (200, `has_avatar`, `avatar_url`) then remove.