Files
coder/docs/about/contributing/CONTRIBUTING.md
T
Nick Vigilante e458692cb8 refactor(docs): convert absolute coder/coder blob/tree/main links to relative (DOCS-351) (#26341)
Closes [DOCS-351](https://linear.app/codercom/issue/DOCS-351).

> [!WARNING]
> **DO NOT MERGE** until
[DOCS-349](https://linear.app/codercom/issue/DOCS-349)
([coder.com#877](https://github.com/coder/coder.com/pull/877)) has
shipped to production and baked for at least one Vercel cycle.
>
> Without DOCS-349, the relative links in this PR resolve to broken
docs-route URLs (`/docs/helm/coder/values.yaml` -> 404) instead of
GitHub URLs tagged with the displayed docs version. DOCS-349 fixes the
rewriter to classify these as GitHub blob/tree URLs with the page's
resolved ref.

## TL;DR

Converts 121 absolute
`https://github.com/coder/coder/(blob|tree)/main/<path>` links across 39
docs markdown files to relative paths. After this lands AND DOCS-349
deploys, every one of these links will follow the displayed docs version
(mainline tag on bare URLs, explicit tag on `/@vX.Y.Z/`, `main` on
`/@main/`) instead of always pointing to `main`.

## Why

Today a reader on `/docs/@v2.30.0/install/docker` follows a
`compose.yaml` link and arrives at `main`'s `compose.yaml`, which
doesn't necessarily match what the docs page describes. Helm values,
Terraform templates, and source-code references in particular drift
across versions. The fix is to let the coder.com rewriter substitute the
page's resolved ref into the URL; that only works on relative links.

## Example payoff (post-DOCS-349)

| URL | Today (absolute, always `main`) | After (relative + rewriter) |
|---|---|---|
| `/docs/install/docker` |
`https://github.com/coder/coder/blob/main/compose.yaml` |
`https://github.com/coder/coder/blob/v2.34.1/compose.yaml` (today's
mainline) |
| `/docs/@v2.30.0/install/docker` | same as above |
`https://github.com/coder/coder/blob/v2.30.0/compose.yaml` |
| `/docs/@main/install/docker` | same as above |
`https://github.com/coder/coder/blob/main/compose.yaml` |

## Scope

- **121 conversions** across **39 files**.
- Verb breakdown: `tree/main` (directories) and `blob/main` (files),
both flipped to relative paths.
- Line anchors (`#L23-L24`) and query strings preserved verbatim.
- Conversion is mechanical: relative path computed from the doc file's
directory to the target via `os.path.relpath`. Any path starting at the
same directory or below gets a `./` prefix; otherwise `../` chains.

## Rebased on main

The branch was rebased onto `main` after the DOCS-350 hotfix
([#26339](https://github.com/coder/coder/pull/26339)) merged. The hotfix
repointed 3 `docs-backend-contrib-guide` refs in `backend.md` to `main`,
which then needed the same `main` -> relative conversion this PR is
doing for the other 121 links. The conflict was resolved by reapplying
the mechanical conversion to `backend.md` after taking the hotfix's
content. Net result: those 3 links land here as relative, same as
everything else. New HEAD `3f501cb622`.

## Inline fix folded in: dead `nix` link

- `docs/about/contributing/CONTRIBUTING.md:7` -> `../../../nix`

The original absolute URL `https://github.com/coder/coder/tree/main/nix`
already returned 404 today. Repointed to `flake.nix` (modern Nix
entrypoint, what the prose "Nix environment" semantically refers to).
Closes [DOCS-357](https://linear.app/codercom/issue/DOCS-357) here since
the `check-docs` Linkspector job surfaced it during rebase; cheaper to
fix inline than in a separate single-line PR.

## Out of scope (filed separately)

- [DOCS-350](https://linear.app/codercom/issue/DOCS-350): 3 dead
`docs-backend-contrib-guide` branch refs in `backend.md`
([#26339](https://github.com/coder/coder/pull/26339), merged).
- [DOCS-352](https://linear.app/codercom/issue/DOCS-352): 10 SHA-pinned
`(blob|tree)/<sha>` links pending intent review.
- [DOCS-355](https://linear.app/codercom/issue/DOCS-355): code-server
analog (4 absolute `(blob|tree)/main` links in `coder/code-server`).
- [DOCS-356](https://linear.app/codercom/issue/DOCS-356): 2 upstream
content bugs in `coder/code-server/docs/CONTRIBUTING.md` (independent of
this PR).


## Not triggering `/coder-agents-review`

Docs-only edit; per `AGENTS.md` the bot review is reserved for
product/CI changes.

## Pre-mortem

| Concern | Mitigation |
|---|---|
| Merging before DOCS-349 deploys regresses ~120 currently-working links
into 404s on coder.com | Clear DO-NOT-MERGE banner; tracked as blocker
in Linear. |
| Relative path computed incorrectly (off-by-one `..`) | Verified all
114 newly-relative non-md/non-image paths resolve to existing files in
the repo (only exception is the pre-existing dead `nix` link above). |
| Line anchors stripped during conversion | Preserved by the
substitution regex; verified `#L<n>-L<m>` cases in `airgap.md` and
`speed-up-templates.md`. |
| Future code reorgs change file locations | Relative links will start
pointing to nothing. Same failure mode as absolute links pointing to
renamed files; can be caught with a future link-checker job. |

## Validation

```
$ grep -rE 'github\.com/coder/coder/(blob|tree)/main' docs --include="*.md" | wc -l
0
$ git diff --stat origin/main | tail -1
39 files changed, 118 insertions(+), 118 deletions(-)
```

114 newly-relative paths verified to resolve to existing repo files
(Python `os.path.exists` check on each computed target).

<details>
<summary>Decision log + planning context</summary>

**Why relative over `(blob|tree)/{{currentDocsVersion}}/...`
templating**: relative paths require zero markdown-system support and
zero upstream churn beyond this one PR. Templating would require a
preprocessor on `coder.com` side AND a convention upstream authors have
to remember; relative paths just work in a plain editor and
`github.com`'s own renderer too.

**Why `./` prefix on same-directory targets**: makes the conversion
grep-able later (`grep -E '\((\.\./|\./)'`).

**Why preserve `#L<n>-L<m>` anchors verbatim**: the anchor is meaningful
to the linked file's content, not to the URL form; keeping it as-is
preserves authorial intent. If the file later changes such that the line
range drifts, that's a different problem the SHA-pin audit
([DOCS-352](https://linear.app/codercom/issue/DOCS-352)) will surface.

</details>

---

*Generated by Coder Agents on @nickvigilante's behalf.*





## Drive-by external link fix folded in

`docs/about/contributing/CONTRIBUTING.md:296` cited
`https://reflectoring.io/meaningful-commit-messages/` which is returning
HTTP 503 (the host appears to be down site-wide right now). `check-docs`
Linkspector flagged it after the rebase. Replaced with
`https://cbea.ms/git-commit/` (Chris Beams' canonical "If applied, this
commit will..." article, confirmed 200), which is the original source of
the rule the prose recites anyway.
2026-06-22 11:39:12 -04:00

16 KiB

Contributing

Requirements

To get started with Coder, the easiest way to set up the required environment is to use the provided Nix environment. Learn more how Nix works.

Nix

  1. Install Nix

  2. After you've installed Nix, instantiate the development with the nix-shell command:

    cd ~/code/coder
    
    # https://nix.dev/tutorials/declarative-and-reproducible-developer-environments
    nix-shell
    
    ...
    copying path '/nix/store/3ms6cs5210n8vfb5a7jkdvzrzdagqzbp-iana-etc-20210225' from 'https://   cache.nixos.org'...
    copying path '/nix/store/dxg5aijpyy36clz05wjsyk90gqcdzbam-iana-etc-20220520' from 'https://   cache.nixos.org'...
    copying path '/nix/store/v2gvj8whv241nj4lzha3flq8pnllcmvv-ignore-5.2.0.tgz' from 'https://cache.   nixos.org'...
    ...
    
  3. Optional: If you have direnv installed with hooks configured, you can add use nix to .envrc to automatically instantiate the development environment:

    cd ~/code/coder
    echo "use nix" >.envrc
    direnv allow
    

    Now, whenever you enter the project folder, direnv will prepare the environment for you:

    cd ~/code/coder
    
    direnv: loading ~/code/coder/.envrc
    direnv: using nix
    direnv: export +AR +AS +CC +CONFIG_SHELL +CXX +HOST_PATH +IN_NIX_SHELL +LD +NIX_BINTOOLS +NIX_BINTOOLS_WRAPPER_TARGET_HOST_x86_64_unknown_linux_gnu +NIX_BUILD_CORES +NIX_BUILD_TOP +NIX_CC +NIX_CC_WRAPPER_TARGET_HOST_x86_64_unknown_linux_gnu +NIX_CFLAGS_COMPILE +NIX_ENFORCE_NO_NATIVE +NIX_HARDENING_ENABLE +NIX_INDENT_MAKE +NIX_LDFLAGS +NIX_STORE +NM +NODE_PATH +OBJCOPY +OBJDUMP +RANLIB +READELF +SIZE +SOURCE_DATE_EPOCH +STRINGS +STRIP +TEMP +TEMPDIR +TMP +TMPDIR +XDG_DATA_DIRS +buildInputs +buildPhase +builder +cmakeFlags +configureFlags +depsBuildBuild +depsBuildBuildPropagated +depsBuildTarget +depsBuildTargetPropagated +depsHostHost +depsHostHostPropagated +depsTargetTarget +depsTargetTargetPropagated +doCheck +doInstallCheck +mesonFlags +name +nativeBuildInputs +out +outputs +patches +phases +propagatedBuildInputs +propagatedNativeBuildInputs +shell +shellHook +stdenv +strictDeps +system ~PATH
    
    🎉
    
    • If you encounter a creating directory error on macOS, check the troubleshooting section below.

Without Nix

If you're not using the Nix environment, you can launch a local DevContainer to get a fully configured development environment.

DevContainers are supported in tools like VS Code and GitHub Codespaces, and come preloaded with all required dependencies: Docker, Go, Node.js with pnpm, mise, and make.

For manual setup outside Nix and DevContainers, install Docker, mise, and make. Run mise install from the repository root to install Go, Node.js with pnpm, and development tools at the versions pinned in mise.toml.

Development workflow

Use the following make commands and scripts in development:

  • ./scripts/develop.sh runs the frontend and backend development server
  • make build compiles binaries and release packages
  • make install installs binaries to $GOPATH/bin
  • make test
  • make pre-commit runs gen, fmt, lint, typos, and builds a slim binary
  • make pre-commit-light runs fmt and lint for shell, terraform, markdown, helm, actions, and typos (skips gen, Go/TS lint+fmt, and binary build)
  • make pre-push runs heavier CI checks including tests (allowlisted)

Install the git hooks to run these automatically:

git config core.hooksPath scripts/githooks

The hooks classify staged/changed files and select the appropriate target. Commits that only touch docs, shell, terraform, or other lightweight files run make pre-commit-light instead of the full make pre-commit, and pre-push is skipped entirely. Changes to Go, TypeScript, SQL, proto, or the Makefile trigger the full targets as before.

Running Coder on development mode

  1. Run the development script to spin up the local environment:

    ./scripts/develop.sh
    

    This will start two processes:

    • http://localhost:3000 — the backend API server. Primarily used for backend development and also serves the static frontend build.
    • http://localhost:8080 — the Node.js frontend development server. Supports hot reloading and is useful if you're working on the frontend as well.

    Additionally, it starts a local PostgreSQL instance, creates both an admin and a member user account, and installs a default Docker-based template.

  2. Verify Your Session

    Confirm that you're logged in by running:

    ./scripts/coder-dev.sh list
    

    This should return an empty list of workspaces. If you encounter an error, review the output from the develop.sh script for issues.

    Note

    coder-dev.sh is a helper script that behaves like the regular coder CLI, but uses the binary built from your local source and shares the same configuration directory set up by develop.sh. This ensures your local changes are reflected when testing.

    The default user is admin@coder.com and the default password is SomeSecurePassword!

  3. Create Your First Workspace

    A template named docker is created automatically. To spin up a workspace quickly, use:

    ./scripts/coder-dev.sh create my-workspace -t docker
    

Deploying a PR

You need to be a member or collaborator of the coder GitHub organization to be able to deploy a PR.

You can test your changes by creating a PR deployment. There are two ways to do this:

  • Run ./scripts/deploy-pr.sh
  • Manually trigger the pr-deploy.yaml GitHub Action workflow.

Available options

  • -d or --deploy, force deploys the PR by deleting the existing deployment.
  • -b or --build, force builds the Docker image. (generally not needed as we are intelligently checking if the image needs to be built)
  • -e EXPERIMENT1,EXPERIMENT2 or --experiments EXPERIMENT1,EXPERIMENT2, will enable the specified experiments. (defaults to *)
  • -n or --dry-run will display the context without deployment. e.g., branch name and PR number, etc.
  • -y or --yes, will skip the CLI confirmation prompt.

Note

PR deployment will be re-deployed automatically when the PR is updated. It will use the last values automatically for redeployment.

Once the deployment is finished, a unique link and credentials will be posted in the #pr-deployments Slack channel.

Styling

Pull Requests

We welcome pull requests (PRs) from community members including (but not limited to) open source users, enthusiasts, and enterprise customers.

We will ask that you sign a Contributor License Agreement before we accept any contributions into our repo.

Please keep PRs small and self-contained. This allows code reviewers (see below) to focus and fully understand the PR. A good rule of thumb is less than 1000 lines changed. (One exception is a mechanistic refactor, like renaming, that is conceptually trivial but might have a large line count.)

If your intended feature or refactor will be larger than this:

  1. Open an issue explaining what you intend to build, how it will work, and that you are volunteering to do the development. Include @coder/community-triage in the body.
  2. Give the maintainers a chance to respond. Changes to the visual, interaction, or software design are easier to adjust before you start laying down code.
  3. Break your work up into a series of smaller PRs.

Stacking tools like Graphite are useful for keeping a series of PRs that build on each other up to date as they are reviewed and merged.

Each PR:

  • Must individually build and pass all tests, including formatting and linting.
  • Must not introduce regressions or backward-compatibility issues, even if a subsequent PR in your series would resolve the issue.
  • Should be a conceptually coherent change set.

In practice, many of these smaller PRs will be invisible to end users, and that is ok. For example, you might introduce a new Go package that implements the core business logic of a feature in one PR, but only later actually "wire it up" to a new API route in a later PR. Or, you might implement a new React component in one PR, and only in a later PR place it on a page.

Reviews

The following information has been borrowed from Go's review philosophy.

Coder values thorough reviews. For each review comment that you receive, please "close" it by implementing the suggestion or providing an explanation on why the suggestion isn't the best option. Be sure to do this for each comment; you can click Done to indicate that you've implemented the suggestion, or you can add a comment explaining why you aren't implementing the suggestion (or what you chose to implement instead).

It is perfectly normal for changes to go through several rounds of reviews, with one or more reviewers making new comments every time, then waiting for an updated change before reviewing again. All contributors, including those from maintainers, are subject to the same review cycle; this process is not meant to be applied selectively or to discourage anyone from contributing.

Releases

Coder releases are managed entirely through the release.yaml GitHub Actions workflow, triggered manually via "Run workflow" in the Actions tab. Release notes are automatically generated from commit titles and PR metadata.

Release types

Type Tag Source Purpose
RC (release candidate) vX.Y.0-rc.W main or branch Pre-release for testing
Create release branch vX.Y.0-rc.W main Cut release/X.Y + tag RC atomically
Release vX.Y.0 release/X.Y First release of a minor version
Patch vX.Y.Z release/X.Y Bug fixes and security patches

Workflow

RC tags can be created from main or from a release branch. The create-release-branch type creates release/X.Y and tags the next RC in one step, continuing the RC numbering sequence.

main:  --*--*--*--*--*--*--*--*--*--
              |  rc.0   rc.1  |
              |               +--- create-release-branch ---+
              |                                             |
              |            release/2.34:  --*-- rc.2 -- rc.3 -- v2.34.0
              |
              +-- (more RCs on main for next cycle)
  1. RC: Go to Actions > Release, click "Run workflow", select main (or a release branch) from the "Use workflow from" dropdown, choose rc, and optionally provide a commit SHA (defaults to HEAD). The workflow calculates the next RC version automatically.
  2. Create release branch: Select main in the dropdown, choose create-release-branch, and optionally provide a commit SHA. This creates release/X.Y and tags the next RC atomically.
  3. Release: Select the release branch (e.g. release/2.34) from the dropdown and choose release. No other inputs needed.
  4. Patch: Cherry-pick fixes onto release/X.Y, select that branch from the dropdown, and choose release.

The workflow validates that commits are on the expected branch for each release type.

Retrying a failed release

If the release.yaml workflow fails after the tag has been pushed, re-run the failed jobs from the GitHub Actions UI. The prepare-release job is idempotent and will detect the existing tag.

To test the workflow without publishing, select dry-run.

Commit messages

Commit messages should follow the Conventional Commits 1.0.0 specification.

Allowed commit types (feat, fix, etc.) are listed in conventional-commit-types. Note that these types are also used to automatically sort and organize the release notes.

A good commit message title uses the imperative, present tense and is ~50 characters long (no more than 72).

Examples:

  • Good: feat(coderd): add feature X
  • Bad: feat(coderd): added feature X (past tense)

Scopes must reference a real path in the repository (a directory or file stem) and must contain all changed files. For example, use coderd/database if all changes are within that directory. If changes span multiple top-level directories, omit the scope.

A good rule of thumb for writing good commit messages is to recite: If applied, this commit will ....

Note: We lint PR titles to ensure they follow the Conventional Commits specification, however, it's still possible to merge PRs on GitHub with a badly formatted title. Take care when merging single-commit PRs as GitHub may prefer to use the original commit title instead of the PR title.

Backporting fixes to release branches

When a merged PR on main should also ship in older releases, add the backport label to the PR. The backport workflow will automatically detect the latest three release/* branches, cherry-pick the merge commit onto each one, and open PRs for review.

The label can be added before or after the PR is merged. Each backport PR reuses the original title (e.g. fix(site): correct button alignment (#12345)) so the change is meaningful in release notes.

If the cherry-pick encounters conflicts, the backport PR is still created with instructions for manual resolution — no conflict markers are committed.

Breaking changes

Breaking changes can be triggered in two ways:

  • Add ! to the commit message title, e.g. feat(coderd)!: remove deprecated endpoint /test
  • Add the release/breaking label to a PR that has, or will be, merged into main.

Generative AI

Using AI to help with contributions is acceptable, but only if the AI Contribution Guidelines are followed. If most of your PR was generated by AI, please read and comply with these rules before submitting.

Security

Caution

If you find a vulnerability, DO NOT FILE AN ISSUE. Instead, send an email to security@coder.com.

The security label can be added to PRs that have, or will be, merged into main. Doing so will make sure the change stands out in the release notes.

Experimental

The release/experimental label can be used to move the note to the bottom of the release notes under a separate title.

Troubleshooting

Database migration mismatch after switching branches

If ./scripts/develop.sh exits with a "database migration conflict" error, it means the database has migrations from another branch that don't exist on the current one. You have two options:

# Roll back the mismatched migrations (preserves your dev data):
./scripts/develop.sh --db-rollback

# Or wipe the database and start fresh:
./scripts/develop.sh --db-reset

Nix on macOS: error: creating directory

On macOS, a direnv bug can cause nix-shell to fail to build or run coder. If you encounter error: creating directory when you attempt to run, build, or test, add a mkdir line to your .envrc:

use nix
mkdir -p "$TMPDIR"