Best Coding Practices
Each workplace has their own different coding styles and practices. Matrix AI is no different. This article goes through the best practices and common pitfalls for contribution to our projects.
Cloning Repositories
From viewing the source code of a library to actively contributing features, any interactions with a large codebase involve cloning it locally to view and run it. Most repositories are public and open-source, so this can be easily achieved. However, there are some private repositories owned by Matrix AI which cannot be cloned using HTTPS.
Cloning such a private repository requires authentication. If you are using
git, then you have two options:
- SSH – Recommended method for cloning private repositories
- GitHub Personal Access Token (PAT) – Legacy method. Not recommended.
If you haven't set up SSH for GitHub yet, go do that first:
https://docs.github.com/en/authentication/connecting-to-github-with-ssh
Once you're done, you can clone a repo like this:
git clone git@github.com:your-org/project-name.git
Branching Strategy
Branches are semantically significant in our workflows. CI jobs only run on branches that start with:
feature-<whatever>
Not feature/. Not feat-. Not fix/. Not ENG-123-some-change.
Copying the branch name from linear will use feature/ prefix by default.
feature/ENG-123-cool-idea
Change it before creating the branch:
git checkout -b feature-ENG-123-cool-idea
If you use the wrong prefix, then the CI workflows won't run. This is bad as you won't know if your code is violating the code practices as automatic linting and testing won't be running, and it's very easy to miss things like this.
There's a technical reason behind this — GitHub Actions' workflow permissions
are scoped to specific branch patterns, and feature- is how we distinguish
trusted internal contributors from drive-by forks. If you're not a organisation
member, you won't have permission to trigger the workflow. Simple as that.
If you accidentally create a branch with the incorrect name, you can still change the name of the branch. There are two ways to do this depending on if the branch has been pushed to GitHub or not.
Renaming a local branch
To rename a local branch, you can run the following command if you're on the
branch you want to rename. Here, -m is the shorthand for --move.
git branch -m feature-new-feature
Or, if you are not on the same branch, then you can run this command instead.
The first argument to -m is the branch you want to rename, and the second
argument is the new name you want to give it.
git branch -m feature/new-feature feature-new-feature
Renaming a remote branch
To rename a branch locally, you first need to rename the branch locally. Follow the instructions in the above section to do this. After renaming the branch locally, you can push the renamed branch to GitHub.
git push -u origin feature-new-feature
Here, -u is the shorthand for --set-upstream. This automatically tracks a
branch on GitHub to remote branch, so you can push new code by simply typing
git push instead of specifying the origin every time.
Note that you cannot delete branches from GitHub. If you have created a PR on the previous branch, then copy the PR description and create a new PR on the newer branch. You also need to close the previous PR. Don't forget to specify the reason for closure.
Closed in favor of #<new pr>
Commit Messages
We use semantic commits. All the commits must follow this standard. You can check out an exhaustive document detailing this and the usage at conventionalcommits.org. Optionally, this gist contains a quick summary of the concepts.
Here’s a (non-exhaustive) list of valid commits:
feat: add ability to clone self
fix: prevent clone from also copying debts
chore: upgrade Node to v20
docs: clarify usage of cloning endpoint
wip: working on APIv2
Conversely, here is a (non-exhaustive) list of invalid commits:
update
stuff
asdf
changes
Don't merge commits like wip: into staging. You need to squash or rename
those commits.
Rewording Commits
No one's perfect, so it's possible to push an incorrectly formatted commit. You can reword this commit by running the following command.
The HEAD~1 part in the command refers to the previous commit. If you want to
rename the third last commit, then you can change that to HEAD~3. Note that
all commits since the third last commit will show up. You should leave the
commands untouched and only change the command for the commit you want changed.
git rebase -i HEAD~1
This command launches your preferred text editor to rebase commits. Rebasing is
a fancy process to change commit information. Here, we will use the reword
operation to reword the commit.
The interactive rebase prompt will look similar to this. Don't worry, it looks
scarier than it is. Any lines starting with # are comments and can be safely
ignored.
pick 7a74c51 working on this new cool feature
# Rebase 037fa4d..7a74c51 onto 037fa4d (1 command)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup [-C | -c] <commit> = like "squash" but keep only the previous
# commit's log message, unless -C is used, in which case
# keep only this commit's message; -c is same as -C but
# opens the editor
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# create a merge commit using the original merge commit's
# message (or the oneline, if no original merge commit was
# specified); use -c <commit> to reword the commit message
# u, update-ref <ref> = track a placeholder for the <ref> to be updated
# to this position in the new commits. The <ref> is
# updated at the end of the rebase
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
Each commit before the comments has the following formatting.
command short-commit-hash commit-message
Here, each commit will by default be pick-ed. This means that we need to use
this commit. No action will be taken. Upon changing the pick command to
reword, you can reword the commit.
Do not touch anything except the command. After you change the command, you can save and quit the rebase window, at which point another window will be launched depending on the chosen commands.
After changing the command from pick to reword, you can save and close your
editor. In a few moments, another editor widow will open up. This time, it will
look like this.
working on this cool new feature
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Author: Roger Qiu <roger.qiu@polyhack.io>
# Date: Wed Mar 26 14:22:51 2025 +1100
#
# interactive rebase in progress; onto 037fa4d
# Last command done (1 command done):
# reword 7a74c51 feat: added a new feature
# No commands remaining.
# You are currently editing a commit while rebasing branch 'staging' on '037fa4d'.
#
# Changes to be committed:
# modified: docs/development/ai.md
#
Once again, all the lines starting with # can be ignored. Here, you can reword
the commit to whatever you want. After you're done, you can save and quit this
window, at which point it will complete the rebase. If the commit is local-only,
then this should reword the commit and allow you to push the reworded commit. If
the commit has already been pushed to remote, then you need to do a force push.
git push --force
Triple-check that no changes are being overwritten. Perform a git pull before
force pushing. Make sure you are not attempting to push to another branch by
mistake.
After this, the commit would have been renamed successfully.
Squashing Commits
This usually comes before merging code into staging. Squashing commits makes sure that the commit history remains clean.
Squashing commits works mostly the same as renaming a commit. To squash the last two commits, you can use the following command, which will open a rebase window as shown in the previous command.
git rebase -i HEAD~2
The squash command merges the contents of one commit into the first kept
commit before it. For example, the following command pair will squash the second
commit into the first commit, making them into one commit. You can also modify
the commit message of the commits at this stage.
pick 037fa4d feat: my new cool feature
squash 7a74c51 feat: my older but equally cool feature
Before merging, you would run a command like this to ensure all the commits look fine before merging. This command also avoids the need to count every single commit in the branch for rebasing. This would also work if the first commit was being renamed instead of being picked, as the commit is being kept regardless of being renamed or not.
git rebase -i staging
If the commits have been pushed to remote, then you will need to perform a force push. Make sure that there are no remote-only changes and, if working with collaborators, everyone has been notified of the change. Ensure you are force-pushing to only your own branch. Don't force push without good reason.
CI Hygiene
Every commit you push triggers the CI pipeline. If CI fails on your branch at any point, it's your responsibility to fix it. Even if it's a flaky test or some other weirdness — you need to investigate.
Broken CI = broken code.
If you have not touched or even indirectly influenced the failing part of the code (which is pretty hard to prove given how closely linked code tends to be), then let the relevant maintainer know about the failure. Otherwise, investigate why the test is failing, and either fix your code or the test.
You're allowed to skip CI only if the commit is a WIP and you absolutely cannot make the pipeline pass right now. For example, if you're pushing up early code that depends on some change not yet deployed.
To skip CI, add [ci skip] on its own line below the commit message:
wip: early prototype
[ci skip]
Don't abuse it. Use it for quick iteration, not to avoid writing tests. When in doubt, let the pipeline run. The CI pipeline is designed to assist you in writing good code, not to work against you.
Also, make sure your final commit history does not contain [ci skip] or
wip: commits before merging into staging. You can reword or squash these
commits before merging.
Pull Requests
When you open a PR, fill out the template. The checklist is there for a reason. If you created your branch via Linear, GitHub will automatically link the issue. If you didn't, you must add a manual reference in the 'Issues' section. Keep in mind that each PR should be referencing an issue. No PR should be without this focus. If no issue exists for the required change, then make an issue and add it.
REF ENG-359
Linear integration with GitHub is kinda flaky. Once you merge the PR, GitHub will try to close the issue but linear will re-open it. You need to manually close the issue on GitHub for this change to be reflected properly.
Avoid changing the PR description for menial tasks. For updates or new changes, make a comment instead. Tha way, everyone will be notified of the changes. Only change the PR description if the PR spec needs to change. This should not happen often, either. The spec should be planned ahead before starting on the implementation.
Good PRs are focused. If you're doing unrelated work in a single PR, you should probably split it. Ideally, each PR should only tackle a single issue. Sometimes, multiple issues are being addressed by a PR. Unless a PR directly resolves multiple issues, only reference the one most likely to be resolved. Sometimes, a PR can address multiple smaller issues making up a larger problem. This is fine as long as it is done in moderation and all the content is related.
Pre-push Checks
There are some checks or commands that you should run before committing and pushing new code. These commands match what the CI will run, so if these checks pass locally, chances are that the CI will also pass.
Before committing, run the following commands.
npm run lintfix
npm run test
Almost all repositories have a nix shell. This means, on NixOS systems, you need to run the following command to gain access to many of these commands.
nix develop
This will default to bash as the shell. If you are using another shell, like
zsh, you can run the following command.
nix develop --command 'zsh'
The first command will automatically lint your code and resolve any automatically-resolvable issues like missing semicolons or misused indentation. Any other issues requiring manual intervention will be left to you to go through and resolve. Even warnings will fail the CI job, so make sure your code quality is impeccable.
The second command will run all the available tests in the repository. Sometimes, these tests might take upwards of 3 minutes to complete, so it's best to do it in the background while you go and drink some water or something. If you need to run a particular test or a test suite, you can increase the specificity using the following command.
npm run test -- filename.test.ts -t 'should pass this test'
This way, you can specify which file to run, and which test to run from that file. Any test matching all the specified criteria wil be run. If no matching tests are found, then the command will exit with a non-zero exit code.