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

  1. Pass a non-negative number of seconds, e.g. 'ttl.relative' => max(0, $deadline - PhabricatorTime::getNow()).
  2. If the intent is 'no expiry', omit the ttl keys entirely instead of sending a sentinel like -1.
  3. 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

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


AI-assisted analysis of phacility/phabricator@5720a38cfe (2026-08-21). Data as JSON: /api/errors/b9b06f3395929dcb. Report an issue: GitHub.