---
key: release.format-as-you-touch-rebase
title: 2026-07-12 — Formatting-as-you-touch, enforced; rebase-not-merge for PR branches (#311 H11 + format CI)
status: active
since: '2026-07-12'
supersedes: none
superseded-by: none
rule: A blocking `format` CI job runs `dotnet format --verify-no-changes` scoped only to the PR's changed `.cs` files (never the legacy BOM backlog), and a PR branch must be kept current by rebasing on `origin/main` (never merging main in), enforced by `.husky/pre-push` → `prepush-rebase-check.sh`.
signals: 'format-as-you-touch, rebase not merge, BOM backlog · paths: `.husky/pre-push`, `.claude/hooks/prepush-rebase-check.sh` · issues: #311 (H11), #309, #310, #269, #312'
mechanics: '`docs/contributing.md` §7; `.claude/hooks/prepush-rebase-check.sh`; `npm run check:api`'
---

Two coupled process decisions, prompted when a stale docs branch *merged main in*, dragged ~17
legacy-BOM `.cs` files it never touched into the merge commit, and the pre-commit `dotnet format`
hook then blocked on code that wasn't the author's (#309 session; the BOM backlog is #310).

**1. Formatting-as-you-touch is the standing rule, and it is now *enforced* (not just prose).** ~2500
of ~3900 `.cs` files carry a legacy UTF-8 BOM that violates `.editorconfig`'s `charset=utf-8`. We do
**not** mass-reformat (a repo-wide normalization stays an unmade, separate decision). Instead, a file
you modify for other work must be normalized (`dotnet format`, incl. BOM strip) in that same PR.
Enforcement — previously only the `--no-verify`-bypassable pre-commit hook, which is how #269 landed 17
BOM files (CI never checked charset):
- a **blocking `format` CI job** runs `dotnet format --verify-no-changes` **scoped to the PR's changed
  `.cs`** (vs the merge-base) — so it demands conformance only of files the PR touched, never the
  untouched legacy 2500; a `.cs`-free PR skips the expensive steps and passes (always reports a status,
  safe as a required check). This closes the "CI never verifies charset/format" gap.
- `docs/contributing.md` §7 documents the rule.

**2. Keep a PR branch current by REBASING on `origin/main`, never merging main in (H11).** A merge
commit pulls in *every* file main changed — including files the author never touched — which then trip
the format hook/CI on code that isn't theirs; rebasing keeps the diff to exactly what changed.
Enforced by `.claude/hooks/prepush-rebase-check.sh` wired into `.husky/pre-push`: a push from a branch
that is behind `origin/main` (origin/main not an ancestor of HEAD) is **blocked** with
`git rebase origin/main` guidance. Fail-open (offline / no origin/main / not a repo → allow, since a
git hook has no "ask"); deliberate escape `ETV_SKIP_REBASE_CHECK=1`. This supersedes the old lore
guidance to "merge main into your PR branch." (After a rebase that conflicts in *generated* artifacts —
v1.json/v1.d.ts/endpoint-index — regenerate, don't hand-resolve; `npm run check:api` guards.)

Rationale, as with the whole hook program: make the process rule a derivation/hook, not prose to
remember (#303 methodology review). Tracked: #311; sibling #312 (H12 issue-qualification audit).
