Skip to content

docs(supabase-otp-hook): scope the dead-session 503 to the option that has it - #70

Merged
rmyndharis merged 1 commit into
mainfrom
fix/supabase-otp-hook-option-b-honesty
Aug 8, 2026
Merged

docs(supabase-otp-hook): scope the dead-session 503 to the option that has it#70
rmyndharis merged 1 commit into
mainfrom
fix/supabase-otp-hook-option-b-honesty

Conversation

@rmyndharis

Copy link
Copy Markdown
Owner

The claim that was wrong

The README stated, without qualification:

A bad signature returns 401 synchronously; a dead session returns 503 synchronously (host-side preflight) — neither reaches the plugin.

That holds for Option A only.

Why Option B cannot have it

The host preflight probes the instance's sessionScope. Option B leaves that blank and puts the sending session in fallbackSessionId — plugin config the host never sees. evaluatePreflight therefore finds no single session to probe and skips the session-alive check, exactly as its own comment says ("wildcard: no single session to probe").

The route is a sync-response route, so the ack is rendered and returned before dispatch. By the time the plugin resolves the fallback and the send fails, the 200 is already committed. There is no point at which the plugin could turn it into a 503.

Why that matters here specifically

On an auth path the failure mode is the bad kind. Supabase reads 200 {"ok":true} as delivered and does not retry; the dedup row suppresses a repeat; no DLQ row and no message row are written. The only trace is the plugin's own sendText failed (background) log line.

It is conditional — it needs the Option-B choice and a fallback session with no live engine at request time — and Option A is what the README calls "Simplest". But an operator who picked Option B was told they had a guarantee they did not have.

No code changes, deliberately

Both behaviours in the chain are reasoned decisions with comments to match:

  • The host renders the ack before enqueueing, because blocking it would hold the HTTP response for up to the inline dispatch timeout.
  • The plugin sends fire-and-forget, because awaiting a slow send risks a 504 → provider retry → duplicate OTP.

Trading silent loss for duplicate OTPs on an auth path is not an improvement, and neither decision is this change's to overturn. What was wrong was the promise, not the code.

What changed

The provisioning list now says what Option B gives up, and the Security section scopes the 503 to Option A. A changelog entry records it.

…t has it

The README stated, unconditionally, that a dead session returns 503
synchronously via the host-side preflight. That holds for Option A only.

The preflight probes the *instance's* sessionScope. Option B leaves that blank
and puts the sending session in `fallbackSessionId`, which is plugin config the
host never sees — so `evaluatePreflight` finds no single session to probe and
skips the check. The route is a sync-response route, so its ack is rendered and
returned before the handler runs; by the time the plugin resolves the fallback
and the send fails, the 200 is already committed.

The consequence on an auth path is the bad kind: Supabase reads 200 {"ok":true}
as delivered and does not retry, the dedup row suppresses a repeat, and the only
trace is the plugin's own `sendText failed (background)` log line.

No code changes. The ack-before-dispatch ordering is deliberate — the comment in
the host explains that blocking it would hold the response for the dispatch
timeout — and the plugin's fire-and-forget send is deliberate too, because
awaiting a slow send risks a 504, a provider retry, and a duplicate OTP. Trading
silent loss for duplicate OTPs on an auth path is not an improvement.

What was wrong was the promise. Both the provisioning list and the Security
section now say which option carries the guarantee, and Option B says plainly
what it gives up.
@rmyndharis
rmyndharis merged commit 07b7bc1 into main Aug 8, 2026
1 check passed
@rmyndharis
rmyndharis deleted the fix/supabase-otp-hook-option-b-honesty branch August 8, 2026 06:24
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.

1 participant