glab mr for and glab mr create --related-issue never return when the issue's branch cannot be created

Checklist

  • I'm using the latest version of the extension: glab built from main at a6fd50ce. The code is the same in v1.124.0, the latest release.
  • Operating system and version: Linux 6.1.0-53-amd64 x86_64
  • Gitlab.com or self-managed instance? Both: the loop is in glab and does not depend on the instance.
  • GitLab version (if self-managed): not applicable, see Environment.
  • I have performed glab auth status to check for authentication issues: not applicable, the failures below are the API's answers.
  • Run the command in debug mode: not needed, the output below shows each request.

Summary

glab mr for <issue> and glab mr create --related-issue <issue> without --source-branch create a branch named after the issue. When that first branch creation fails, both commands try numbered names (<name>-1, <name>-2, ...) until one succeeds, whatever the error was. A failure that is not about an existing branch (no permission to push, an invalid name, a target branch that does not exist, a network error) therefore repeats forever. The numbered names also skip the sanitization of the first name, so for an issue whose branch already exists and whose title contains characters such as :, ~ or ?, every numbered name is invalid and the loop never ends either.

Ctrl+C does not stop it: glab turns SIGINT and SIGTERM into a cancelled command context, but neither the loop nor its requests look at that context, so neither signal ends it. In the runs below, only SIGKILL did.

Environment

  • OS: Linux 6.1.0-53-amd64 x86_64
  • SHELL: /bin/bash
  • TERM: none, the commands ran without a terminal
  • GLAB: built from main at a6fd50ce (glab DEV)

Other: no GitLab instance. The commands ran against a local stand-in for the API routes they call (get the project and the issue, create a branch, create a merge request), answering as GitLab does: 403 Forbidden for a user who cannot push, and 400 with Branch already exists or Branch name is invalid.

Steps to reproduce

  1. In a project you cannot push to, run glab mr for <issue-iid>, or glab mr create --related-issue <issue-iid> --title "x" --yes, without --source-branch.
  2. Or, in your own project, create an issue titled Fix: crash in ~/.config?, create the branch <iid>-fix-crash-in-config- yourself, and run either command for that issue.

What is the current bug behavior?

The command never returns and sends a new branch creation request for each number. Against the local stand-in, each run below was sent SIGINT 15 seconds after it started and SIGKILL 5 seconds later:

Command Answer to branch creation Requests sent Ended by
glab mr for 7 403 1567 SIGKILL
glab mr create --related-issue 7 403 1682 SIGKILL
glab mr for 7 (title Fix: crash in ~/.config?) 400, the branch exists, then 400, the name is invalid 887 SIGKILL
glab mr create --related-issue 7 (same title) same 797 SIGKILL

glab mr for also prints the error of every retry to stdout, and prints <nil> once a numbered name is created. No merge request is created in any of the four runs.

What is the expected correct behavior?

A numbered name is tried only when the branch, or a branch below it (<name>/...), already exists, it is built from the sanitized name (7-fix-crash-in-config--1), the attempts stop after a bounded number, and any other error ends the command with the API's message.

Relevant logs and/or screenshots

The first three branch creation requests of glab mr for 7 for the title Fix: crash in ~/.config?, when 7-fix-crash-in-config- exists, as the stand-in recorded them (method, path and the branch parameter):

POST /api/v4/projects/OWNER%2FREPO/repository/branches 7-fix-crash-in-config-
POST /api/v4/projects/OWNER%2FREPO/repository/branches 7-fix:-crash-in-~/.config?-1
POST /api/v4/projects/OWNER%2FREPO/repository/branches 7-fix:-crash-in-~/.config?-2

and the first two lines of its stdout:

POST http://127.0.0.1:18745/api/v4/projects/OWNER%2FREPO/repository/branches: 400 {message: Branch name is invalid}
POST http://127.0.0.1:18745/api/v4/projects/OWNER%2FREPO/repository/branches: 400 {message: Branch name is invalid}

Possible fixes

The loops are in mr_for.go#L84-97 and mr_create.go#L546-552. The first name goes through utils.ReplaceNonAlphaNumericChars (mr_for.go#L77), the numbered names through strings.ReplaceAll(title, " ", "-") (mr_for.go#L88). GitLab answers an existing branch with 400 Branch already exists (branches.rb#L240, validate_new_service.rb#L13), or with 400 Failed to create branch '<name>': Branch name conflicts with existing branch hierarchy. when a branch <name>/... exists (create_service.rb#L89-94), and an invalid name with the same status (validate_new_service.rb#L10), so the message is what tells them apart. Retrying only on those two answers, numbering the sanitized name, capping the attempts and passing the command's context to the requests would fix both commands; I have a merge request ready for it.