---
name: openclaw-changelog-update
description: Regenerate OpenClaw release changelog sections from git history before beta, stable, or extended-stable releases.
---

# OpenClaw Changelog Update

Use this for changelog rewrites and GitHub release-note source text. For regular
beta/stable, prepare complete notes before final-source qualification when
possible; Code SHA may then also be Release SHA. Editorial work may overlap
Code validation. Do not rerun it per validation-recovery backport; when backports
replace the Code SHA, rerun it once after the final Code SHA is green, as a
genuine CHANGELOG-only descendant under the existing product-evidence reuse
policy ([timing](../release-openclaw-maintainer/references/preparation.md#changelog-and-release-notes)). For
extended-stable, run it before final exact-head validation and tagging. Do not
rerun it for tooling retries, resumed publication, or promotion.
Use it with `release-openclaw-maintainer`; this skill owns changelog content,
ordering, grouping, and attribution discipline.

## Goal

Rebuild the target `CHANGELOG/<version>.md` release section from a complete, generated
history manifest, not stale draft notes. Produce grouped user-facing release
notes sorted by user interest while preserving every relevant issue/PR ref and
every verified human `Thanks @...` attribution.

`CHANGELOG.md` is the generated release index. The shared owner
`scripts/lib/release-changelog.mjs` resolves release sections and contribution
records from a working tree or a pinned Git ref, including historical refs
that still contain a monolith. Use `node scripts/release-changelog.mjs read
--version <version> [--ref <sha-or-tag>]` for a section, or add `--record` for
its contribution record. A missing split artifact must not fall back to the
root index. Historical releases without records gain no invented provenance.
Generated `CHANGELOG/**` files retain exact migrated or mirrored bytes and are
excluded from generic formatting, like the root changelog. Validate them through
their owner with `pnpm changelog:check`.

Current writes use the split layout. To save a complete initial section, use
`node scripts/release-changelog.mjs write --version <version> --file <section.md>`;
it updates the release entry, matching `CHANGELOG/records/<version>.md` when
present, and index together. Initial generation retains the section format
below. Published docs mirrors follow the separate post-release route at the end
of this skill; initial generation must never overwrite them.

## Inputs

- Target version: exact `YYYY.M.PATCH-beta.N` for a beta delta; `YYYY.M.PATCH`
  for cumulative stable notes. Beta entries and records are separate frozen
  artifacts, never copies of the stable-base section.
- Beta base tag: capture the version currently selected by public npm
  `openclaw@beta` before publication, not the last GitHub prerelease or the
  highest beta-suffixed version. The selector may name a stable version also
  selected by `latest`. Omit `--base` (or use `--base npm-beta`) with an exact
  beta version to resolve it once; preserve the resolved tag from the generated
  manifest and reuse that explicit `--base` for edits, verification and recovery.
  If npm already selects the candidate or a newer version, generation refuses
  to guess historical selector state; supply the previously captured tag. When
  that tag is not an ancestor (for example a stable closeout forward-port), the
  verifier uses its reachable merge base and subtracts the captured shipped
  tag's cumulative PR record through `--shipped-ref` semantics. The manifest and
  record retain both boundaries; replay with the captured tag to resolve the
  same range, never select an older GitHub beta instead.
- Stable base tag: last reachable shipped stable release tag. It must be an ancestor of
  the target; a newer but divergent tag is not a valid history boundary. Use
  an explicit shipped/main-closeout SHA only when it is also reachable from the
  target.
- Target ref: the exact product-complete history being documented. Its
  contribution-record target must be an ancestor of the final release target;
  it need not name a not-yet-created changelog commit. Include any later fixes
  before finalizing notes. Final notes may be committed before qualification,
  or afterward as a CHANGELOG-only descendant of a green Code SHA.
- Canonical main ref: current `origin/main`, fetched before verification. Release
  notes cite the original merged main PR when the same work is carried by a
  backport. A release-branch PR is used only while no forward-port exists on
  current main.

## Workflow

1. Confirm the release branch and exact history target:
   - `git fetch --tags origin`
   - confirm clean `git status -sb`
   - record `git rev-parse HEAD` as the history target
   - record the Full Release Validation run id and attempt when qualification already exists
   - finish pending product/version/backport changes before freezing final source; refresh the inventory for actual changes
2. Audit history, including direct commits:
   - `git log --topo-order --date=iso-strict --pretty=format:'%h%x09%ad%x09%s' <base-tag>..<target-ref>`
   - `git log --topo-order --grep='(#' --date=short --pretty=format:'%h%x09%ad%x09%s' <base-tag>..<target-ref>`
   - Include every commit reachable from the target but not the base, including merged side branches. Resolve PR associations before deduplicating and subtracting shipped records; retain the existing revert exclusions.
   - also inspect `--since='24 hours ago'` when main moved during the release.
3. Generate the complete contribution record and editorial manifest before
   writing grouped prose:

   ```bash
   node --import tsx .agents/skills/openclaw-changelog-update/scripts/verify-release-notes.mjs \
     --base <base-tag> \
     --target <target-ref> \
     --release-branch <canonical-release-branch> \
     --main-ref origin/main \
     --version <YYYY.M.PATCH> \
     --manifest /tmp/openclaw-release-<YYYY.M.PATCH>.json \
     --write-ledger
   ```

   For beta generation, use `--version YYYY.M.PATCH-beta.N` and omit `--base`
   on the first inventory run. Start an exact-version section with Highlights,
   Changes and Fixes using the shared writer; generate the delta manifest, then
   write prose for that range only. Subsequent runs use the captured npm baseline
   tag explicitly (not only a divergent range's merge-base SHA). Do not query npm again after publication or overwrite the
   cumulative stable entry. Regenerate cumulative stable notes at stable prep.

   Add repeatable `--release-provenance '<40sha> -> #PR[, #PR]'` inputs when
   release commits cannot carry provenance metadata. These use the same exact
   marker grammar and source validation as commit-body markers. A merged
   release-branch original is valid only while no corresponding current-main
   forward-port exists; its GitHub merge commit pins the original source.

   The verifier automatically reuses public GitHub GraphQL responses from an
   exact base/target SHA snapshot under the worktree's git metadata. Iterative
   rewrites at the same target avoid repeated network discovery. Use
   `--refresh-github-snapshot` after suspect API data, `--github-snapshot
   <path>` for an explicit artifact, or `--no-github-snapshot` for a live-only
   audit. GitHub release bodies are always read live.
   Explicit `CI #`, `CI run #`, `Actions run #`, and `workflow run #` references
   in active source are classified separately only when issue/PR resolution
   fails and a live same-repository Actions lookup confirms the exact run ID.
   Any ordinary occurrence of that number in active source, notes, or the
   contribution record remains a strict issue/PR requirement. Confirmed runs
   appear as `workflowRuns` in verification output and the manifest, never as
   PR associations or contributor credit.
   Only GitHub `NOT_FOUND` references confined to contextual commit-body text, absent from subjects, closing references, provenance, and notes/records, and below the highest resolved number may be omitted and reported as manifest `unavailableReferences` without credit.
   - the manifest is the required input to the rewrite, not an after-the-fact
     audit; it contains every referenced PR, eligible contributor credit,
     inline issue context, every direct commit, and an editorial-eligibility
     classification for PRs and direct commits
   - schema version 3 is the required ephemeral manifest contract. Regenerate
     older manifests; version 2 is not a supported downstream-reader boundary
   - for a historical backfill, add `--seed-ref <pre-backfill-ref>` once so
     contribution records from the prior changelog are retained even when an
     older merged commit omitted its PR number; the verifier excludes records
     for work reverted after the base tag, including beta work reverted before
     the stable release
   - generated provenance reports in-range PRs separately from retained
     seed-only PRs, then states the unique row total. A PR present in both
     inventories counts as in-range; never describe the seed-inclusive total
     as work merged in the current release range
   - add repeatable `--shipped-ref <prior-shipped-tag>` when the reachable main
     closeout differs from the shipped tag or later forward-port commits
     re-associate PRs that were already released. Each tag is a cumulative
     shipped boundary: the verifier unions explicit PR rows from complete
     contribution records in numbered release sections, excludes only overlapping PRs,
     and ignores `Unreleased`. Never infer this boundary from the base SHA,
     target prose, or target record. The manifest and generated provenance retain
     each tag plus the exact excluded PR inventory and count for deterministic
     candidate validation
   - source PR discovery bounds GitHub commit associations to the selected target
     history, or the frozen main history for canonical carriers. A contextual
     source reference becomes a contribution only when its merged commit is
     reachable in the target history and its merge time is within the target.
     Keep all references resolvable, but do not promote unrelated PRs merely
     because they merge while release preparation continues. Explicit seeds
     retain their historical membership and remain seed-only unless independently
     proven in-range. Resolve every association page; existing canonical,
     cherry-pick, and provenance contracts remain authoritative.
   - explicit multi-commit reverts require a revert subject and one standalone
     `Reverts <full SHA> and <full SHA>.` declaration (comma-separated lists
     with final `and` also work). The exact ending ` to restore the previous behavior.`
     is accepted. Duplicate, abbreviated, embedded, or repeated declarations do
     not establish reversal. Each named commit must be a single-parent ancestor,
     and reverse-applying all named patches must reproduce the complete revert
     tree. Recognized declarations that fail this proof stop verification. Proof
     uses private Git index/object storage without hooks or external diffs;
     canonical single-revert and revert-of-revert accounting stays intact.
   - canonicalize backports to the original merged PR on `main`: explicit
     cherry-pick origins win, then a unique normalized-subject match requires
     the same author and an overlapping changed path. Suppress release/backport
     PRs whenever the corresponding main PR exists on current `origin/main`.
     Keep a release-branch PR only when that change landed there first and has
     not yet been forward-ported to `main`
   - read the manifest before editing `### Highlights`, `### Changes`, or
     `### Fixes`; do not carry old grouped prose forward without re-auditing it
   - inspect linked PRs/issues or diffs for ambiguous commits. Direct commits
     are editorial input, not public ledger rows; infer material user outcomes
     from subject, body, touched files, tests, and nearby commits

4. Rewrite one selected release section only:
   - beta: `## YYYY.M.PATCH-beta.N`, containing only changes since the captured npm beta baseline
   - stable: `## YYYY.M.PATCH`, cumulative since the previous stable baseline
   - do not leave a stale `## Unreleased` section above the target release
   - if `Unreleased` contains release-bound notes, fold them into the target
     section instead of deleting them
5. Section shape:
   - `### Highlights`: 5-8 bullets for stable, 0-8 for a beta delta; do not invent highlights for small hotfix betas
     - include only a clear user-visible capability or workflow unlock, a
       material reliability/safety fix, a broad cross-surface improvement, or
       a release-defining integration/compatibility milestone
     - every highlight must say what changed for a user in one sentence; use
       one user story per bullet and group its supporting PRs
     - exclude tests, CI, refactors, docs, catalog churn, and implementation
       detail unless the outcome is a material install/update, data-safety, or
       widely visible user improvement
   - `### Changes`: new capabilities and behavior changes
   - `### Fixes`: user-facing fixes first, grouped by impact and surface
   - group related changes/fixes by surface and user impact; avoid one bullet
     per tiny commit when several commits tell one user-facing story
   - `### Complete contribution record`: generated PR-first record after the
     grouped prose; it is the exhaustive accounting surface, not a second
     release summary
6. Preserve verified attribution:
   - PR authorship comes from the canonical GitHub PR, never the release commit
     author, committer, or backport operator. Resolve coauthors from that PR's
     original merge commit; release-carrier trailers and contextual mentions
     do not establish credit on another PR
   - rebuild generated thanks from PR, original coauthor, and linked-reporter
     evidence. Seeds retain membership and references, not unverified old
     thanks. Regeneration replaces stale generated credit in records and grouped
     prose; verification rejects both missing and unsupported credit
   - when a vanished PR meets the existing exact-SHA recovery contract, retain
     its recorded thanks as historical credit without inventing a PR author
   - keep `#issue`, `(#PR)`, `Fixes #...`, and `Thanks @...`
   - every human-authored merged PR represented by a user-facing entry needs
     its PR ref and `Thanks @author`, even when the PR had no linked issue
   - every human issue reporter for a `Fixes #...` or referenced bug issue
     represented by a user-facing entry needs `Thanks @reporter` unless the
     same handle is already thanked in that bullet
   - every human `Co-authored-by` contributor on represented user-facing work
     needs `Thanks @handle` when a GitHub handle is known
   - when grouping multiple PRs/issues in one bullet, include every relevant
     PR/issue ref and every human contributor handle in that same bullet
   - multiple `Thanks @...` handles in one bullet are expected; do not drop or
     collapse contributor credit just because the note is grouped
   - if one grouped bullet covers both direct commits and PRs, keep all PR refs
     and thanks, plus any issue refs and human credit from the direct work
   - issues remain normal inline `#NNN` references. Do not add a separate
     linked-issues inventory. The generated PR record keeps source issues
     inline as `Related #NNN` on the PR that shipped them
   - when backfilling an older linked-issues inventory, preserve reporter
     credit inline for every GitHub-confirmed closing PR relationship. Do not
     infer a PR relationship from a generic cross-reference event, invent an
     unrelated PR link for a standalone report, or recreate the retired
     inventory
   - the complete contribution record lists every verified in-range PR and
     explicitly retained seed-only PR exactly once as `**PR #NNN**`. Discovery
     preserves canonical/cherry-pick provenance and requires frozen-history
     membership for contextual references; inline context alone cannot create a
     contribution row. It preserves author/co-author credit and any issue
     references in the original title
   - the provenance arithmetic and unique total must match the rendered PR
     rows exactly; candidate validation rejects malformed or forged counts
   - direct commits remain in the manifest with GitHub-resolved author,
     co-author, issue, and editorial-eligibility data. They inform grouped
     prose but are never rendered as a public `#### Direct commits` dump. Add
     direct-commit credit to a grouped bullet only when it shares an explicit
     closing issue reference or at least two distinctive subject terms
   - the verifier rejects ordinary `docs`, `test`, `refactor`, `ci`, `build`,
     `chore`, and `style` PRs in Highlights, Changes, or Fixes. An explicit
     Conventional Commits `!` marker makes any type editorial-eligible; include
     its verified user-facing breaking change and migration guidance with the
     original PR ref and credit. Keep other internal contributions only in the
     complete PR record
   - classify conventional titles from their declared type and scope, not
     incidental words such as `doc` or `build` in their descriptions. For
     untyped titles, retain internal-work signals such as `QA`, `test`, `docs`,
     `refactor`, `lint`, or `CI`; eligibility never replaces the source audit
     that establishes a user-visible outcome
   - do not add GHSA references, advisory IDs, or security advisory slugs to
     changelog entries or GitHub release-note text unless explicitly requested
   - credit verified human authors, including maintainers such as `@steipete`.
     Never thank bots, known automation accounts such as `@steipete-oai`,
     `@claude`, `@codex`, `@openclaw`, or `@clawsweeper`
   - do not use GitHub's release contributor count as the source of truth; the
     changelog must carry the complete human credit set itself
7. Sorting preference:
   - security/data-loss and content-boundary fixes
   - transcript/replay/reply delivery correctness
   - channels and mobile integrations
   - providers/Codex/local model reliability
   - install/update/release path reliability
   - performance and observability
   - docs and contributor-only/internal details last or omitted
8. Keep bullets single-line unless existing file style forces otherwise. Avoid
   internal release-process noise unless it changes user install/update safety.
9. Check release-note side conditions:
   - inspect `src/plugins/compat/registry.ts`
   - inspect `src/commands/doctor/shared/deprecation-compat.ts`
   - if a deprecated compatibility record reaches `removeAfter`, remove it when
     proven safe or move it to `removal-pending` and record the blocker; keep a
     due `removal-pending` record only until its documented conditions are met
10. Validate and ship:

- after the manifest-driven rewrite, regenerate and verify the complete
  contribution record before committing:
  ```bash
  node --import tsx .agents/skills/openclaw-changelog-update/scripts/verify-release-notes.mjs \
    --base <base-tag> \
    --target <target-ref> \
    --main-ref origin/main \
    --version <YYYY.M.PATCH> \
    --manifest /tmp/openclaw-release-<YYYY.M.PATCH>.json \
    --write-ledger
  ```
- the command fails when any `#NNN` reference in release history or the
  rendered release section cannot resolve, when reverted work is presented
  as shipped, when a source PR is absent from the contribution record, when
  direct commits are rendered as a public record dump, when non-editorial
  PRs appear in grouped prose, or when an eligible PR author or known
  co-author is missing from that PR's `Thanks @...` credit, or unsupported
  credit appears in its record or represented editorial bullet. It also fails
  before history col