
Changesets
- 12 installs
- 51 repo stars
- Updated August 4, 2026
- clerk/cli
Create or refresh a .changeset markdown file for the current branch, or report that no changeset is required.
About
Creates or refreshes a .changeset markdown file for the current branch or reports that none is needed. A developer uses it when work touches packages source files on a feature branch and a changeset entry is required.
- Detects whether a changeset is required for the branch
- Creates or updates the per-branch changeset slug
Changesets by the numbers
- 12 all-time installs (skills.sh)
- Ranked #179 of 248 Release Management skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/clerk/cli --skill changesetsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 12 |
|---|---|
| repo stars | ★ 51 |
| Last updated | August 4, 2026 |
| Repository | clerk/cli ↗ |
What it does
Create or refresh a .changeset markdown file for the current branch, or report that no changeset is required.
Files
/changesets create
Decide whether the current branch needs a changeset, then either write one or report that none is required. Full policy lives in references/policy.md; this file is the decision flow for the create subcommand.
Subcommand
create is the only supported subcommand. Any other argument: report unsupported and stop.
Workflow
1. Resolve the branch and base
git rev-parse --abbrev-ref HEAD
git fetch origin main --quiet
git diff --name-only --diff-filter=ACMR origin/main...HEADIf the current branch is main, stop and tell the user changesets are only generated on feature branches.
2. Classify changed files
A path is exempt when it matches any of:
.github/**.changeset/**docs/**scripts/**.claude/**- Root dotfiles or docs at repo root:
.gitignore,CLAUDE.md,
CONTRIBUTING.md, README.md
- Test-only files inside packages:
packages/**/*.test.ts,
packages/**/__tests__/**
Anything else (notably non-test files under packages/cli-core/src/** or packages/cli/**) is non-exempt and requires a changeset.
3. Branch on the result
All files exempt: run bun changeset status --since=origin/main to confirm. Then report to the user, naming the exempt categories that matched, and stop without writing a file. Do not create an empty changeset for fully exempt branches; changeset status already passes.
Some files non-exempt and `.changeset/<slug>.md` already exists for this branch: treat as a refresh. Read the existing file, then jump to step 5 with the existing slug.
Some files non-exempt, no changeset file exists, and every commit on the branch uses a non-user-facing prefix (refactor:, chore:, perf:, build:, ci:, style:, docs:, test:) and the diff has no observable effect on the CLI surface: skip to the empty-changeset path in step 7a.
Some files non-exempt, no changeset file exists, and the branch contains any user-facing commit (feat:, feat(, fix:, fix(, feat!:, fix!:) or the diff changes a flag, command, exported API, env var, output format, or any other user-visible surface: continue to step 4.
4. Pick the slug
Strip the conventional prefix from the branch name and kebab-case the rest. Drop username segments (wyattjoh/...).
| Branch | Slug |
|---|---|
feat/oauth-github | oauth-github |
fix/login-redirect-loop | login-redirect-loop |
wyattjoh/scripts-rules | scripts-rules |
If .changeset/<slug>.md already exists on main, suffix with -v2, -followup, or -part-2.
5. Decide the bump type
Read the cumulative branch diff (not just the latest commit), the PR title and body if a PR exists (gh pr view --json title,body 2>/dev/null), and recent commit subjects (git log origin/main..HEAD --pretty=%s).
| Intent | Bump |
|---|---|
| New user-facing feature, command, or flag | minor |
| Bug fix, perf, internal refactor with no behavior change | patch |
Breaking change (removed/renamed flag, incompatible behavior, feat!/fix!, BREAKING CHANGE footer) | major |
| Mixed scope | highest applicable |
Stop-and-ask gates. Pause, do not write, and prompt the user when:
1. The classification would be major. 2. Intent is genuinely ambiguous (refactor that may change observable behavior; dependency bump with unclear downstream effect). 3. The branch is a revert without a stated motivation.
When the split between minor and patch is unclear but the change is clearly non-breaking, default to patch and note the reasoning in the response.
6. Author the summary
- Imperative-present tense ("Add X", "Fix Y", "Remove Z"). Never past tense.
- User-facing language; a CLI user reads this. No internal file paths, class
names, or implementation details.
- One sentence ending in a period. Continuation lines only when the change
genuinely needs more context (each becomes an indented sub-bullet).
- Authored independently from the PR title. The PR title describes the
work; the summary describes the user-visible result.
- Do not include
(#123)orby @user;.changeset/changelog.jsappends
PR, commit, and author links at version time.
7. Write the file
Path: .changeset/<slug>.md. Content:
---
"clerk": <bump>
---
<summary>Only "clerk" is valid. @clerk/cli-core is in the ignore list in .changeset/config.json and must not appear.
Write the file directly with the Write tool. Do not run bun changeset add; it is interactive and --message only pre-fills the summary.
7a. Empty changeset path
When step 3 routed here, the branch touches non-exempt paths but has no user-facing impact. CI requires a file; an empty one satisfies enforcement without producing a changelog entry or version bump.
Generate it with:
bun changeset --emptyThis writes .changeset/<slug>.md with empty frontmatter (no package keys). Continue to step 8 to place the commit.
Skip steps 4 through 6: there is no slug to pick (the CLI generates one), no bump to choose, and no summary to author. When step 8 places a tip commit on a multi-commit branch, use the fixed title docs(changeset): add empty changeset since there is no summary.
8. Place the commit
Inspect the branch shape: git log origin/main..HEAD --oneline | wc -l.
| Branch shape | Action |
|---|---|
| Single commit | Stage .changeset/<slug>.md and amend the existing commit. |
| Multi-commit | Create a new tip commit titled docs(changeset): <summary>. |
When refreshing an existing changeset on a multi-commit branch, amend the existing tip docs(changeset): commit instead of stacking another.
Do not run `git push` or `gh pr edit` from this skill. Branches in this repo carry stack metadata in git config; ad-hoc push/edit corrupts it. Hand off to the stacked-prs:stacked-prs skill for any push or PR mutation.
9. Report
Tell the user:
- Which path the skill took: real changeset, refresh of an existing one,
empty changeset (internal-only non-exempt change), or skip (fully exempt branch). For skip, name the exempt categories that matched.
- For real or refresh: the chosen slug, bump type, and summary.
- For empty: the slug the CLI generated.
- The commit placement (amended vs. new tip commit).
- Any follow-up:
stacked-prs:stacked-prspush, PR description sync.
Examples
Exempt branch
Branch wyattjoh/scripts-rules touches only .claude/rules/scripts.md.
Report: "All changes are under .claude/** (exempt). No changeset required. bun changeset status will pass."
New feature
Branch feat/oauth-github adds GitHub OAuth to clerk login. Single commit.
Write .changeset/oauth-github.md:
---
"clerk": minor
---
Add GitHub as an OAuth provider for `clerk login`. Set `CLERK_GITHUB_CLIENT_ID` to enable.Stage and amend the existing commit.
Refresh after new commits
Branch feat/oauth-github already has .changeset/oauth-github.md (minor). A new commit adds GitLab support. Rewrite the summary to cover the cumulative diff; bump stays minor. Amend the tip docs(changeset): commit (multi-commit branch).
Internal-only non-exempt change
Branch refactor/extract-logger moves a helper inside packages/cli-core/src/lib/. Single commit, prefix refactor:, no flag, command, or output change. Run bun changeset --empty. The CLI writes .changeset/<generated-slug>.md with empty frontmatter. Stage and amend the existing commit. The next release skips a version bump for this change but the branch passes enforcement.
Anti-patterns
- Empty changeset to bypass enforcement on user-facing changes
(silently drops a real bump from the changelog).
- Past-tense or implementation-leaking summaries ("Added oauth.ts with
GithubProvider class").
- Manually appending
(#123)orby @user; the changelog renderer adds
these.
- Using
@clerk/cli-coreas the package key. - Creating a second changeset file on a branch that already has one (one
changeset per PR).
- Running
git pushorgh pr editdirectly from this skill.
References
- Full policy with rationale: references/policy.md
- Changelog renderer:
.changeset/changelog.js - Changesets config:
.changeset/config.json - Enforcement workflow:
.github/workflows/enforce-changeset.yml
Changeset Policy
Canonical rules for generating .changeset/*.md entries in this repo. The Enforce Changeset workflow (.github/workflows/enforce-changeset.yml) fails a PR when packages/** source changes without a corresponding changeset.
Exempt paths
A changeset is NOT required when the branch diff touches only files in these locations. bun changeset status exits 0 in this case because no tracked workspace package changed.
.github/**.changeset/**docs/**scripts/**.claude/**- Root dotfiles and docs at repo root:
.gitignore,CLAUDE.md,
CONTRIBUTING.md, README.md
- Test-only changes matching
packages/**/*.test.tsor
packages/**/__tests__/**
If a branch mixes exempt and non-exempt changes, generate a changeset for the non-exempt subset only.
Direct file write (do not use bun changeset add)
@changesets/cli add is interactive: it prompts for bump type and package selection. --message only pre-fills the summary, and --empty writes a zero-content changeset. Neither mode is suitable for programmatic generation. Write the file directly with the Write tool.
Frontmatter schema
---
"clerk": patch
---
Imperative-present summary on one line becomes the CHANGELOG bullet.
Optional continuation lines become indented sub-bullets.Rules:
- Only
"clerk"is a valid key.@clerk/cli-coreis in
.changeset/config.json's ignore list and must not appear.
- Values are exactly one of
patch,minor,major. - Do not manually include
(#123)orby @userin the body;
.changeset/changelog.js appends PR, commit, and author links at changeset version time.
File naming
Kebab-case slug of the branch name with the conventional prefix stripped. Drop username segments (e.g., wyattjoh/...).
| Branch | Filename |
|---|---|
feat/oauth-github | oauth-github.md |
fix/login-redirect-loop | login-redirect-loop.md |
perf/init-cold-start | init-cold-start.md |
wyattjoh/scripts-rules | scripts-rules.md |
ci/enforce-changeset | n/a (exempt; .github/** only) |
If the slug collides with an existing file on main, suffix with a short differentiator (-v2, -followup, -part-2).
Bump-type decision
Map the PR's dominant intent over the cumulative branch diff, not the conventional-commit prefix alone. A fix: PR that also adds a new public flag is a minor.
| Intent | Bump | Signal |
|---|---|---|
| New user-facing feature, command, flag | minor | feat:, new exported API, new CLI surface |
| Bug fix, perf, internal refactor with no behavior change | patch | fix:, perf:, refactor: |
| Breaking change | major | Removed flag, incompatible CLI behavior, BREAKING CHANGE footer, feat!: / fix!: |
| Mixed scope | highest | feat plus fix becomes minor |
Stop-and-ask gates
Pause and prompt the user. Do not guess.
1. Any classification that would produce major. Breaking changes need human sign-off. 2. Ambiguous intent (a refactor that may change observable behavior, a dependency bump with unclear downstream effects). 3. Revert commits without a stated motivation.
Default-to-patch rule
If the change is clearly non-breaking but the split between minor and patch is genuinely unclear (e.g., internal reorganization that incidentally exposes a new helper), use patch and note the reasoning in the response to the user.
Summary authoring
- Imperative-present tense ("Add X", "Fix Y", "Remove Z"). Never past
tense.
- User-facing language. A CLI user reads this; they do not know internal
file paths, class names, or implementation details.
- Authored independently from the PR title. The PR title describes the
work; the summary describes the user-visible result.
- One sentence ending in a period. Continuation lines only when the change
genuinely needs more context.
Example contrast:
| PR title | Changeset summary |
|---|---|
feat(cli): wire up OAuth GitHub provider to login command | "Add GitHub as an OAuth provider for clerk login." |
fix(init): register --app option shown in help examples | "Fix clerk init --app so it works when invoked from the help example." |
Optional body prefixes
.changeset/changelog.js recognizes these at the start of the body and consumes them before rendering. Rarely needed.
| Prefix | Effect |
|---|---|
pr: #<number> | Override the associated PR link. |
commit: <sha> | Override the commit link. |
author: @<login> | Manually attribute. Filtered through EXCLUDED_LOGINS (claude, claude[bot], claude-code[bot] are dropped). |
Commit placement
| Branch shape | Where the changeset lives |
|---|---|
| Single commit | Amended into the same commit as the code change. |
| Multi-commit | Its own commit at the tip, titled docs(changeset): <summary>. |
Single commits stay self-describing; multi-commit branches keep the changeset regeneratable by rewriting only the tip commit.
Update workflow on new commits
When pushing additional commits to a branch that already has a changeset:
1. Read the existing .changeset/<slug>.md. 2. Regenerate the summary from the cumulative branch diff vs main. Do not just append; rewrite so the summary matches the current state of the branch. 3. Escalate the bump type if the new commits warrant it:
patchplus new feat becomesminor.- Any escalation to
majortriggers the stop-and-ask rule.
4. Commit the regenerated changeset:
- Single-commit branch: amend.
- Multi-commit branch: amend the existing tip
docs(changeset):
commit. Do not stack a second one.
Do not create a second changeset file on the same branch unless the user explicitly opts into that. The repo convention is one changeset per PR.
bun changeset --empty
An empty changeset (frontmatter with no package keys) is valid when the branch touches non-exempt paths but has zero user-facing impact. CI still requires a file; an empty one satisfies enforcement and produces no changelog entry or version bump, which is the correct outcome for an internal-only change.
Use the empty path when all of the following hold:
- At least one non-exempt file changed (so a real exempt-skip is not an
option).
- Every commit on the branch uses a non-user-facing prefix (
refactor:,
chore:, perf:, build:, ci:, style:, docs:, test:).
- The diff has no observable effect on the CLI's surface (no flag,
command, exported API, env var, output format, or behavior change a user could detect).
Generate it with:
bun changeset --emptyPlace the resulting file with the same commit-placement rules as a real changeset (amend single-commit; new docs(changeset): tip on multi-commit).
Do NOT use an empty changeset to bypass enforcement on user-facing changes. If any commit is feat: / fix: (or a variant), or the diff changes anything a user sees, write a real changeset instead. Mixed branches (any user-facing commit) get a real changeset covering the user-visible portion.
When the branch is fully exempt (no non-exempt paths touched at all), changeset status already passes without any file. Skip; do not write an empty one in that case.
Examples
Good
.changeset/oauth-github.md on branch feat/oauth-github:
---
"clerk": minor
---
Add GitHub as an OAuth provider for `clerk login`. Set `CLERK_GITHUB_CLIENT_ID` to enable.Bad: past tense, implementation-leaking
---
"clerk": minor
---
Added oauth.ts with GithubProvider class wired into login.ts.Bad: empty frontmatter used to bypass enforcement
---
---
fix something