phacility/phabricator · error · Exception
Relative TTL must be zero or more seconds, but "%s" is negat
Error message
Relative TTL must be zero or more seconds, but "%s" is negative.
What it means
A relative TTL was supplied as 'ttl.relative' (seconds from now), but the value is negative. Phabricator computes ttl = PhabricatorTime::getNow() + relative, and a negative result would create an already-expired file, which the absolute-TTL path also forbids. Only zero or positive durations are accepted.
Source
Thrown at src/applications/files/storage/PhabricatorFile.php:1496
$absolute_ttl = idx($params, 'ttl.absolute');
$relative_ttl = idx($params, 'ttl.relative');
if ($absolute_ttl !== null && $relative_ttl !== null) {
throw new Exception(
pht(
'Specify an absolute TTL or a relative TTL, but not both.'));
} else if ($absolute_ttl !== null) {
if ($absolute_ttl < PhabricatorTime::getNow()) {
throw new Exception(
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;
View on GitHub (pinned to 5720a38cfe)
Solutions
- Pass a non-negative number of seconds, e.g. 'ttl.relative' => max(0, $deadline - PhabricatorTime::getNow()).
- If the intent is 'no expiry', omit the ttl keys entirely instead of sending a sentinel like -1.
- Fix the operand order where the duration is computed; add an assertion that the deadline is in the future before computing the delta.
Example fix
// before
$params = array(
'ttl.relative' => $deadline - time(), // negative once deadline passed
);
// after
$seconds_left = $deadline - PhabricatorTime::getNow();
$params = array();
if ($seconds_left > 0) {
$params['ttl.relative'] = $seconds_left;
} Defensive patterns
Strategy: validation
Validate before calling
if (isset($params['ttl.relative'])) {
$params['ttl.relative'] = max(0, (int)$params['ttl.relative']);
} Type guard
function isNonNegativeSeconds($value): bool {
return is_int($value) && $value >= 0;
} Prevention
- Compute deltas as $deadline - now (not now - $deadline) and assert the deadline is in the future first.
- Never use -1 or other sentinels as 'no TTL' — omit the key instead.
- Clamp user-supplied retention values with max(0, ...) at the input boundary.
When it happens
Trigger: PhabricatorFile::newFromParams() with 'ttl.relative' => -3600 (or any int < 0). Typically the sign survives from a 'expires_in' vs 'expired_ago' mix-up, or from subtracting instead of adding when building the duration.
Common situations: Code that computes $ttl = $deadline - time() for a deadline that has already passed, yielding a negative remainder; swapping the operands of a subtraction ($now - $expiry instead of $expiry - $now); configuration tables storing -1 as 'no limit' that gets passed through as a TTL.
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 not be more than "%s" seconds, but TTL "%s
- 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/b9b06f3395929dcb.
Report an issue: GitHub.