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

  1. For expiries beyond one year, convert to an absolute timestamp: 'ttl.absolute' => PhabricatorTime::getNow() + $seconds (with $seconds still checked to be future).
  2. For expiries within a year, cap the relative value at 31536000 or recompute it from the real deadline.
  3. 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

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


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