phacility/phabricator · error · PhabricatorInvalidQueryCursorException
Cursor "%s" does not identify a valid object in query "%s".
Error message
Cursor "%s" does not identify a valid object in query "%s".
What it means
Thrown as PhabricatorInvalidQueryCursorException by cursor-paged policy queries when an external cursor (the 'after' paging token — usually an object ID or PHID) cannot be resolved. The engine re-executes the query constrained to that single object: this both validates the object exists and enforces that the viewer may see it (using an invisible object as a cursor would leak information). If executeOne() returns nothing, the cursor is invalid for this viewer/query combination.
Source
Thrown at src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php:100
// like a cursor this parent query would generate.
$query->setOrderVector($this->getOrderVector());
$this->applyExternalCursorConstraintsToQuery($query, $cursor);
// If we have a Ferret fulltext query, copy it to the subquery so that we
// generate ranking columns appropriately, and compute the correct object
// ranking score for the current query.
if ($this->ferretEngine) {
$query->withFerretConstraint($this->ferretEngine, $this->ferretTokens);
}
// We're executing the subquery normally to make sure the viewer can
// actually see the object, and that it's a completely valid object which
// passes all filtering and policy checks. You aren't allowed to use an
// object you can't see as a cursor, since this can leak information.
$result = $query->executeOne();
if (!$result) {
$this->throwCursorException(
pht(
'Cursor "%s" does not identify a valid object in query "%s".',
$cursor,
get_class($this)));
}
// Now that we made sure the viewer can actually see the object the
// external cursor identifies, return the internal cursor the query
// generated as a side effect while loading the object.
return $query->getInternalCursorObject();
}
final protected function throwCursorException($message) {
throw new PhabricatorInvalidQueryCursorException($message);
}
protected function applyExternalCursorConstraintsToQuery(
PhabricatorCursorPagedPolicyAwareQuery $subquery,View on GitHub (pinned to 5720a38cfe)
Solutions
- Restart pagination from the first page to get a fresh cursor
- Make sure the 'after' token comes from the immediately preceding page of the same query with the same filters and the same viewer
- If writing an API consumer, treat this exception as 'cursor expired' and transparently re-issue the query from page one
Example fix
// before: blindly paging with a saved cursor
$results = $query->setAfterCursor($saved_cursor)->execute();
// after: recover from an expired/invalid cursor by restarting
try {
$results = $query->setAfterCursor($cursor)->execute();
} catch (PhabricatorInvalidQueryCursorException $ex) {
$results = $query->setAfterCursor(null)->execute(); // page 1 again
} Defensive patterns
Strategy: try-catch
Validate before calling
// Optional pre-check: verify the cursor object is visible before paging.
if ($cursor) {
$visible = (bool)id(clone $query)
->setAfterCursor(null)
->withPHIDs(array($cursor))
->execute();
if (!$visible) { $cursor = null; } // restart from page 1
} Try / catch
try {
$results = $query->setAfterCursor($cursor)->execute();
} catch (PhabricatorInvalidQueryCursorException $ex) {
// cursor expired/invalid: restart pagination from the first page
$results = $query->setAfterCursor(null)->execute();
} Prevention
- Always take the 'after' token from the immediately previous page of the same query, same filters, same viewer
- Treat saved cursors as short-lived in API clients; handle the restart case explicitly
- Do not construct cursors by hand — use getNextCursor() / results of the executed query
When it happens
Trigger: Passing after=<PHID> for an object the viewer cannot see under policy filtering; a cursor referencing a since-deleted object; a cursor from a different query (whose filters exclude the object); stale paging links after the underlying data changed.
Common situations: A user leaves a browser tab open on page N of results, the objects on that page are deleted or restricted, and they click 'next page'; API consumers caching 'after' tokens across query modifications or long time spans.
Related errors
- Maximum page size for Conduit API method calls is 100, but t
- Minimum page size for API searches is 1, but this call speci
- Unable to load file ("%s") for import.
- ERR-INVALID-AUTH
- To search for commits which are ancestors of particular refs
AI-assisted analysis of phacility/phabricator@5720a38cfe (2026-08-21).
Data as JSON: /api/errors/e2a5bbca8db6a718.
Report an issue: GitHub.