PostgREST ilike filters: * wildcards only, never %
Date: 2026-07-16 (session 58)
Type: decision / bug fix
Commit: entirely-api 2d470c9
Problem
postgrest-py 2.28.2 sends .ilike()/.like() filter values un-encoded in the URL query string. A pattern like address=ilike.%gibson% contains a malformed percent-escape (%gi), and Supabase’s Cloudflare edge worker throws error 1101 (HTML error page) on every such request. Every direct .ilike()/.like() call with % wildcards was failing platform-wide.
.or_("col.ilike.%term%") string filters are NOT affected — those encode correctly.
Decision
Repo-wide rule: all direct .ilike()/.like() filter patterns use * as the wildcard (PostgREST’s documented URL-safe equivalent). User/LLM-supplied pattern text (Friday query filters, file search, whatsapp field-lookup, project resolution) additionally maps any embedded % → * before the call. This was already the idiom in project_id.py, media_auto_filer.py, and match_pwc_abandoned_permits.py from earlier encounters with the same bug class.
Fixed call sites (9 files)
services/supabase_read_service.py (get_project_by_title, list_records contains), services/friday_write_service.py (_resolve_project_id, 4 patterns), services/friday_query_service.py (like/ilike operator sanitizer), services/file_registry_service.py (search_files), services/xero_history_service.py (blkhse dedup fetch), routes/whatsapp.py (field-lookup), routes/portal_friday.py (address search), scripts/migrate_legacy_ppdr.py, scripts/bq10_migrate_supabase_direct.py.
All paths verified live in-container post-rebuild. Worker + scheduler restarted on the new image.
Follow-up flagged
_fetch_supabase_blkhse_project_ids (xero_history_service.py) has no pagination — returns 1,000 of 2,369 blkhse project IDs (PostgREST row cap), silently truncating the PMS dedup set. Pre-existing, spawned as a separate task.