Blog / A git branch naming convention with the ticket number your tracker can parse

A git branch naming convention with the ticket number your tracker can parse

The Kevta team6 min read

Nothing reads your feat/ prefix. The only segment doing work is the issue key, and where it sits decides whether your tracker sees the branch at all. The matching rules, from source.

Most advice about branch naming is taxonomy. feat/ or feature/. Whether chore earns a place next to refactor. A git branch naming convention with a ticket number in it usually gets one line near the end, phrased as a nice-to-have.

That is backwards. Take feature/MS-42-refactor-router. Three segments, and exactly one has a machine reading it. The prefix is a note to your colleagues, the slug a reminder to yourself. Only MS-42 does work, and whether it works at all depends on where you put it.

Nothing parses the prefix

No tracker in this article reads the word feature. None of them branches on it, sorts by it, or behaves differently because you wrote feat. The tooling question is narrower: what does the thing on the other end scan, and what shape does it need?

Tool What it scans Where the identifier must sit
GitHub closing keywords The pull request description, or a commit message Not the branch name; it is not part of the mechanism
GitLab The branch name At the very start, followed by a hyphen
Linear The branch name, the PR title, the PR description Anywhere the issue ID appears
Kevta The branch ref, the PR title, the PR body, commit messages Anywhere, on a word boundary

Four different answers to the same question, which is why a convention borrowed from someone else's stack produces branches your own tracker ignores.

Where the ticket number has to sit

GitLab is the clearest case, because its documentation states the position outright: "To streamline the creation of merge requests, start your Git branch name with the issue or task number, followed by a hyphen. For example, to link a branch to issue #123, start the branch name with 123-" (docs.gitlab.com). The payoff is on the same page: "If the name of the created branch is prefixed with the issue number, GitLab cross-links the issue and related merge request."

Prefixed with. Not containing. Put a type in front and the number is no longer the prefix — a request filed against GitLab opens with the line "Prefixed branch names (e.g. feature/1-add-some-feature) are not being related to GitLab issues" (gitlab.com/gitlab-org/gitlab-foss#58147). On GitLab the conventional name and the linked name are two different names.

GitHub answers differently by not answering. Its linking mechanism is keyword based: "You can link a pull request to an issue by using a supported keyword in the pull request's description or in a commit message" (docs.github.com). Branch names do not appear in that mechanism at all. The same page adds a constraint people discover late: if the pull request targets "any other branch" than the default, "these keywords are ignored, no links are created, and merging the PR has no effect on the issues." A branch is not shut out entirely — the same page describes manually linking "a pull request or branch to an issue" from the issue sidebar, capped at ten issues per pull request — but manual is the opposite of a convention. What Closes #42 actually does is its own argument.

Linear takes the position-free route: "If your branch name includes a Linear issue ID, Linear will automatically link the pull request to that issue." The opt-out is not something you do to the branch: you put skip or ignore in front of the ID in the pull request description (linear.app/docs/github). It has also let you change the copied format since 2020 (linear.app/changelog), which it can afford because the matcher does not care about the format.

The branch naming convention, written as a spec

Five rules. Each one exists because of something above.

  1. The key is mandatory and the rest is optional. A branch with no identifier is invisible to every row in that table. A branch with no type prefix is merely untidy.
  2. Put the key where your tracker looks, not where the style guide says. On GitLab that means the number first and no prefix at all. Do not carry a convention across a tool boundary without rereading the rules.
  3. End the key with a non-alphanumeric character. Every matcher has to decide where the identifier stops. The hyphen is what makes MS-42-refactor-router parse as MS-42 rather than as one long token.
  4. Generate the name, do not type it. The failure is always a keystroke — MS_42, ms42, a digit off by one — and it fails silently.
  5. Keep the identifier stable. A key's value is that it survives renames; anything that regenerates it defeats the point.

What this looks like in Kevta

We build one of the trackers in that table, so read this as the worked example rather than the recommendation.

Copy branch on an issue emits <type>/<KEY>-<slug>feature/MS-42-refactor-router. The type comes from the board's default and falls back to feature; bugfix, chore and hotfix are the alternatives. The slug is lowercased, stripped of accents, has every non-alphanumeric run collapsed to a hyphen and is cut at 50 characters — which is why the key sits in front of it. Truncation can eat the description but never the identifier.

The matcher is a word-boundary-anchored pattern on the board key followed by alphanumerics, matched case-insensitively. So ms-42 matches, the key can sit anywhere in the ref rather than at the front, and the capture stops at the first non-alphanumeric, which is why the -refactor-router tail is ignored. A rule can scan the branch ref, the PR title, the PR body and commit messages, or be narrowed to branch only, or to title and body. No magic word, no linking step. Extracted keys are looked up against that board's issues, so a false positive — a board key that is also an English word — finds nothing.

The limits, plainly: one board to one repository, the key has to land somewhere the chosen strategy scans, and a branch pushed before the issue existed will not retroactively match. The settings are documented under task keys and branches.

The part a branch name cannot do

A parseable name introduces the branch to the issue. It does not move anything on its own — that takes a rule bound to a git event, a separate decision with its own set of things you should not automate.

But naming is the cheap half, and the half teams get wrong by optimising for the segment nobody reads. Pick the position your tracker needs, generate the string, stop arguing about the prefix.

If you want the version where the issue hands you the branch name and the pull request finds its own way back, join the Kevta waitlist.

Kevta is in beta