Skip to content

gh pr create --web now always overwrites autofilled content #11277

Description

@bewuethr

Describe the bug

gh pr create --web now always generates autofilled content; previously, the contents of .github/PULL_REQUEST_TEMPLATE.md would be populated. It looks like the behaviour now defaults to --fill, even though I don't provide that flag.

Affected version

This was introduced in #10547 (fixes #10527) and released in v2.75.0.

Steps to reproduce the behavior

  1. Make sure the repository has a non-empty .github/PULL_REQUEST_TEMPLATE.md
  2. On a topic branch, create and push a few commits
  3. Run gh pr create --web

Expected vs actual behavior

Actual: the title is prefilled with the branch name, and the body is a list of commit titles.

The previous (and expected) behaviour was to leave the title empty (I think...) and have the body pre-populated with the contents of .github/PULL_REQUEST_TEMPLATE.md.

Logs

Click for logs
$ GH_DEBUG=api gh pr create --web
[git remote -v]
[git config --get-regexp ^remote\..*\.gh-resolved$]
* Request at 2025-07-11 10:12:13.294054503 -0700 PDT m=+0.036090788
* Request to https://api.github.com/graphql
> POST /graphql HTTP/1.1
> Host: api.github.com
> Accept: application/vnd.github.merge-info-preview+json, application/vnd.github.nebula-preview
> Authorization: token ████████████████████
> Content-Length: 392
> Content-Type: application/json; charset=utf-8
> Graphql-Features: merge_queue
> Time-Zone: America/Vancouver
> User-Agent: GitHub CLI 2.75.0

GraphQL query:
fragment repo on Repository {
    id
    name
    owner { login }
    viewerPermission
    defaultBranchRef {
      name
    }
    isPrivate
  }
  query RepositoryNetwork {
    viewer { login }

    repo_000: repository(owner: "myorg", name: "myrepo") {
      ...repo
      parent {
        ...repo
      }
    }

  }
GraphQL variables: null

< HTTP/2.0 200 OK
< Access-Control-Allow-Origin: *
< Access-Control-Expose-Headers: ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset
< Content-Security-Policy: default-src 'none'
< Content-Type: application/json; charset=utf-8
< Date: Fri, 11 Jul 2025 17:12:15 GMT
< Referrer-Policy: origin-when-cross-origin, strict-origin-when-cross-origin
< Server: github.com
< Strict-Transport-Security: max-age=31536000; includeSubdomains; preload
< Vary: Accept-Encoding, Accept, X-Requested-With
< X-Accepted-Oauth-Scopes: repo
< X-Content-Type-Options: nosniff
< X-Frame-Options: deny
< X-Github-Media-Type: github.v4; param=merge-info-preview.nebula-preview; format=json
< X-Github-Request-Id: CC7C:289371:4546EF1:8BFE0B0:687145EE
< X-Oauth-Client-Id: 178c6fc778ccc68e1d6a
< X-Oauth-Scopes: admin:enterprise, admin:org, admin:org_hook, admin:public_key, gist, project, repo
< X-Ratelimit-Limit: 5000
< X-Ratelimit-Remaining: 4998
< X-Ratelimit-Reset: 1752257518
< X-Ratelimit-Resource: graphql
< X-Ratelimit-Used: 2
< X-Xss-Protection: 0

{
  "data": {
    "viewer": {
      "login": "bewuethr"
    },
    "repo_000": {
      "id": "someID=",
      "name": "myrepo",
      "owner": {
        "login": "myorg"
      },
      "viewerPermission": "ADMIN",
      "defaultBranchRef": {
        "name": "main"
      },
      "isPrivate": true,
      "parent": null
    }
  }
}

* Request took 522.365894ms
[git status --porcelain]
[git symbolic-ref --quiet HEAD]
[git config --get-regexp ^branch\.topic-branch\.(remote|merge|pushremote|gh-merge-base)$]
[git rev-parse --symbolic-full-name fix-annotation@{push}]
[git show-ref --verify -- HEAD refs/remotes/origin/topic-branch]
[git -c log.ShowSignature=false log --pretty=format:%H%x00%s%x00%b%x00 --cherry origin/main...topic-branch]
Opening https://github.com/myorg/myrepo/compare/main...topic-branch in your browser.
Opening in existing browser session.

Activity

  1. andyfeller commented on Jul 11, 2025

    @andyfeller
    Contributor

    Thank you for raising this concern up and my apologies for the problem, @bewuethr! 🙇 Let me dig into this a little deeper to assess the original PR and the information provided.

    Expand for my regression testing notes

    [!NOTE]
    Testing against v2.74.2 using local build:

    gh repo clone cli/cli
    cd cli
    git checkout v2.74.2
    make
    alias gh=$(realpath bin/gh)

    Base line

    Testing v2.74.2 against single-commit branch cli/cli#10547

    1. gh pr create --web launches pull request where the UI defaults the title and body based on the commit combined with default pull request template

      Image
    2. gh pr create --web --title "Title from a flag" launches pull request with title parameter where the UI uses the title from the flag and the body based on the commit combined with default pull request template

      Image
    3. gh pr create --web --body "Body from a flag" launches pull request with body parameter where the UI defaults the title based on the commit and the body from the flag, no template used

      Image
    4. gh pr create --web --template nonstandard.md launches pull request with template parameter where the UI uses the title from the flag and the body based on the commit combined with non-standard pull request template

      Image

    Testing v2.74.2 against multi-commit branch cli/cli#10547-multi

    1. gh pr create --web launches pull request where the UI defaults the title to " + multi" and body based on the default pull request template

      Image
    2. gh pr create --web --title "Title from a flag" launches pull request with title parameter where the UI uses the title from the flag and the body based on the default pull request template

      Image
    3. gh pr create --web --body "Body from a flag" launches pull request with body parameter where the UI defaults the title to " + multi" and body from the flag

      Image
    4. gh pr create --web --template nonstandard.md launches pull request with template parameter where the UI defaults the title to " + multi" and body based on the non-standard pull request template

      Image

    Testing v2.75.0 against single-commit branch cli/cli#10547

    1. gh pr create --web launches pull request with title and body parameters

      Image

  2. austinorth commented on Jul 14, 2025

    @austinorth

    I am also running into this behavior. Slowing my workflow down considerably, so kudos to whomever is able to resolve. Here are versions:

    gh version 2.75.0 (2025-07-09)
    https://github.com/cli/cli/releases/tag/v2.75.0
    
  3. andyfeller commented on Jul 14, 2025

    @andyfeller
    Contributor

    @bewuethr @austinorth: I've been putting together regression notes (see above) and discussing this with maintainers and figuring out a path forward.

  4. andyfeller commented on Jul 14, 2025

    @andyfeller
    Contributor

    Follow up

    TLDR

    cc: @simon-isler

    Regression explained

    I believe this regression is due to unexpected side effect in the previous gh pr create --web code related to the logic of generating title and body from local commits.

    Under the covers, gh pr create calls initDefaultTitleBody(...) to determine a default title and body based on local git commits, which are stored in the issue metadata state type:

    state, err := NewIssueState(*ctx, *opts)
    if err != nil {
    return err
    }
    if opts.TitleProvided {
    state.Title = opts.Title
    }
    if opts.BodyProvided {
    state.Body = opts.Body
    }
    var openURL string
    if opts.WebMode {
    if opts.Template != "" {
    state.Template = opts.Template
    }
    err = handlePush(*opts, *ctx)
    if err != nil {
    return err
    }
    openURL, err = generateCompareURL(*ctx, *state, projectsV1Support)
    if err != nil {
    return err
    }
    if !shared.ValidURL(openURL) {
    err = fmt.Errorf("cannot open in browser: maximum URL length exceeded")
    return err
    }
    return previewPR(*opts, openURL)
    }

    func NewIssueState(ctx CreateContext, opts CreateOptions) (*shared.IssueMetadataState, error) {
    var milestoneTitles []string
    if opts.Milestone != "" {
    milestoneTitles = []string{opts.Milestone}
    }
    meReplacer := shared.NewMeReplacer(ctx.Client, ctx.PRRefs.BaseRepo().RepoHost())
    assignees, err := meReplacer.ReplaceSlice(opts.Assignees)
    if err != nil {
    return nil, err
    }
    state := &shared.IssueMetadataState{
    Type: shared.PRMetadata,
    Reviewers: opts.Reviewers,
    Assignees: assignees,
    Labels: opts.Labels,
    ProjectTitles: opts.Projects,
    Milestones: milestoneTitles,
    Draft: opts.IsDraft,
    }
    if opts.FillVerbose || opts.Autofill || opts.FillFirst || !opts.TitleProvided || !opts.BodyProvided {
    err := initDefaultTitleBody(ctx, state, opts.FillFirst, opts.FillVerbose)
    if err != nil && (opts.FillVerbose || opts.Autofill || opts.FillFirst) {
    return nil, fmt.Errorf("could not compute title or body defaults: %w", err)
    }
    }
    return state, nil
    }

    func initDefaultTitleBody(ctx CreateContext, state *shared.IssueMetadataState, useFirstCommit bool, addBody bool) error {
    commits, err := ctx.GitClient.Commits(context.Background(), ctx.BaseTrackingBranch, ctx.PRRefs.UnqualifiedHeadRef())
    if err != nil {
    return err
    }
    if len(commits) == 1 || useFirstCommit {
    state.Title = commits[len(commits)-1].Title
    state.Body = commits[len(commits)-1].Body
    } else {
    state.Title = humanize(ctx.PRRefs.UnqualifiedHeadRef())
    var body strings.Builder
    for i := len(commits) - 1; i >= 0; i-- {
    fmt.Fprintf(&body, "- **%s**\n", commits[i].Title)
    if addBody {
    x := regexPattern.ReplaceAllString(commits[i].Body, " ")
    fmt.Fprintf(&body, "%s", x)
    if i > 0 {
    fmt.Fprintln(&body)
    fmt.Fprintln(&body)
    }
    }
    }
    state.Body = body.String()
    }
    return nil
    }

    Prior to v2.75.0, the following logic blanked this information out when launch the browser for --web flag:

    if opts.WebMode {
    if !(opts.Autofill || opts.FillFirst) {
    state.Title = opts.Title
    state.Body = opts.Body
    }
    if opts.Template != "" {
    state.Template = opts.Template
    }
    err = handlePush(*opts, *ctx)
    if err != nil {
    return err
    }
    openURL, err = generateCompareURL(*ctx, *state, projectsV1Support)

    This had the side effect of allowing the GitHub.com PR creation experience to determine the title and body using server-side information. I believe this wasn't caught by gh pr create tests because of the local git commit state needed to be setup when going to web.

  5. andyfeller commented on Jul 14, 2025

    @andyfeller
    Contributor

    This should be fixed in v2.75.1, thank you again for your patience! 🙇

  6. self-assigned this
    on Jul 14, 2025
  7. added
    priority-1Affects a large population and inhibits work
    and removed on Jul 14, 2025
  8. bewuethr commented on Jul 14, 2025

    @bewuethr
    Author

    Excellent, thank you for the prompt fix!

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

Metadata

Metadata

Assignees

Labels

bugSomething isn't workinggh-prrelating to the gh pr commandpriority-1Affects a large population and inhibits work

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions