Simulators
Issue and pull request search
GET /search/issues and the GraphQL search query both search the issues and pull requests the scenario holds, through one grammar. gh pr list --search and gh issue list --search send the GraphQL one. Search a repository’s pull requests by state, draft flag, label, author or date:
H='Authorization: Bearer ghp_anything'
curl -s -G "$RYSTIC_URL/search/issues" -H "$H" --data-urlencode 'q=repo:tb-owner/tb-repo is:pr is:open' | jq -c '{total_count, numbers: [.items[].number]}'
# -> {"total_count":4,"numbers":[44,43,42,41]}
curl -s -G "$RYSTIC_URL/search/issues" -H "$H" --data-urlencode 'q=repo:tb-owner/tb-repo is:pr draft:true' | jq -c '{total_count, numbers: [.items[].number]}'
# -> {"total_count":1,"numbers":[43]}
curl -s -G "$RYSTIC_URL/search/issues" -H "$H" --data-urlencode 'q=repo:tb-owner/tb-repo is:pr label:bug' | jq -c '{total_count, numbers: [.items[].number]}'
# -> {"total_count":1,"numbers":[41]}
curl -s -G "$RYSTIC_URL/search/issues" -H "$H" --data-urlencode 'q=repo:tb-owner/tb-repo retry created:2026-01-01..2026-01-31' | jq -c '{total_count, numbers: [.items[].number]}'
# -> {"total_count":1,"numbers":[42]}
curl -s -G "$RYSTIC_URL/search/issues" -H "$H" --data-urlencode 'q=repo:tb-owner/tb-repo is:pr created:garbage' -o /dev/null -w '%{http_code}\n'
# -> 422
curl -s -G "$RYSTIC_URL/search/issues" -H "$H" --data-urlencode 'q=repo:tb-owner/tb-repo is:pr reactions:>1' -D - -o /dev/null | grep -i x-rystic-marker
# -> X-Rystic-Marker: unmodeled_guard; iv=iv_op_get_search_issues; guard=g_qualifiers_modelled
Qualifiers
repo:, user:, org: and owner: scope the query, and a query naming none matches nothing. The grammar reads these other qualifiers:
| Kind | Qualifiers |
|---|---|
| Family and state | is:pr, is:pull-request, is:issue, type:, state:, reason:completed, reason:"not planned", draft:true, draft:false, archived:, language: and is: with open, closed, merged, unmerged, draft, locked, unlocked, public or private |
| People | author:, assignee:, review-requested:, reviewed-by:, commenter:, mentions:, involves:, each with @me as the caller |
| Content | label:, milestone:, no:label, no:assignee, no:milestone, head: and base: (a branch-name prefix), comments: (a count or range), review:none, review:approved, review:changes_requested |
| Dates | created:, updated:, closed:, merged: |
A date is YYYY-MM-DD, covering its whole UTC day, or an ISO 8601 timestamp. a..b is inclusive, and * leaves an end open. >=, >, <= and < bound one side.
Combining
Terms AND, and - negates any of them. label:a,b ORs its labels. The advanced syntax is what gh sends as type: ISSUE_ADVANCED, and what REST takes with advanced_search=true. The two syntaxes read a query differently:
| Query | Default syntax | Advanced syntax |
|---|---|---|
a repeated author: or other person qualifier | ORs | ANDs |
a repeated label: | ANDs | ANDs |
AND and OR | join only free text | operators |
| parentheses | a bare one matches nothing | operators |
Under the advanced syntax, -( … ) matches nothing.
Free text
Words match title, body and comments, case-insensitively, or the fields in:title, in:body and in:comments name. A word in the record also matches the query word one suffix shorter, so fix finds “fixes” and “fixed”, but fixes does not find “fix” and there is no prefix match. A quoted phrase matches its words in order. A key the grammar does not know, like foo:bar, is text.
Differs from GitHub: free-text matches come within a few pull requests of GitHub’s over the imported scenario.
Refusals
Over REST, an unrecognized date, a login or repository the scenario does not hold or operators with no text return GitHub’s 422. Over GraphQL each matches nothing.
Differs from GitHub: a qualifier GitHub accepts that the simulator cannot evaluate is refused rather than ignored. Such qualifiers include review:required, reactions:, project: and linked:.
| Over | The refusal |
|---|---|
| REST | 501, with X-Rystic-Marker: unmodeled_guard; iv=iv_op_get_search_issues; guard=g_qualifiers_modelled and GitHub’s error envelope (message, documentation_url, status). The body does not name the qualifier. |
| GraphQL | an error naming g_qualifiers_modelled, not the qualifier |
Results
total_count and issueCount count every match. Over REST, per_page (at most 100) and page window the items, with GitHub’s Link header. sort and order, or an in-query sort:created-asc, order them, newest first by default. A pull request’s item carries draft and its own node_id.
Differs from GitHub: a write is searchable at once. GitHub’s index lags by seconds.
Differs from GitHub: closed: and comments: follow the record. GitHub’s index disagrees with its own records: closed: misses pull requests closed in bulk, and comments: undercounts.
Other search types
Differs from GitHub: code and commit search (GET /search/code and GET /search/commits) are not served, and will not be. REST returns GitHub’s 404 Not Found, and GraphQL undefinedField.
Differs from GitHub: GraphQL search with a type other than ISSUE or ISSUE_ADVANCED is refused with an error naming g_search_type_served. That covers ISSUE_SEMANTIC, ISSUE_HYBRID, DISCUSSION, REPOSITORY and USER.
Package and wiki search are not served.