Skip to content

fix(github-mcp): pass each search word to gh as its own keyword - #18

Merged
Martin Bens (SpiGAndromeda) merged 3 commits into
mainfrom
fix/search-keyword-split
Oct 3, 2026
Merged

Martin Bens (SpiGAndromeda) merged 3 commits into
mainfrom
fix/search-keyword-split

Conversation

@SpiGAndromeda

@SpiGAndromeda Martin Bens (SpiGAndromeda) commented Oct 3, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #10.

gh search quotes every keyword argument that contains whitespace and reads the text before an argument's first : as a qualifier name (formatKeywords() and quote() in cli/cli pkg/search/query.go). search, search_repos, and search_commits passed the whole search value as one argument, so every multi-word query ran as an exact phrase, and a leading qualifier swallowed the rest: is:open timeout hang became is:"open timeout hang". child_process timeout in nodejs/node returned 2 issues as a phrase and returns 74 as separate keywords.

Changes

  • _gh_split_search_terms (lib/search.sh): splits search into one argument per word. A double-quoted span stays inside its term, loses its quotes, and has its whitespace collapsed to single spaces, so gh quotes it again: "exact phrase" stays a phrase, and label:"good first issue" reaches gh as label:good first issue, which gh turns back into label:"good first issue". The split runs on read -a with an explicit IFS, so it takes linear time and does not depend on the locale. An unbalanced quote fails before gh runs, and so does a search without words for search and search_commits; search_repos then searches by its filters alone.
  • -- before the search: search, search_repos, search_commits, and search_code now put the search after --, so a search that starts with - (-label:bug crash, the code ->getId() is not read as a gh flag.
  • search_code still sends the whole search as one argument. Its description promises an exact text match, which gh's phrase quoting delivers.
  • Docs: the search parameter descriptions in tools-read.json and REFERENCE.md state the syntax: each word is its own keyword, double quotes keep a phrase together, and an unmatched quote fails the call.

Known limitation

gh's keyword formatting cannot pass three forms through, so REFERENCE.md lists them:

  • A negated phrase: -"exact phrase" becomes a phrase that starts with -. NOT "exact phrase" works.
  • A quoted single word: gh re-quotes only terms with whitespace, so "OR" becomes the operator.
  • A phrase containing :: "error: timeout" reaches GitHub as error:" timeout".

`gh search` quotes every keyword argument that contains whitespace, so search, search_repos, and search_commits, which passed the whole expression as one argument, ran every multi-word query as an exact phrase: `child_process timeout` in nodejs/node returned 2 issues instead of 74. gh also cuts an argument at its first colon, so a leading qualifier swallowed the rest: `is:open timeout hang` became `is:"open timeout hang"`.

A new _gh_split_search_terms helper splits the expression on whitespace. A double-quoted span stays in one term with its quotes removed, so gh quotes it again: `"exact phrase"` stays a phrase and `label:"good first issue"` keeps working. The terms go after `--`, so a negated qualifier such as `-label:bug` is not read as a gh flag. An unbalanced quote, or an expression with no terms, fails before gh runs.

search_code is unchanged, since its description promises an exact text match. A quoted phrase that contains a colon still does not reach GitHub as that phrase; gh has no argument form for it.

Co-Authored-By: Claude <noreply@anthropic.com>
The per-character loop in _gh_split_search_terms took quadratic time in bash: a 60 KB search took 7 s under LC_ALL=C and 17 s under a UTF-8 locale before gh ran. The splitter now uses `read -a` with an explicit IFS, first to cut the expression at its double quotes and then to split it into words, and takes 0.09 s for the same input. The explicit IFS also makes the split independent of the locale, where `[[:space:]]` matched U+00A0 under UTF-8 only.

Whitespace inside a quoted phrase now collapses to single spaces, since gh's quoting turned a tab into a literal `\t`. The split joins a phrase's words with a unit separator (U+001F) and turns it back into a space afterwards, so a search containing U+001F is rejected. search_repos accepts a search without words again and searches by its filters alone, as it did before the split was added.

The search parameter descriptions in tools-read.json and REFERENCE.md now state the syntax: each word is its own keyword, double quotes keep a phrase together, and an unmatched quote fails the call. REFERENCE.md also lists what gh cannot pass through: a negated phrase (`-"exact phrase"`, use `NOT "exact phrase"` instead), a quoted single word such as `"OR"`, and a phrase containing a colon.

Co-Authored-By: Claude <noreply@anthropic.com>
search_code put the search before its flags without `--`, so gh read a search that starts with `-`, such as the PHP code `->getId(`, as flags and failed with an unknown-flag error. The search now follows `--` as one argument, so gh still quotes it as the exact-text phrase the tool describes.

Co-Authored-By: Claude <noreply@anthropic.com>
@SpiGAndromeda
Martin Bens (SpiGAndromeda) merged commit f768ccc into main Oct 3, 2026
1 check passed
@SpiGAndromeda
Martin Bens (SpiGAndromeda) deleted the fix/search-keyword-split branch October 3, 2026 13:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

search, search_repos, and search_commits turn multi-word queries into exact-phrase searches

1 participant