jsonb shared_with bug: .contains() was serializing the wrong format

Two call sites in the backend were passing a plain JS array to PostgREST's `.contains()` on a `jsonb` column. PostgreSQL rejected every request that hit the shared-with code path with a 500 error.

workflow

PostgrestFilterBuilder.contains(column, value) serializes a JS array as a PostgreSQL array literal ({a,b}). That's correct for text[] columns. For jsonb, PostgreSQL needs a JSON array string - ["user@example.com"]. The mismatch meant every GET /projects call that hit the shared-with branch returned 500: invalid input syntax for type json.

The fix is JSON.stringify([userEmail]) at both call sites: backend/src/lib/access.ts:138 and backend/src/routes/projects.ts:34. A third call site in tabular.ts:107 was already doing this correctly - the pattern existed, these two locations just weren't following it.

Commit 653d055. One-line change in each file, no behavioral side effects beyond making the broken path return 200.

So what Worth pulling if your fork's `shared_with` column is `jsonb`. Check `database/` to confirm column type. The fix is trivial to apply and independent of anything else Lef-F has done - no need to take the docker-compose work alongside it.

View this fork on GitHub →

Spotted something wrong? Or know the PR text has fresher detail than the writeup above?

Commits in this thread

1 commit from Lef-F/mike, oldest first. Source extracted verbatim from the harvested git log.

SHA Subject Author Date
653d0550 fix(backend): use JSON-array form of contains() for jsonb shared_with Lef 2026-05-05 ↗ GitHub
commit body
PostgrestFilterBuilder's .contains(column, value) serializes a JS
array as PostgreSQL array literal '{a,b}', which is the right shape
for text[] columns but invalid input for jsonb. Two of three call
sites (projects.ts and access.ts) passed bare arrays, causing every
GET /projects request to return 500 with 'invalid input syntax for
type json' on vanilla Postgres+PostgREST. The third site
(tabular.ts:107) already uses JSON.stringify([...]) - this commit
makes the other two match.

Tested by tracing the generated PostgREST URL: bare array produces
'cs.{smoke@test.local}' (4xx), JSON.stringify produces
'cs.["smoke@test.local"]' (200).

Capture this thread into my fork

Download a single Markdown prompt that tells Claude how to port every commit above into your working tree — adapting paths and structure to match your repo. Run it via claude -p < capture-thread-40.md from inside the repo you want the changes in.

⬇ Download capture-thread-40.md