sessions_timebox rendered with an 'h' suffix: GoTrue fails to boot on 'invalid duration' (and silently x3600 below the overflow threshold)

Open
#2,770 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
5/5
Estimated time
Over a week
Newbie friendliness
35/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Active
Tech stack
go

Research direction

Start by tracing how the Management API's sessions_timebox value reaches GoTrue configuration and how GoTrue parses it at startup. Reproduce the issue with PATCH /v1/projects/{ref}/config/auth, then verify that seconds are preserved and that an out-of-range value cannot cause an unrecoverable boot loop; confirm recovery through /auth/v1/health with an apikey.

Written by the indexing model from the issue text.

Description

Summary

On a hosted Pro project, the platform rendered the stored sessions_timebox value into
GoTrue's environment with an h suffix instead of s. The stored value is 2592000
— thirty days in seconds, which is the unit the Management API documents and returns.
The container therefore received GOTRUE_SESSIONS_TIMEBOX=2592000h, which overflows Go's
time.Duration, and GoTrue refused to boot:

level=fatal msg="Failed to load configuration: envconfig.Process: assigning
GOTRUE_SESSIONS_TIMEBOX to Timebox: converting '2592000h' to type *time.Duration.
details: time: invalid duration \"2592000h\""

This repeated roughly every 3.2 seconds — 6,539 occurrences — and every /auth/v1/*
call returned 503 upstream connect error ... delayed connect error: 111 for six hours.
Postgres and PostgREST were unaffected (GET /rest/v1/401, i.e. healthy), so this was
auth-only: no user on the project could sign in.

The value had been set on 2026-08-11 and the project ran normally for twenty days on
v2.195.0. The restart that broke it was a version upgrade: the instance came back as
v2.196.0, and that is the boot at which 2592000h first appears in the logs. GoTrue reads
its config only at process start, so whatever changed in how the platform renders this key,
nothing could surface until the next restart — which was scheduled by the platform, not by
the project owner.

That is the part worth designing against. A configuration value that is only validated at
boot is a value that can sit armed for weeks behind a green dashboard, a green config API,
and a green config-drift check, and then detonate on a maintenance window nobody chose.

The wider problem: below the overflow threshold there is no error at all

The crash is the lucky branch.

time.Duration is int64 nanoseconds, so the ceiling is (2^63−1)/1e9/3600 =
2,562,047.79 h. 2592000h exceeds it by 1.17% — which is the only reason this
project got a loud failure instead of a silent one.

Any project whose sessions_timebox is below ~2,562,047 seconds (≈29.65 days) boots
cleanly and silently applies a value multiplied by 3600:

stored (seconds) intended actually applied if rendered as h
3600 1 hour 3600h — 150 days
86400 1 day 86400h — 9.86 years
604800 7 days 604800h — 69 years
2592000 30 days overflow → crash loop

That is a security control silently disabled — sessions that should time-box do not — with
no error, no log line, and nothing in the dashboard to indicate it. Far more projects are
likely affected that way than by the crash, and by construction none of them can tell.

Expected behaviour

  1. The control plane should render sessions_timebox in the unit it stores (seconds), or
    convert it, so GoTrue receives a duration equal to the configured value.
  2. Independently of (1), auth should not be able to enter an unrecoverable boot loop on
    an out-of-range duration. Two options that would each have prevented six hours of
    downtime here: reject the value at config-write time (so the dashboard/API refuses it
    while the service is still up), or clamp to the maximum representable duration and log
    a warn rather than fatal. A config value that is only validated at boot is a value
    that can sit armed for weeks.

Reproduction

  1. On a hosted project, PATCH /v1/projects/{ref}/config/auth with
    {"sessions_timebox": 2592000} (or set Auth → Sessions → Time-box user sessions).
  2. Confirm GET /v1/projects/{ref}/config/auth returns sessions_timebox: 2592000.
  3. Trigger (or wait for) a GoTrue restart.
  4. GET /auth/v1/health with an apikey header → 503. Auth logs show the fatal line
    above, repeating.

Diagnostic note for whoever picks this up

A keyless GET /auth/v1/health returns 401 while GoTrue is completely dead — the
gateway rejects the request before it reaches the service, so the response is identical to
the healthy case. Any external uptime check that cannot set an apikey header is
structurally incapable of detecting this outage. That may be worth documenting.

Workaround

Set the value so that stored × 1 hour equals the intended duration (30 days → 720), or
set 0 to disable the time-box entirely.

Resolution on our side, and what it confirms

Setting sessions_timebox: 720 via the Management API restored the service immediately:

  • fatal count: 19/min at 05:01:50 UTC → 0 from 05:02, first GoTrue API started at 05:01:54.
  • /auth/v1/health (with apikey): 503 → 200, reporting v2.196.0.
  • Total outage: 7 h 29 m.
  • A partial PATCH carrying only that key changed exactly 1 of 242 config keys (diffed
    against a GET captured beforehand).

It also gives an observational read on the unit, without waiting 30 days: a session created
7.51 days earlier refreshed successfully 6 seconds after the service came back. GoTrue
enforces the time-box as created_at + timebox at refresh, so a 720 interpreted as seconds
(12 min) or minutes (12 h) would have terminated that session. It did not — consistent with
720h, and confirming the integer is being read as hours.

Environment

  • Hosted Supabase, Pro plan, us-west — project ref available on request / also filed via support
  • GoTrue version reported by /auth/v1/health before the outage: v2.195.0
  • First fatal: 2026-08-31 21:32:48 UTC · last successful /token: 2026-08-31 18:53:22 UTC
Dominant language
Go
Stars
2.6k
Forks
764
Avg merge
5d 3h
Merged PRs (30d)
39

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from supabase/auth

All issues in supabase/auth

Similar issues

More Go issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.