Your AI agent at Elevare Digital went idle because a newly added PostgreSQL row-level security (RLS) policy filtered out every job row, making the queue appear empty. The mistake went unnoticed until jobs piled up, forcing the team to redesign how the orchestrator detects an empty queue.

The hidden blind spot

ARIA, Elevare’s autonomous AI system, polls a PostgreSQL table for pending jobs. The query succeeded, returned zero rows, and the agent fell asleep. In reality the table was full. An RLS policy limited SELECT access to a specific set of users. The orchestrator connected with a service role that lacked the bypass privilege, so the database silently stripped every row from the result set. PostgreSQL treats a filtered read the same as an empty table, so no error, warning, or failure code appeared. A healthy heartbeat from the idle agent gave no hint that anything was wrong.

How RLS turned a full queue into silence

RLS adds a predicate to each row during a SELECT. If the predicate is false, the row disappears from the result. The client sees only rows that satisfy the policy; it never knows rows were hidden. For a queue worker, an empty result set looks exactly like a genuinely empty queue. The orchestrator assumed “no rows = no work” and entered its idle loop while jobs accumulated behind the scenes.

The team discovered that a policy meant to scope reads to individual users had unintentionally caught the service role itself. Because the role lacked the special “bypass RLS” attribute, the policy applied to every query the orchestrator issued. This illustrates a classic security-vs-observability trade-off: RLS protects data from unauthorized users, but it also removes a useful failure signal for system components that rely on visibility.

The canary-check pattern

To break the reliance on a silent empty result, Elevare added a “canary” check. The new flow is:

  1. Query the pending-jobs table.
  2. If rows are returned, process them as before.
  3. If the result is empty, issue a second query against a dedicated canary row that must always exist.
  4. If the canary query returns the expected row, the queue is truly empty; log an idle heartbeat.
  5. If the canary query also returns nothing, the agent is blind; raise an immediate alert.

Now the orchestrator distinguishes three states:

  • Jobs found – normal processing.
  • No jobs, canary OK – genuine idle period.
  • No jobs, canary failed – hidden RLS block, trigger alert.

The canary table is a single row that never changes. Setting it up took about an hour, but it eliminates an entire class of silent failures.

What teams should do

If you run queue workers against PostgreSQL or a hosted service built on it (such as Supabase), follow these steps:

  • Use a service-role credential with the “bypass RLS” flag. This lets system components see all rows regardless of user-level policies.
  • Audit RLS policies for missing bypass permissions for service roles. A policy that looks correct for end users may unintentionally trap internal services.
  • Add a canary table (or an equivalent always-present row) and incorporate the canary check into the worker’s idle logic. The extra query is cheap and provides a clear safety net.

The trade-off

RLS remains a powerful tool for enforcing fine-grained data access. It prevents accidental data leaks and supports multi-tenant architectures without sprinkling application-level filters throughout the codebase. The downside is that it can hide failures from components that expect a simple “no rows” signal to mean “nothing to do”. The canary pattern does not weaken RLS; it adds a lightweight verification step that restores observability.

Takeaway

A hidden RLS policy can turn a busy queue into a silent dead end, leaving AI agents idle while work backs up. Grant service roles the proper bypass privilege and pair every empty-queue read with a canary check; teams can keep their autonomous workers honest and avoid costly blind spots.