Skip to main content
ClaudeWave
Skill1k repo starsupdated today

release-sidecar

The release-sidecar skill automates the complete release workflow for the sidecar Go project, including semantic versioning, dependency updates via `go get`, go.mod validation, CHANGELOG management, and GoReleaser integration for automated binary distribution. Use this skill when preparing a new version for public release after verifying prerequisites like passing tests, clean git state, and proper credential configuration.

Install in Claude Code
Copy
git clone --depth 1 https://github.com/marcus/sidecar /tmp/release-sidecar && cp -r /tmp/release-sidecar/.claude/skills/release-sidecar ~/.claude/skills/release-sidecar
Then start a new Claude Code session; the skill loads automatically.

SKILL.md

# Releasing a New Version

## Prerequisites

- Go installed matching go.mod version
- Clean working tree (`git status` shows no changes)
- All tests passing (`go test ./...`)
- GitHub CLI authenticated (`gh auth status`)
- No `replace` directives in go.mod
- GoReleaser configured (`.goreleaser.yml` in repo root)
- `HOMEBREW_TAP_TOKEN` secret exists in GitHub repo settings

**Beware of go.work**: A parent `go.work` file can silently use local dependencies instead of published versions. Always use `GOWORK=off` when updating dependencies and testing builds.

## Release Process

### 1. Determine Version

Follow semantic versioning:
- **Major** (v2.0.0): Breaking changes
- **Minor** (v0.2.0): New features, backward compatible
- **Patch** (v0.1.1): Bug fixes only

```bash
git tag -l | sort -V | tail -1
```

### 2. Update td Dependency

Sidecar embeds td as a Go module. Always update to latest before releasing:

```bash
GOWORK=off go get github.com/marcus/td@latest
GOWORK=off go mod tidy
```

### 3. Verify go.mod

Ensure no `replace` directives (they break `go install`):
```bash
grep replace go.mod && echo "ERROR: Remove replace directives before releasing!" && exit 1
```

### 4. Verify Build Without go.work

```bash
GOWORK=off go build ./...
```

If this fails with "undefined" errors, the required dependency version hasn't been published yet.

### 5. Update CHANGELOG.md

```markdown
## [vX.Y.Z] - YYYY-MM-DD

### Features
- New feature description

### Bug Fixes
- Fix description

### Dependencies
- Dependency update description
```

```bash
git add CHANGELOG.md
git commit -m "docs: Update changelog for vX.Y.Z"
```

### 6. Create and Push Tag

```bash
git tag vX.Y.Z -m "Brief description of release"
git push origin main && git push origin vX.Y.Z
```

### 7. GitHub Release + Homebrew Tap (Both Automated)

Pushing the tag triggers `.github/workflows/release.yml`, which has **two jobs**:

1. **`goreleaser`** — creates the GitHub Release, builds/attaches binaries for
   darwin/linux (amd64/arm64), and generates `checksums.txt`. The GitHub release
   notes are auto-generated by GoReleaser from commit subjects (excluding
   `docs:`/`test:`/`ci:`/`chore:`) — these are separate from the hand-written
   `CHANGELOG.md`, which is bundled into the archives.
2. **`update-homebrew-tap`** — runs after `goreleaser`, computes the source
   tarball's sha256, and rewrites the tag URL + sha256 in
   `Formula/sidecar.rb` in `marcus/homebrew-tap`, then commits and pushes.

> GoReleaser-the-tool does not publish the formula (there is no `brews:` block,
> because the formula builds from source to dodge macOS Gatekeeper warnings).
> The bump is done by the **`update-homebrew-tap` job**, not by GoReleaser.
> Earlier versions of this skill said the tap had to be bumped by hand — that is
> no longer true for `sidecar.rb`.

### 7b. Manual Homebrew Steps (only when needed)

For a normal sidecar-only release you do **nothing** here — the
`update-homebrew-tap` job bumps `Formula/sidecar.rb` for you. The job is a no-op
if the formula is already current (it checks `git diff --cached --quiet`).

You only touch the tap by hand in two cases:

- **The job failed** (e.g. token/permission error). Re-run the
  `update-homebrew-tap` job from the Actions UI, or bump it manually:
  ```bash
  curl -sL "https://github.com/marcus/sidecar/archive/refs/tags/vX.Y.Z.tar.gz" | shasum -a 256
  cd /opt/homebrew/Library/Taps/marcus/homebrew-tap   # or clone marcus/homebrew-tap
  # Edit Formula/sidecar.rb — update the URL tag and sha256
  git add Formula/sidecar.rb && git commit -m "sidecar: bump to vX.Y.Z" && git push
  ```
- **Co-releasing td and/or nightshift.** The job only touches `sidecar.rb`. If
  this release also bumps the td dependency (step 2) or nightshift, update
  `Formula/td.rb` / `Formula/nightshift.rb` by hand the same way.

### 8. Verify

```bash
# Check workflow succeeded
gh run list --workflow=release.yml --limit=1

# Check release exists with binaries
gh release view vX.Y.Z

# Test Homebrew install
brew install marcus/tap/sidecar
sidecar --version

# Test go install (critical!)
GOWORK=off go install github.com/marcus/sidecar/cmd/sidecar@vX.Y.Z
sidecar --version
# Should output: sidecar version vX.Y.Z

# Test update notification
go build -ldflags "-X main.Version=v0.0.1" -o /tmp/sidecar-test ./cmd/sidecar
/tmp/sidecar-test
# Should show toast: "Update vX.Y.Z available!"
```

## Version in Binaries

Version is embedded at build time via ldflags:
```bash
go build -ldflags "-X main.Version=v0.2.0" ./cmd/sidecar
go install -ldflags "-X main.Version=v0.2.0" ./cmd/sidecar
```

Without ldflags, version falls back to:
1. Go module version (if installed via `go install`)
2. Git revision (`devel+abc123`)
3. `devel`

## Update Mechanism

On startup, sidecar checks `https://api.github.com/repos/marcus/sidecar/releases/latest`, compares `tag_name` against current version, and shows a toast if newer. Results cached for 3 hours. Pre-release suffixes (`-rc1`, `-beta`) are stripped for comparison. Dev versions skip the check.

## Recovery: Fixing a Bad Release

1. Publish a new patch release with fixes
2. For critical bugs, release immediately
3. Delete unpublished GitHub release: `gh release delete vX.Y.Z`
4. Keep git tags to preserve history
5. If GoReleaser workflow fails, re-run locally: `goreleaser release --clean`

## Install Methods

1. **Setup script**: `curl -fsSL https://raw.githubusercontent.com/marcus/sidecar/main/setup.sh | bash`
2. **Homebrew**: `brew install marcus/tap/sidecar`
3. **Download binary**: from GitHub Releases page
4. **From source**: `go install github.com/marcus/sidecar/cmd/sidecar@latest`

## Checklist

- [ ] Tests pass
- [ ] Working tree clean
- [ ] td dependency updated (`GOWORK=off go get github.com/marcus/td@latest`)
- [ ] No `replace` directives in go.mod
- [ ] Build works without go.work (`GOWORK=off go build ./...`)
- [ ] CHANGELOG.md updated
- [ ] Version follows semver
- [ ] Tag created and pushed
- [ ] Git