Skip to content

gh search code --web returns zero matches and error #11029

Description

@onnimonni

Describe the bug

Same query for gh search code should probably work both in terminal and web?

This works and returns examples:

$ gh search code --extension nix "karabiner-elements"

But when adding --web it doesn't show any results because extension doesn't work in web:

$ gh search code --extension nix "karabiner-elements" --web

Affected version

$ gh version
gh version 2.73.0 (nixpkgs)
https://github.com/cli/cli/releases/tag/v2.73.0

Steps to reproduce the behavior

  1. Type this:
gh search code --extension nix "karabiner-elements" --web
  1. It then opens this link: https://github.com/search?q=karabiner-elements+extension%3Anix&type=code
  2. Where I can see this error:
Image

Expected vs actual behavior

If the web search query would not use extension:nix but path:*.nix it would work correctly.

Activity

  1. andyfeller commented on Jun 18, 2025

    @andyfeller
    Contributor

    Thank you for taking the time to raise this problem, @onnimonni! 🙇 Totally makes sense.

    Digging into the underlying code, I see the following comment that was added in #6945 as part of #7376 when gh search code was added:

    func codeRun(opts *CodeOptions) error {
    io := opts.IO
    if opts.WebMode {
    // FIXME: convert legacy `filename` and `extension` ES qualifiers to Blackbird's `path` qualifier
    // when opening web search, otherwise the Blackbird search UI will complain.
    url := opts.Searcher.URL(opts.Query)
    if io.IsStdoutTTY() {
    fmt.Fprintf(io.ErrOut, "Opening %s in your browser.\n", text.DisplayURL(url))
    }
    return opts.Browser.Browse(url)
    }

    Technical details

    The Query type is used for searching via the API as formatting the web search URLs, however 1) it does not have the path qualifier and 2) it doesn't have a mechanism for translating certain qualifiers:

    cli/pkg/search/query.go

    Lines 28 to 93 in d9d0e14

    type Qualifiers struct {
    Archived *bool
    Assignee string
    Author string
    AuthorDate string
    AuthorEmail string
    AuthorName string
    Base string
    Closed string
    Commenter string
    Comments string
    Committer string
    CommitterDate string
    CommitterEmail string
    CommitterName string
    Created string
    Draft *bool
    Extension string
    Filename string
    Followers string
    Fork string
    Forks string
    GoodFirstIssues string
    Hash string
    Head string
    HelpWantedIssues string
    In []string
    Interactions string
    Involves string
    Is []string
    Label []string
    Language string
    License []string
    Mentions string
    Merge *bool
    Merged string
    Milestone string
    No []string
    Parent string
    Project string
    Pushed string
    Reactions string
    Repo []string
    Review string
    ReviewRequested string
    ReviewedBy string
    Size string
    Stars string
    State string
    Status string
    Team string
    TeamReviewRequested string
    Topic []string
    Topics string
    Tree string
    Type string
    Updated string
    User []string
    }
    func (q Query) String() string {
    qualifiers := formatQualifiers(q.Qualifiers)
    keywords := formatKeywords(q.Keywords)
    all := append(keywords, qualifiers...)
    return strings.TrimSpace(strings.Join(all, " "))
    }

    cli/pkg/search/query.go

    Lines 140 to 149 in d9d0e14

    func formatQualifiers(qs Qualifiers) []string {
    var all []string
    for k, vs := range qs.Map() {
    for _, v := range vs {
    all = append(all, fmt.Sprintf("%s:%s", k, quote(v)))
    }
    }
    sort.Strings(all)
    return all
    }

    I would prefer avoiding a gh search code specific workaround as it would be brittle and there might be similar situations with other searches and qualifiers.

    My initial thought might be to enhance the URL method used to transform certain qualifiers before they are formatted:

    cli/pkg/search/searcher.go

    Lines 243 to 256 in d9d0e14

    func (s searcher) URL(query Query) string {
    path := fmt.Sprintf("https://%s/search", s.host)
    qs := url.Values{}
    qs.Set("type", query.Kind)
    qs.Set("q", query.String())
    if query.Order != "" {
    qs.Set(orderKey, query.Order)
    }
    if query.Sort != "" {
    qs.Set(sortKey, query.Sort)
    }
    url := fmt.Sprintf("%s?%s", path, qs.Encode())
    return url
    }

    It appears the URL method is used exclusively for web search URLs, so the function documentation should be updated to reflect its exclusive use and that certain qualifiers are translated:

    https://github.com/search?q=repo%3Acli%2Fcli+%22Searcher.URL%28%22&type=code

  2. added
    priority-2Affects more than a few users but doesn't prevent core functions
    and removed on Jun 18, 2025
  3. andyfeller commented on Jun 18, 2025

    @andyfeller
    Contributor

    Acceptance Criteria

    1. When user runs gh search code --extension <EXT> such as gh search code --extension nix karabiner-elements
      Then the GitHub API returns results that match files with that extension such as:

      Showing 30 of 828 results
      
      nix-darwin/nix-darwin modules/services/karabiner-elements/default.nix
      	cfg = config.services.karabiner-elements;
      	options.services.karabiner-elements = {
      	enable = mkEnableOption "Karabiner-Elements";
      
      Enzime/dotfiles-nix overlays/karabiner-elements.nix
      	karabiner-elements = super.karabiner-elements.overrideAttrs (old: {
      
      rgomezcasas/dotfiles nix/_homebrew.nix
      	"karabiner-elements"
      
      ahmedelgabri/dotfiles nix/modules/shared/karabiner.nix
      	"karabiner-elements"
      
      eikster-dk/nixcfg modules/hosts/karabiner-elements/darwin.nix
      	"karabiner-elements"
    2. When user runs gh search code --web --extension <EXT> such as gh search code --web --extension nix karabiner-elements
      Then web browser is opened to GitHub global search with path:*.nix search qualifier instead of extension:nix

    3. When user runs gh search code --filename <FILENAME> such as gh search code --filename default.nix karabiner-elements
      Then the GitHub API returns results that match files with that filename such as:

      Showing 30 of 169 results
      
      nix-darwin/nix-darwin modules/services/karabiner-elements/default.nix
      	cfg = config.services.karabiner-elements;
      	options.services.karabiner-elements = {
      	enable = mkEnableOption "Karabiner-Elements";
      
      tiiuae/nixpkgs-spectrum pkgs/os-specific/darwin/karabiner-elements/default.nix
      	pname = "karabiner-elements";
      	url = "https://github.com/pqrs-org/Karabiner-Elements/releases/download/v${version}/Karabiner-Elements-${version}.dmg";
      
      abayomi185/nix-dotfiles modules/darwin/apps/karabiner-elements/default.nix
      	services.karabiner-elements = {
    4. When user runs gh search code --web --filename <FILENAME> such as gh search code --filename default.nix karabiner-elements --web
      Then web browser is opened to GitHub gloabl search with path:**/default.nix search qualifier instead of filename:default.nix

  4. samcoe commented on Jun 27, 2025

    @samcoe
    Contributor

    @andyfeller Hi 👋 I was checking out the amazing progress on gh and thought I would contribute!

    I was taking a look at this issue and had a couple questions for you.

    1. Do we need to worry about any GHES versions that do not support the path qualifier? From the docs I was unable to figure out what web search each GHES version was using.

    2. As far as implementation, what are your thoughts around introducing a --path flag instead of trying to convert the --filename and --extension flag values into a valid path qualifier string? I am envisioning that the --path flag would only be compatible with the --web flag and result in an error if not accompanied by it. If the --filename or --extension flags were used with the --web flag it would output an error saying to use the --path flag instead. The thought process here is that this new --path flag would be more powerful and reflect the web search better than any sort of conversion we can do using the --filename and --extension flag values. Additionally, it sets up nicely to deprecate the --filename and --extension flags in the future if the search API does eventually get around to matching the web search. Perhaps this would constitute a breaking change but it would only impact use cases where the --web flag is being used so maybe that is a bit of a gray area.

    Looking forward to your thoughts. I am happy to implement whichever direction you feel is best.

  5. andyfeller commented on Jun 27, 2025

    @andyfeller
    Contributor

    Thank you so much, @samcoe, as I'd love a chance to work with you again! ❤

    Do we need to worry about any GHES versions that do not support the path qualifier? From the docs I was unable to figure out what web search each GHES version was using.

    Very insightful question!

    GHES code search appears to be the same as GitHub.com legacy code search. This means that path: is available but it is only a directory path within the repo without support for wildcards or other pattern matching.

    Whereas GitHub.com has Blackbird, too. This allows more complex path searches that use regex to match files or directories. For example, repo:cli/cli path:**/*_test.go "fmt.Fprintf" finds all Go tests in cli/cli that print to files.

    So, path: is technically available everywhere; its just a question of what you can do based on whether the host is GHES or GitHub.com and whether you're using it for API or web searches.

    What are your thoughts around introducing a --path flag instead of trying to convert the --filename and --extension flag values into a valid path qualifier string?

    I am envisioning that the --path flag would only be compatible with the --web flag and result in an error if not accompanied by it. If the --filename or --extension flags were used with the --web flag it would output an error saying to use the --path flag instead.

    The thought process here is that this new --path flag would be more powerful and reflect the web search better than any sort of conversion we can do using the --filename and --extension flag values. Additionally, it sets up nicely to deprecate the --filename and --extension flags in the future if the search API does eventually get around to matching the web search. Perhaps this would constitute a breaking change but it would only impact use cases where the --web flag is being used so maybe that is a bit of a gray area.

    🤔 Let me see if I understand how this proposal would affect users' experience.

    Scenario 1

    • User wants to find repositories with go.mod files using github.com/cli/go-gh/v2 module

    • They would retrieve this via the REST Search code endpoint or GraphQL search query would look like:

      filename:go.mod "github.com/cli/go-gh/v2"
      
      gh search code --filename go.mod "github.com/cli/go-gh/v2"
    • If they want to view it on the web and re-run it using --web, they would receive an error stating --filename can't be used with --web and to use --path instead

    • The user doesn't understand why this error happened and looks at the command's help docs and is directed to the legacy code search documentation:

      ›$ gh search code --help
      Search within code in GitHub repositories.
      
      The search syntax is documented at:
      <https://docs.github.com/search-github/searching-on-github/searching-code>

      Maybe we have a special note in the docs that say, "If you're using --web on github.com or ghe.com, then use --path flag with this other search documentation syntax"

    • The legacy code search documentation talks about path: search qualifier and how it is only a static directory path

    In this scenario, I fear the user is confused having to navigate between API and web search documentation to come up with 2 different incantations of gh search code to get what they ultimately want.

    Scenario 2

    • Let's see which cli repositories have our standard lint.yml workflow setup

      $ gh search code org:cli path:.github/workflows filename:lint.yml
      Working...
      
      Showing 3 of 3 results
      
      cli/cli .github/workflows/lint.yml
      
      cli/go-gh .github/workflows/lint.yml
      
      cli/oauth .github/workflows/lint.yml
    • I want to view it on the web, so re-run it with --web which results in a error and suggestion to use --path

    • Viewing this on the web would require the following query: org:cli path:.github/workflows/lint.yml

  6. samcoe commented on Jun 30, 2025

    @samcoe
    Contributor

    @andyfeller

    So, path: is technically available everywhere; its just a question of what you can do based on whether the host is GHES or GitHub.com and whether you're using it for API or web searches.

    Ok I got it I think. I will limit the implementation to just github.com and not modify any invocations targeting GHES for now.

    Scenario 1
    In this scenario, I fear the user is confused having to navigate between API and web search documentation to come up with 2 different incantations of gh search code to get what they ultimately want.

    I agree it is not ideal and a bit confusing for sure if the user is not aware of the correct path qualifier syntax to use. As you suggested we could mitigate it by linking to the correct search documentation syntax in the error message but the user would still need to figure out the correct syntax again.

    Scenario 2
    gh search code org:cli path:.github/workflows filename:lint.yml

    This scenario is not exactly correct based on my proposal. I am envisioning that in this scenario gh would error out saying that the --path flag is only available when using the --web flag. As you said, there is a path qualifier in the API but it has different functionality than the web search so my thought was that it would be confusing to have a flag that works differently based on targeting the API or web and that an error might be a nicer user experience.

    Having thought through this a bit more and running through your scenarios it is pretty clear that because we are trying to target two different search implementations with different features, gh will likely need to do some conversion/magic behind the scenes to hide this fact from the user and reduce confusion. I think your original acceptance criteria is looking like the simplest and best option. If you are comfortable with going that route, let me know and I will begin implementing.

  7. 3dbrows commented on Nov 17, 2025

    @3dbrows

    @andyfeller In your acceptance criteria:

    When user runs gh search code --web --filename ...
    Then web browser is opened ... with path:**/default.nix search qualifier instead of filename:default.nix

    This is not quite the actual behaviour at present: the web browser is opened with path:default.nix rather than path:**/default.nix. A subtle, but important difference if the file is in a subdirectory.

    I think the latter (i.e. your acceptance criteria) would be correct behaviour. But there may be nuance that I am missing here. What do you and @samcoe think?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

bugSomething isn't workinggh-searchrelating to the gh search commandpriority-2Affects more than a few users but doesn't prevent core functions

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions