phacility/phabricator · error · Exception
Relative TTL must not be more than "%s" seconds, but TTL "%s
Error message
Relative TTL must not be more than "%s" seconds, but TTL "%s" was specified.
What it means
A relative TTL was supplied that exceeds the hard cap of phutil_units('365 days in seconds') = 31,536,000 seconds. Phabricator limits relative TTLs to one year so that callers do not silently create effectively-permanent files; anything longer must be expressed as an absolute 'ttl.absolute' epoch timestamp (still in the present or future).
Source
Thrown at src/applications/files/storage/PhabricatorFile.php:1505
pht(
'Absolute TTL must be in the present or future, but TTL "%s" '.
'is in the past.',
$absolute_ttl));
}
$this->setTtl($absolute_ttl);
} else if ($relative_ttl !== null) {
if ($relative_ttl < 0) {
throw new Exception(
pht(
'Relative TTL must be zero or more seconds, but "%s" is '.
'negative.',
$relative_ttl));
}
$max_relative = phutil_units('365 days in seconds');
if ($relative_ttl > $max_relative) {
throw new Exception(
pht(
'Relative TTL must not be more than "%s" seconds, but TTL '.
'"%s" was specified.',
$max_relative,
$relative_ttl));
}
$absolute_ttl = PhabricatorTime::getNow() + $relative_ttl;
$this->setTtl($absolute_ttl);
}
$view_policy = idx($params, 'viewPolicy');
if ($view_policy) {
$this->setViewPolicy($params['viewPolicy']);
}
$is_explicit = (idx($params, 'isExplicitUpload') ? 1 : 0);View on GitHub (pinned to 5720a38cfe)
Solutions
- For expiries beyond one year, convert to an absolute timestamp: 'ttl.absolute' => PhabricatorTime::getNow() + $seconds (with $seconds still checked to be future).
- For expiries within a year, cap the relative value at 31536000 or recompute it from the real deadline.
- Double-check the unit: the field is seconds, not minutes, hours, or milliseconds.
Example fix
// before $params = array( 'ttl.relative' => 2 * 365 * 86400, // 63,072,000 > cap ); // after $params = array( 'ttl.absolute' => PhabricatorTime::getNow() + (2 * 365 * 86400), );
Defensive patterns
Strategy: validation
Validate before calling
$max_relative = phutil_units('365 days in seconds');
if (idx($params, 'ttl.relative', 0) > $max_relative) {
// long expiries must be absolute
$params['ttl.absolute'] = PhabricatorTime::getNow() + $params['ttl.relative'];
unset($params['ttl.relative']);
} Type guard
function isWithinRelativeTtlCap($seconds): bool {
return is_int($seconds) && $seconds >= 0 && $seconds <= phutil_units('365 days in seconds');
} Prevention
- Remember the field unit is seconds and the cap is 31,536,000 (365 days).
- For multi-year retention, store a deadline and convert to 'ttl.absolute' yourself.
- Centralize TTL computation in one helper so the cap is enforced in one place.
When it happens
Trigger: PhabricatorFile::newFromParams() with 'ttl.relative' => 40000000 (or 366 days, '2 years', etc.). The check runs after the negative check, so any relative value in (31536000, INF) throws with both the cap and the offending value in the message.
Common situations: Product specs asking for retention of 18 months or 2 years implemented as relative seconds; multiplying days by the wrong factor (e.g. 365 * 24 * 60 computed as 525,600 minutes passed as if seconds); copying a milliseconds value (a year in ms is 3.15e10) into the field.
Related errors
- Specify an absolute TTL or a relative TTL, but not both.
- Absolute TTL must be in the present or future, but TTL "%s"
- Relative TTL must be zero or more seconds, but "%s" is negat
- Unable to upload file: this server is not configured with an
- Service "%s" is unrecognized, restricted, or you do not have
AI-assisted analysis of phacility/phabricator@5720a38cfe (2026-08-21).
Data as JSON: /api/errors/a629ee1ec29c2813.
Report an issue: GitHub.