phacility/phabricator · error · Exception

Specify an absolute TTL or a relative TTL, but not both.

Error message

Specify an absolute TTL or a relative TTL, but not both.

What it means

PhabricatorFile rejects file-creation parameters that set the file expiry twice: 'ttl.absolute' (a Unix epoch timestamp) and 'ttl.relative' (seconds from now) are two mutually exclusive ways to set a TTL. If both keys hold non-null values, the parameter validation in PhabricatorFile::buildFileFromParams() throws before any storage engine is touched. Only one TTL style may be supplied per file.

Source

Thrown at src/applications/files/storage/PhabricatorFile.php:1481

        'canCDN' => 'optional bool',
        'profile' => 'optional bool',
        'format' => 'optional string|PhabricatorFileStorageFormat',
        'mime-type' => 'optional string',
        'builtin' => 'optional string',
        'storageEngines' => 'optional list<PhabricatorFileStorageEngine>',
        'chunk' => 'optional bool',
      ));

    $file_name = idx($params, 'name');
    $this->setName($file_name);

    $author_phid = idx($params, 'authorPHID');
    $this->setAuthorPHID($author_phid);

    $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.',

View on GitHub (pinned to 5720a38cfe)

Solutions

  1. Delete one of the two keys from the params array; keep only the TTL style the caller actually wants.
  2. If you merge defaults into user input, only inject the default relative TTL when 'ttl.absolute' is absent, and vice versa (unset the unused key).
  3. In a UI or API client that exposes both options, make them mutually exclusive inputs (radio buttons, or drop one field) before the request reaches the server.

Example fix

// before
$params = array(
  'ttl.absolute' => $expires_epoch,
  'ttl.relative' => 86400,
);
$file = PhabricatorFile::newFromParams($params);

// after
$params = array(
  'ttl.absolute' => $expires_epoch,
);
$file = PhabricatorFile::newFromParams($params);
Defensive patterns

Strategy: validation

Validate before calling

$has_absolute = (idx($params, 'ttl.absolute') !== null);
$has_relative = (idx($params, 'ttl.relative') !== null);
if ($has_absolute && $has_relative) {
  unset($params['ttl.relative']); // pick one policy explicitly
}
$file = PhabricatorFile::newFromParams($params);

Type guard

function paramsHaveSingleTtl(array $params): bool {
  $set = 0;
  if (idx($params, 'ttl.absolute') !== null) { $set++; }
  if (idx($params, 'ttl.relative') !== null) { $set++; }
  return $set <= 1;
}

Try / catch

try {
  $file = PhabricatorFile::newFromParams($params);
} catch (Exception $ex) {
  if (preg_match('/not both/', $ex->getMessage())) {
    unset($params['ttl.relative']);
    $file = PhabricatorFile::newFromParams($params);
  } else {
    throw $ex;
  }
}

Prevention

When it happens

Trigger: Calling PhabricatorFile::newFromParams() (or anything that funnels into buildFileFromParams: the file.upload Conduit method, PhabricatorFileUploadSource, PhabricatorFileQuery writes) with a $params array where both $params['ttl.absolute'] and $params['ttl.relative'] are set. This exact branch runs when idx($params, 'ttl.absolute') !== null AND idx($params, 'ttl.relative') !== null.

Common situations: An upload wrapper that injects a default relative TTL (e.g. ttl.relative => 86400) on top of a user-supplied absolute expiry; a refactor from absolute to relative TTL that left the old key in the params array; a Conduit client that copies every field from a form into the request and sends both ttl inputs.

Related errors


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