Skip to content

Bug: gh gist edit selector only fetches ~10 gists, lacks filters/limit options #11738

Open

Description

@0xdevalias

Describe the bug

When using the gh gist edit command, the new gist selector (added in PR #7537) only fetches 10 of the most recent(?) gists and does not provide options to filter/limit results (unlike gh gist list), nor does it let us search for additional gists beyond that initial 10. This makes it difficult to select and edit older or specific gists.

Affected version

⇒ gh version
gh version 2.67.0 (2025-02-11)
https://github.com/cli/cli/releases/tag/v2.67.0

Steps to reproduce the behavior

  1. Run gh gist edit
  2. Observe the list of selectable gists (only ~10, no filtering or limit options)
  3. Try to find and edit an older gist that does not appear in the list

Expected vs actual behavior

Expected: The gist selector should support filtering and allow specifying a higher limit, similar to gh gist list, so users can select and edit any of their gists.

Actual: Only ~10 recent gists are shown, with no way to filter or adjust the limit, making it hard to access and edit older gists.

Logs

No logs provided.

See Also

Tangentially related:

Activity

  1. 0xdevalias commented on Sep 15, 2025

    @0xdevalias
    Author

    This workaround command using fzf from another tangentially related thread is actually quite useful here:

    workaround:

    alias gist='gh gist edit $(gh gist list | fzf --reverse | awk "{print \$1}")'

    Originally posted by @hholst80 in #2471 (comment)

    And because it uses gh gist list, it means we could also pass in extra args like --limit, --filter, --public, --secret, etc. eg:

    gist-edit() {
      local search="$1"
      gh gist edit "$(
        gh gist list --filter "$search" --limit 100 |
        fzf --reverse |
        awk '{print $1}'
      )"
    }

    Though maybe the 'official' implementation could include caching / local filtering / similar maybe, so we're not having to hit the API with a high --limit all the time?

  2. 0xdevalias commented on Sep 15, 2025

    @0xdevalias
    Author

    I haven't deeply verified this, but here is some additional context about the likely root cause and related code-paths; as generated by GitHub Codex (GPT-5) when fed this repo + issue:

    Summary of findings

    • What’s happening

      • When run interactively with no selector, gh gist edit invokes a gist picker that calls a helper to fetch gists and present them in a select prompt.
      • That helper currently only retrieves a single page of gists (about 10 by default) and does not paginate or allow filtering/limit options, so older gists are not reachable from the selector.
    • Why

      • The edit command defers selection to shared.PromptGists(...). Tests around the edit flow show a GraphQL query named GistList used to populate the prompt with the user’s gists, but there’s no path to fetch more than the first page.
      • There is no flag plumbing in gh gist edit to pass limit/visibility filters like gh gist list does.

    Relevant files and sections

    • pkg/cmd/gist/edit/edit.go

      • Where the interactive selection is invoked:
        • Calls the gist selector when no selector argument is provided:
          • shared.PromptGists(opts.Prompter, client, host, cs)
          • Reference: lines around:
            • func editRun(opts *EditOptions) error {
              client, err := opts.HttpClient()
              if err != nil {
              return err
              }
              cfg, err := opts.Config()
              if err != nil {
              return err
              }
              host, _ := cfg.Authentication().DefaultHost()
              gistID := opts.Selector
              if gistID == "" {
              cs := opts.IO.ColorScheme()
              if gistID == "" {
              if !opts.IO.CanPrompt() {
              return cmdutil.FlagErrorf("gist ID or URL required when not running interactively")
              }
              gist, err := shared.PromptGists(opts.Prompter, client, host, cs)
              if err != nil {
              return err
              }
              if gist.ID == "" {
              fmt.Fprintln(opts.IO.Out, "No gists found.")
              return nil
              }
              gistID = gist.ID
              }
              }
      • Resolution of selector and ownership checks happen after selection:
        • username, err := api.CurrentLoginName(apiClient, host)
          if err != nil {
          return err
          }
          if username != gist.Owner.Login {
          return errors.New("you do not own this gist")
          }
      • Command wiring shows no filter/limit flags exposed for the selector:
        • Flags are only: --add, --desc, --filename, --remove
        • cmd := &cobra.Command{
          Use: "edit {<id> | <url>} [<filename>]",
          Short: "Edit one of your gists",
          Args: func(cmd *cobra.Command, args []string) error {
          if len(args) > 2 {
          return cmdutil.FlagErrorf("too many arguments")
          }
          return nil
          },
          RunE: func(c *cobra.Command, args []string) error {
          if len(args) > 0 {
          opts.Selector = args[0]
          }
          if len(args) > 1 {
          opts.SourceFile = args[1]
          }
          if runF != nil {
          return runF(&opts)
          }
          return editRun(&opts)
          },
          }
    • pkg/cmd/gist/edit/edit_test.go

      • Evidence of the underlying gist list fetching and prompt behavior used by the selector:
        • Mocks GraphQL query named GistList to supply selectable items:
          • if tt.mockGistList {
            sixHours, _ := time.ParseDuration("6h")
            sixHoursAgo := time.Now().Add(-sixHours)
            reg.Register(httpmock.GraphQL(`query GistList\b`),
            httpmock.StringResponse(
            fmt.Sprintf(`{ "data": { "viewer": { "gists": { "nodes": [
            {
            "description": "whatever",
            "files": [{ "name": "cicada.txt" }, { "name": "unix.md" }],
            "isPublic": true,
            "name": "1234",
            "updatedAt": "%s"
            }
            ],
            "pageInfo": {
            "hasNextPage": false,
            "endCursor": "somevaluedoesnotmatter"
            } } } } }`, sixHoursAgo.Format(time.RFC3339))))
            reg.Register(httpmock.REST("GET", "gists/1234"),
            httpmock.JSONResponse(tt.mockGist))
            reg.Register(httpmock.GraphQL(`query UserCurrent\b`),
            httpmock.StringResponse(`{"data":{"viewer":{"login":"octocat"}}}`))
            gistList := "cicada.txt whatever about 6 hours ago"
            pm.RegisterSelect("Select a gist",
            []string{gistList},
            func(_, _ string, opts []string) (int, error) {
            return prompter.IndexFor(opts, gistList)
            })
            }
          • The stub includes pageInfo.hasNextPage, but the flow does not test or handle “load more” behavior.
        • Sets up prompter for “Select a gist”:
          • gistList := "cicada.txt whatever about 6 hours ago"
            pm.RegisterSelect("Select a gist",
            []string{gistList},
            func(_, _ string, opts []string) (int, error) {
            return prompter.IndexFor(opts, gistList)
            })
    • pkg/cmd/gist/shared/... (inferred)

      • The selector is implemented in shared.PromptGists, which performs the GistList GraphQL request. Based on tests, it appears to request a single page and does not loop over pageInfo.hasNextPage to support pagination or offer a “Load more” option.
      • While this file wasn’t shown above, it is the logical location of:
        • PromptGists(...)
        • GraphQL query GistList (viewer.gists with nodes, pageInfo{hasNextPage, endCursor})

    What the bug is

    • The gist picker used by gh gist edit only fetches the first page (~10) of the user’s gists and does not paginate or support filtering/limit configuration. As a result, older/specific gists are not reachable via the interactive selector unless the user supplies an explicit ID/URL.

    Ideal solution

    • Add pagination support to the gist selector so users can access all their gists interactively, and expose parity filters with gh gist list.

    Proposed implementation details

    Pagination and “Load more”

    • Update shared.PromptGists to:
      • Accept an options struct, e.g. PromptGistsOptions{Limit int, Visibility string} to control page size and filtering.
      • Fetch gists with first: pageSize via GraphQL GistList and capture pageInfo.endCursor and hasNextPage.
      • Build the prompt choices from nodes and, if hasNextPage is true, append a sentinel choice “Load more…”.
      • If the user picks “Load more…”, fetch the next page with after: endCursor, merge choices, and re-prompt.
      • Return the selected gist when a real gist item is chosen.
    • Default page size could be raised to something more practical (e.g., 30 or 50) to reduce the number of “Load more” interactions, while still keeping requests reasonable.

    Filtering/limit parity with gh gist list

    • Add flags to gh gist edit that mirror gh gist list:
      • --limit, -L: maximum number of gists to fetch before prompting
      • --public / --secret (or a single --visibility=public|secret) to filter on visibility
    • Plumb these flags into the PromptGistsOptions to influence the initial fetch(es).
    • If no flags are provided, fall back to the paginated interactive flow described above.

    UX niceties

    • Include a “Enter ID/URL…” option in the selector to let users paste a gist ID/URL if they know it.
    • Include file names or snippet of description in the list entries as already done in tests (e.g., “cicada.txt whatever about 6 hours ago”).
    • If the underlying prompter supports type-to-filter, enable it; otherwise, client-side filter could be offered via an optional text input shown before/after the first page load and applied to loaded items.

    Alternative or complementary approach

    • Reuse the gist list logic directly to fetch up to --limit (default 100) via REST (GET /gists?per_page=100) or GraphQL, then prompt.
    • This gives immediate parity with gh gist list flags and reduces selector complexity at the cost of potentially larger initial requests.

    Test updates

    • Add tests that:
      • Stub GistList to return hasNextPage=true on first page and verify the presence and behavior of the “Load more…” option.
      • Verify that --limit controls the total number of items fetched.
      • Verify visibility filter flags result in only public/secret gists shown.
      • Preserve existing flows (still works when a specific selector/URL is provided; non-interactive mode still requires an explicit selector).

    Impact

    • Users can reliably select any of their gists from gh gist edit without having to remember IDs.
    • Behavior aligns with gh gist list expectations, improving consistency across gist subcommands.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    gh-gistrelating to the gh gist commandneeds-triageneeds to be reviewed

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions