{"record":{"id":"e2a5bbca8db6a718","repo":"phacility/phabricator","slug":"cursor-s-does-not-identify-a-valid-object-in-qu","errorCode":null,"errorMessage":"Cursor \"%s\" does not identify a valid object in query \"%s\".","messagePattern":"Cursor \"(.+?)\" does not identify a valid object in query \"(.+?)\"\\.","errorType":"validation","errorClass":"PhabricatorInvalidQueryCursorException","httpStatus":null,"severity":"error","filePath":"src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php","lineNumber":100,"sourceCode":"    // like a cursor this parent query would generate.\n    $query->setOrderVector($this->getOrderVector());\n\n    $this->applyExternalCursorConstraintsToQuery($query, $cursor);\n\n    // If we have a Ferret fulltext query, copy it to the subquery so that we\n    // generate ranking columns appropriately, and compute the correct object\n    // ranking score for the current query.\n    if ($this->ferretEngine) {\n      $query->withFerretConstraint($this->ferretEngine, $this->ferretTokens);\n    }\n\n    // We're executing the subquery normally to make sure the viewer can\n    // actually see the object, and that it's a completely valid object which\n    // passes all filtering and policy checks. You aren't allowed to use an\n    // object you can't see as a cursor, since this can leak information.\n    $result = $query->executeOne();\n    if (!$result) {\n      $this->throwCursorException(\n        pht(\n          'Cursor \"%s\" does not identify a valid object in query \"%s\".',\n          $cursor,\n          get_class($this)));\n    }\n\n    // Now that we made sure the viewer can actually see the object the\n    // external cursor identifies, return the internal cursor the query\n    // generated as a side effect while loading the object.\n    return $query->getInternalCursorObject();\n  }\n\n  final protected function throwCursorException($message) {\n    throw new PhabricatorInvalidQueryCursorException($message);\n  }\n\n  protected function applyExternalCursorConstraintsToQuery(\n    PhabricatorCursorPagedPolicyAwareQuery $subquery,","sourceCodeStart":82,"sourceCodeEnd":118,"githubUrl":"https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php#L82-L118","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before: blindly paging with a saved cursor\n$results = $query->setAfterCursor($saved_cursor)->execute();\n\n// after: recover from an expired/invalid cursor by restarting\ntry {\n  $results = $query->setAfterCursor($cursor)->execute();\n} catch (PhabricatorInvalidQueryCursorException $ex) {\n  $results = $query->setAfterCursor(null)->execute(); // page 1 again\n}","handlingStrategy":"try-catch","validationCode":"// Optional pre-check: verify the cursor object is visible before paging.\nif ($cursor) {\n  $visible = (bool)id(clone $query)\n    ->setAfterCursor(null)\n    ->withPHIDs(array($cursor))\n    ->execute();\n  if (!$visible) { $cursor = null; } // restart from page 1\n}","typeGuard":null,"tryCatchPattern":"try {\n  $results = $query->setAfterCursor($cursor)->execute();\n} catch (PhabricatorInvalidQueryCursorException $ex) {\n  // cursor expired/invalid: restart pagination from the first page\n  $results = $query->setAfterCursor(null)->execute();\n}","preventionTips":["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"],"tags":["pagination","cursor","policy","search","phabricator"],"backgroundTag":"invalid-pagination-cursor","analyzedSha":"5720a38cfe95b00ca4be5016dd0d2f3195f4fa04","analyzedAt":"2026-08-21T05:07:25.672Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}