appwrite/appwrite · error · Appwrite\Extend\Exception

general_cursor_not_found

general_cursor_not_found

Error message

Deployment '{$deploymentId}' for the 'cursor' value not found.

What it means

The cursor value passed syntax validation, but the deployments lookup for that ID returned an empty document: the cursor deployment was deleted (retention cleanup, concurrent delete) or never existed. Cursor pagination anchors to an existing neighbor document, so the query cannot proceed.

Source

Thrown at src/Appwrite/Platform/Modules/Functions/Http/Deployments/XList.php:112

        // Set resource queries
        $queries[] = Query::equal('resourceInternalId', [$function->getSequence()]);
        $queries[] = Query::equal('resourceType', ['functions']);

        $cursor = Query::getCursorQueries($queries, false);
        $cursor = \reset($cursor);

        if ($cursor !== false) {
            $validator = new Cursor();
            if (!$validator->isValid($cursor)) {
                throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
            }

            $deploymentId = $cursor->getValue();
            $cursorDocument = $dbForProject->getDocument('deployments', $deploymentId);

            if ($cursorDocument->isEmpty()) {
                throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Deployment '{$deploymentId}' for the 'cursor' value not found.");
            }

            $cursor->setValue($cursorDocument);
        }

        $grouped = Query::groupByType($queries);
        $filterQueries = $grouped['filters'];
        $selectQueries = $grouped['selections'];

        try {
            $results = $dbForProject->find('deployments', $queries);
            $total = $includeTotal ? $dbForProject->count('deployments', $filterQueries, APP_LIMIT_COUNT) : 0;
        } catch (OrderException $e) {
            throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null.");
        } catch (QueryException $e) {
            throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
        }

View on GitHub (pinned to a1d520eea4)

Solutions

  1. Restart pagination from the first page without the cursor
  2. Always use a cursor taken from the most recent list response
  3. Raise deployments retention or complete pagination faster than cleanup runs
  4. Fall back to limit/offset paging when a stable snapshot is acceptable
Defensive patterns

Strategy: validation

Validate before calling

// Use cursors only from a list response fetched moments ago:
const page = await functions.listDeployments({ functionId, queries: [Query.limit(25)] });
const last = page.deployments.at(-1);
if (!last) return; // empty page — done
const next = await functions.listDeployments({
  functionId,
  queries: [Query.cursorAfter(last.$id), Query.limit(25)],
});

Try / catch

try {
  const res = await functions.listDeployments({ functionId, queries });
} catch (e) {
  if (e?.code === 'general_cursor_not_found') {
    // Cursor deployment was deleted — restart from page one without the cursor.
  }
  throw e;
}

Prevention

When it happens

Trigger: Cursor references a deployment removed by retention or a concurrent DELETE between two pages; cursor reused from a stale cached list response; cursor ID typo'd or from another project/function.

Common situations: Long-running pagination over deployments while builds churn and old rows are purged; resuming pagination sessions from cached state; sharing cursors across environments.

Related errors


AI-assisted analysis of appwrite/appwrite@a1d520eea4 (2026-08-18). Data as JSON: /api/errors/086cf345f8473868. Report an issue: GitHub.