Branches and Releases

Work collects on dev, and a release pull request moves it to main, which builds and deploys. Pushes and merges to any other branch run nothing.

Where work lands

Every merge to main deploys the relay (relay.yml) and, after a version bump, the lnk release (release.yml, on macOS runners, which cost the most). So main only moves when a release is ready.

  • dev is where work lands. Branch from dev, with one branch and one PR per batch of related work, each change its own commit, and the PR against dev. Don't bump the version in these PRs. Fill in the PR template's Release notes (Breaking, Added or Fixed, one line per user-visible change) and its Semver line, and keep them and the description current as commits are added, because the release PR reads them to pick the version. Say in the description how to try the branch.
  • main is what's released. It takes release PRs from dev, and the rare hotfix described below.
  • Trying unreleased work takes git fetch && git checkout <branch> && cargo build, or an install of lnk and its plugins from a branch with cargo, as Install lnk says.

Cutting a release

A release is a version bump on dev and a pull request from dev into main.

  1. List what dev adds: the PRs merged into dev since the last release (git log --merges --first-parent origin/main..origin/dev), and their Release notes.
  2. Pick the version from the largest change, against the current version in the root Cargo.toml. Before 1.0, Breaking or Added bumps the minor version (0.11.0 -> 0.12.0) and Fixed bumps the patch (0.11.0 -> 0.11.1). From 1.0, Breaking is major, Added minor and Fixed patch. If nothing is user-visible, merge without a bump, and only the relay is rebuilt.
  3. On dev, commit the bump: version under [workspace.package], then cargo build so Cargo.lock follows. The release also ships Link Harness, Link's tools and the OpenClaw, Hermes and DeepSeek Harness adapters, which lnk installs only at its own version. So bump version in each one's Cargo.toml (link-harness, link-tools's [workspace.package], link-openclaw, link-hermes, link-deepseek) and run cargo build there as well, because release.yml fails if one differs. That commit is the only change the release makes itself.
  4. Open a PR from dev to main titled Release v<version>. Its body is the combined Release notes (Breaking, Added, Fixed), each line with its PR number.
  5. Before merging, run the checks on dev's head. checks.yml runs them again after the merge and publishes nothing if they fail.
  6. Merge with a merge commit, never a squash or rebase, or dev and main stop sharing history. The merge publishes v<version> and relay-latest.

A hotfix that can't wait for dev branches from main and goes in with its own PR and a patch bump. Then main is merged back into dev.

What a release publishes

A release publishes lnk with its plugins and the relay, signs both, and deploys the website.

  • checks.yml runs fmt, clippy, tests and shellcheck on release PRs into main only. The deploy workflows call it first and publish nothing if it fails.
  • lnk (release.yml) publishes when the workspace version has no release yet. It makes v<version>, the latest release of woodpav/link-releases, with one archive per program and platform (lnk-core-<target> and lnk-<plugin>-<target>) for macOS and Linux, each on arm64 and x86_64, with Linux arm64 built on GitHub's arm64 runner. It adds the old all-in-one archive for 0.10 and older, all under one signed SHA256SUMS. The plugins are the core's PLUGINS in src/local/cli/src/main.rs, which release.yml names again, and src/tests/e2e/tests/release.rs checks the two match. Users get lnk from curl -fsSL https://local.link/install.sh | sh, which the relay serves from src/local/cli/install.sh, built into its binary. They get plugins from lnk up and lnk plugin add, and updates from lnk upgrade. The relay advertises its own version as the latest client in Welcome, so after a bump connected CLIs print Update lnk X is available.
  • The relay (relay.yml) publishes on every relevant merge to main, and needs no version bump. It makes a static x86_64 Linux link-relay and the deploy files as relay-latest, a pre-release and never "latest", here and in woodpav/link-releases. bootstrap.sh and the release key are not in it, because operators run bootstrap from their checkout. Publishing removes any asset the build no longer has. Servers install it within about 5 minutes.
  • Signing. Both releases sign SHA256SUMS with the RELEASE_SIGNING_KEY secret, using the repository's .github/sign-release.sh. The job runs only from main, in the release environment, which only main may use and a reviewer approves. release.rs checks that every job using a secret does the same. Relay servers, lnk upgrade (link_plugin::release) and install.sh accept only the keys in src/shared/release-signers. install.sh and bootstrap.sh embed a copy, and src/tests/e2e/tests/release.rs checks they match.
  • The website. The last step of release.yml calls the VERCEL_DEPLOY_HOOK secret, so the website goes live with the release it describes. Without the secret it warns and skips.

The bare domain's /_link/* and /install.sh belong to the relay. With LINK_WEBSITE set, which is --website in bootstrap, any other path there redirects to the website (link-web, on Vercel).