Branch and PR conventions that survive contact with GitLab
How to name branches and merge requests so GitLab links them to issues on its own, what its native referencing actually covers, and where the automatic link quietly stops working.
You have a repository where half the branches are called things like fix-login and rob/wip-2, and six months later nobody can say which merge request delivered which issue. Someone suggests a naming convention. Someone else points out that the last three naming conventions lasted about a fortnight.
The good news is that GitLab does more of this for you than most teams use. The convention that survives is the one the tool creates for you, not the one you write in a wiki page.
The referencing syntax people underuse
GitLab’s referencing syntax is the part people underuse. Inside a project, #123 refers to issue 123 and !456 refers to merge request 456. Across projects you can write the full path, group/project#123, and it resolves. Type any of those in a commit message, a merge request description, or a comment, and GitLab creates a visible cross-reference on the other object. That cross-reference is stored, not rendered on the fly, so it survives edits and shows up in the issue’s activity feed.
On top of that there are closing keywords. Put a phrase like Closes #123 in a merge request description, and when that merge request merges into the default branch GitLab closes the issue and records why. The list of accepted verbs is generous (close, closes, fix, fixes, resolve, resolves and similar), and it accepts multiple issues in one line.
The mechanism that matters most, though, is starting from the other end. From an issue, GitLab will offer to create a branch, and the branch name it proposes contains the issue number. Take that offer and three things happen before anyone types a convention: the branch is linked to the issue, the merge request created from that branch inherits the link, and the closing keyword is pre-filled in the description. The convention becomes a side effect of the workflow rather than a rule people have to remember.
Convention as a rule
- Written in a wiki nobody reopens
- Enforced by whoever is reviewing that day
- Breaks the first busy week
- Retroactively unfixable
Convention as a side effect
- Branch created from the issue itself
- Number is in the name before you start
- Merge request inherits the link
- Closing keyword pre-filled
Two more native tools are worth knowing about. GitLab supports merge request description templates stored in the repository itself, so a required reference line can be part of the description every author sees rather than a thing they are asked to remember. And GitLab has push rules that can reject a push whose branch name or commit message does not match a regular expression. Availability of push rules depends on your tier and on whether you are on GitLab.com or self-managed, so check what your instance actually offers before you build a plan around it.
Four places, in the order you will hit them
Four places, in the order you will hit them.
Closing keywords are scoped. The auto-close behaviour is tied to merging into the default branch of the project that owns the issue. Cross-project closing needs the fully qualified reference and, depending on your setup and permissions, may not close at all. If your issues live in one project and your code in another, test this deliberately rather than assuming.
The link only happens if someone started from the issue. Nothing stops an engineer running git checkout -b quick-fix in a terminal. That branch has no number, the merge request has no reference, and GitLab has no way to know it should have. This is the single largest source of unlinked work, and it is not a tooling failure.
Push rules cannot see intent. A regex can confirm that a branch name contains something shaped like an issue number. It cannot confirm the issue exists, is open, is in this project, or has anything to do with the change. People learn to satisfy the pattern.
If your tracker is not GitLab Issues, almost none of this applies. Teams running GitLab for code and Jira for planning get no closing keywords and no native cross-references. GitLab has a Jira integration that recognises issue keys in commits and merge requests, but it is a different mechanism with different coverage, and you should verify what it links rather than assume parity.
Change the entry point, not the rule
Ordered from cheapest to most involved. Most teams should stop after the second.
Change the entry point, not the rule. Make “create the branch from the issue” the documented way to start work, and make sure people know the button exists. This costs nothing and fixes the majority of cases. Pair it with the honest statement that a branch without a number is not forbidden, it is just invisible, and invisible work is the work that gets asked about in three months when nobody remembers it.
Add a merge request template with a reference line. One line at the top of the description, pre-filled with a closing keyword and a blank for the number. An empty blank is visible to the reviewer, which is a far better enforcement mechanism than a rule, because it puts the check in front of a human at exactly the moment they care.
Add a CI job that warns rather than fails. GitLab CI exposes the branch name and merge request metadata to jobs as predefined variables. Check the predefined variables reference for your version rather than copying names out of a blog post, because the set has grown over time and some are only populated in merge request pipelines. Have the job match the branch name and merge request title against your issue reference pattern and emit a warning when neither matches. Make it non-blocking for the first month. A blocking check on day one produces #1 in every branch name and teaches you nothing.
Then turn on push rules, if you have them and you still need them. By this point you will know your actual failure rate and can decide whether a hard block is proportionate.
Where this breaks down
Some work genuinely has no issue. A typo in a comment, a dependency bump, a revert at two in the morning. If your convention treats those as violations you will get compliance theatre: issues created purely to satisfy the check, closed immediately, adding noise to the tracker you were trying to make trustworthy. Decide up front what is exempt and write it down.
Retroactive linking is mostly a lost cause. You can regex old branch names and backfill some references, but the merge requests where somebody wrote fix-login are gone. Any measurement you start today is a measurement of today onwards. That is worth saying out loud before someone asks for a two-year trend.
Push rules on a busy monorepo cause more pain than they prevent. A hard block at push time interrupts people mid-thought and is usually satisfied by the least useful compliant value. Warnings in the merge request are slower but produce better data.
A high reference rate is not a high quality rate. Every merge request can carry a valid issue number and the issues can still say nothing useful about what was built. Linking is a prerequisite for traceability, not the same thing as it.
The takeaway
GitLab already contains most of a working convention. The referencing syntax, closing keywords and branch-from-issue flow will connect the majority of your work without anyone learning a rule, provided you make the linked path the easy one and put the missing reference in front of a reviewer instead of a linter.
If you do one thing this week: open the last fifty merged merge requests and count how many carry an issue reference. That number is your convention’s real adoption rate, and it is usually a surprise.