Skip to content

fix: keep media.prefix in the schema regardless of storage env - #24

Merged
surajair merged 8 commits into
mainfrom
claude/get-assets-missing-prefix-ilj9e3
Aug 2, 2026
Merged

fix: keep media.prefix in the schema regardless of storage env#24
surajair merged 8 commits into
mainfrom
claude/get-assets-missing-prefix-ilj9e3

Conversation

@surajair

@surajair surajair commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

GET_ASSETS failed with "column media.prefix does not exist" on any
deployment whose migrations were generated without bucket credentials and
then run with them. s3Storage only injects its prefix field when a prefix
is configured, so the database schema silently depended on environment
variables — the column was never created, but every media query selected it.

Declare the field on the Media collection instead, so the schema is the same
whether or not object storage is set up. s3Storage reuses a prefix field it
finds already present, and its default now comes from getMediaStoragePrefix(),
a function so the app-specific prefix stays a runtime value rather than being
emitted as a column default (which would reintroduce the same drift).

Verified: with bucket credentials set, payload migrate:create reports no
schema changes, and the media list query that produced the error succeeds.

Co-Authored-By: Claude Opus 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01ApeAUnBTvGojDTftDGqTYu

GET_ASSETS failed with "column media.prefix does not exist" on any
deployment whose migrations were generated without bucket credentials and
then run with them. s3Storage only injects its `prefix` field when a prefix
is configured, so the database schema silently depended on environment
variables — the column was never created, but every media query selected it.

Declare the field on the Media collection instead, so the schema is the same
whether or not object storage is set up. s3Storage reuses a `prefix` field it
finds already present, and its default now comes from getMediaStoragePrefix(),
a function so the app-specific prefix stays a runtime value rather than being
emitted as a column default (which would reintroduce the same drift).

Verified: with bucket credentials set, `payload migrate:create` reports no
schema changes, and the media list query that produced the error succeeds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ApeAUnBTvGojDTftDGqTYu
@vercel

vercel Bot commented Aug 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
chaibuilder-starter Ready Ready Preview Aug 2, 2026 10:37am
chaibuilder-starter-mgd7 Ready Ready Preview Aug 2, 2026 10:37am

Request Review

The prefix is part of every stored object's path, so its shape is public.
Strip the hyphens from the derived UUID and drop its final group, leaving a
20-character hex token: still deterministic from CHAIBUILDER_APP_KEY, still
80 bits against collisions, but nothing in the path reads as a UUID and no
complete UUID is published.

Only new uploads are affected. Reads resolve the prefix from each document's
stored `prefix` value, so files already in the bucket keep resolving under
the prefix they were written with.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ApeAUnBTvGojDTftDGqTYu
The prefix was a UUID v5 of the app key under a fixed namespace constant.
That constant was a second input to a value that must never move: editing it
would leave every existing file under the old prefix while new uploads went
to a new folder, since Payload resolves reads, deletes and URLs from each
document's stored `prefix` and only falls back to the configured value.

Derive the prefix from the app key and nothing else — hyphens removed, final
group dropped, 20 hex characters. One folder per app, fixed for the life of
the app, with no constant anyone can change to split it in two. The dropped
group keeps the whole app key out of object paths.

A non-UUID app key now throws rather than being truncated into a prefix short
enough to collide with another app or to land files at the bucket root.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ApeAUnBTvGojDTftDGqTYu
Taking the leading characters of the app key made the folder name read as a
truncated app key. Drop the final group and join the remaining four in
reverse order instead:

    926e3219-b756-4b17-856b-ad17c4fe139c  ->  856b4b17b756926e3219

Still 20 hex characters, still a pure function of the app key with no other
input, still withholding the final group's 48 bits.

Validation is now shape-based rather than character-based, since dropping a
group off a value that has none would leave an empty prefix: a hyphen-less
key carrying all 32 hex characters is rejected where it previously passed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ApeAUnBTvGojDTftDGqTYu

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aims to eliminate environment-dependent drift in the media schema by ensuring a prefix field/column exists regardless of whether S3 storage is configured at migration-generation time, preventing runtime failures like column media.prefix does not exist when queries select that field.

Changes:

  • Adds a prefix field to the Media collection (hidden/read-only) with a runtime defaultValue.
  • Introduces getMediaStoragePrefix() and updates Payload’s s3Storage registration to use it.
  • Adds a SQLite migration to add media.prefix and expands integration tests around prefix derivation / configuration gating.

Reviewed changes

Copilot reviewed 7 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/int/app-storage-prefix.int.spec.ts Expands integration tests for app/media storage prefix behavior and validation.
src/utilities/getAppStoragePrefix.ts Reworks prefix derivation + adds getMediaStoragePrefix() helper.
src/payload.config.ts Switches S3 registration to use computed mediaStoragePrefix.
src/payload-types.ts Updates generated types to include Media.prefix.
src/migrations/index.ts Registers the new migration in the migration list.
src/migrations/20260802_002150_media_prefix.ts Adds migration to create the media.prefix column.
src/migrations/20260802_002150_media_prefix.json Updates the generated schema snapshot to include media.prefix.
src/collections/Media.ts Declares the prefix field on the media collection to stabilize schema.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/migrations/20260802_002150_media_prefix.ts Outdated
Comment thread src/utilities/getAppStoragePrefix.ts

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 12 changed files in this pull request and generated 1 comment.

Files not reviewed (1)
  • pnpm-lock.yaml: Generated file
Suppressed comments (2)

src/utilities/getAppStoragePrefix.ts:33

  • getAppStoragePrefix now derives the prefix by rearranging/dropping UUID groups, which changes the computed prefix for the same CHAIBUILDER_APP_KEY. Since this prefix is used as the object-storage folder, changing it will route new uploads to a different folder than existing files and can break reads/URLs for previously uploaded media.

If this is meant to be a pure schema-drift fix, the prefix derivation should remain backward-compatible (or you need an explicit migration/compat path that can still resolve existing objects under the old prefix).

  // `/setup` always writes a `randomUUID()`. Anything else is a hand-edited key,
  // and dropping a group off a value that has none would put files at the bucket
  // root or under a prefix short enough to collide — fail loudly instead.
  if (!UUID_RE.test(normalized)) {
    throw new Error('CHAIBUILDER_APP_KEY must be a UUID to compute storage prefix')
  }
  return normalized.split('-').slice(0, -1).reverse().join('')

src/chaibuilder.config.ts:29

  • This PR is described as a fix for media storage prefix/schema drift, but src/chaibuilder.config.ts also makes substantial unrelated changes (AI model catalog refactor, plugin list changes, and it implicitly depends on the chaipro 0.3.0 bump). Bundling these together makes it harder to reason about risk and rollback for the original storage-prefix fix.

Consider splitting the AI/chaipro upgrade work into a separate PR (or explicitly document why it’s required for the media prefix fix).

/**
 * The eight models the editor offers, picked for web design and front-end work.
 * Most take images too, so a screenshot or mockup can be attached to the prompt;
 * the text-only ones say so via `allowedFileTypes: []`. Each model is described
 * once and mapped to the provider's own slug — Vercel AI Gateway and OpenRouter
 * disagree on some vendor prefixes (`xai` vs `x-ai`, `zai` vs `z-ai`), so the id
 * has to follow whichever provider is wired up.
 */
const AI_MODELS = [

Comment thread src/migrations/index.ts
@surajair
surajair merged commit c8cd9b4 into main Aug 2, 2026
4 checks passed
@surajair
surajair deleted the claude/get-assets-missing-prefix-ilj9e3 branch August 2, 2026 10:40
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.

4 participants