sessions_timebox rendered with an 'h' suffix: GoTrue fails to boot on 'invalid duration' (and silently x3600 below the overflow threshold)
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
- Domain
- authentication, backend
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
- The control plane should render
sessions_timeboxin the unit it stores (seconds), or
convert it, so GoTrue receives a duration equal to the configured value. - Independently of (1),
authshould 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
awarnrather thanfatal. A config value that is only validated at boot is a value
that can sit armed for weeks.
Reproduction
- On a hosted project,
PATCH /v1/projects/{ref}/config/authwith
{"sessions_timebox": 2592000}(or set Auth → Sessions → Time-box user sessions). - Confirm
GET /v1/projects/{ref}/config/authreturnssessions_timebox: 2592000. - Trigger (or wait for) a GoTrue restart.
GET /auth/v1/healthwith anapikeyheader →503. Auth logs show thefatalline
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:
fatalcount: 19/min at 05:01:50 UTC → 0 from 05:02, firstGoTrue API startedat 05:01:54./auth/v1/health(withapikey): 503 → 200, reportingv2.196.0.- Total outage: 7 h 29 m.
- A partial
PATCHcarrying only that key changed exactly 1 of 242 config keys (diffed
against aGETcaptured 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/healthbefore 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from supabase/auth
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
-
Difficulty 1/5 Under an hour Newbie friendliness 86/100
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
Similar issues
-
optimization optimization:agents-md-curator
Difficulty 2/5 1-3 hours Newbie friendliness 86/100
githubnext/gh-aw-cao#13143 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
blinklabs-io/bursa#904 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
yanet-platform/ipfw-go#129 ·
-
bug confmap/provider/googlesecretmanagerprovider needs triage
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
open-telemetry/opentelemetry-collector-contrib#51273 · 2 comments ·
-
bug: AI Gateway client filter lists "Unknown" twice when NULL and literal Unknown clients coexist Openbug
Difficulty 2/5 1-3 hours Newbie friendliness 90/100