Skip to content

Commit b447a25

Browse files
alicodingclaude
andcommitted
docs: ADR-0034 — trunk-based + PR-per-goal + ruleset-gated main (CI operating model)
Owner-ratified full-go after a primary-source research pass. The ignored-pipeline failure the owner remembered from past AI-agent projects turned out to be ON RECORD in this repo's own CI history: 5 of the last 11 runs cancelled (rapid direct pushes outrunning a 43-minute suite under cancel-in-progress) and then pushing stopped for 273 commits. Key mechanical finding: required status checks cannot gate a direct push (a SHA needs an already-recorded status), so the PR flow is the only way green-before-main is enforceable — adopted as PR-per-goal with owner bypass 'for pull requests only', self-merged when green, no review ceremony. Push cadence at least per session. Merge queue rejected (solo); CI path filtering deferred (skipped-status/required- checks footgun, documented). CLAUDE.md + SPEC §1.3 updated in-change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FYwojT8GdUbYSoggbvEFft
1 parent 8b43091 commit b447a25

3 files changed

Lines changed: 123 additions & 0 deletions

File tree

CLAUDE.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,21 @@ anything secret-shaped, and never force-push, amend a previous commit, or
7373
rewrite history without being explicitly asked — this rule covers regular
7474
commits, not destructive git operations.
7575

76+
**Deliver through short-lived branches + a PR per goal; push at least
77+
once per session — never let unpushed work accumulate**
78+
([ADR-0034](docs/adr/0034-git-ci-operating-model.md), owner-ratified
79+
after this repo's own CI history demonstrated the ignored-pipeline
80+
failure). `main` is ruleset-protected: direct pushes are blocked (even
81+
for the owner — bypass is "for pull requests only"), the CI checks are
82+
required, and a green PR self-merges without waiting on anyone. So: a
83+
goal's work lands as ONE self-merged PR when its scope completes
84+
(quick out-of-goal fixes ride the next goal's PR or a small dedicated
85+
one); worktree/agent branches live only as long as their one task,
86+
then merge and delete. Required checks can't gate a direct push at all
87+
(GitHub evaluates a SHA's already-recorded status, never runs checks
88+
at push time) — the PR flow is what makes green-before-main real, not
89+
review ceremony.
90+
7691
If `docs/SPEC.md` marks something `OPEN`, do not silently resolve it by
7792
implementing one option — surface the choice.
7893

docs/SPEC.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,19 @@ and [`docs/adr/0002-cicd-pipeline-phased-rollout.md`](adr/0002-cicd-pipeline-pha
286286
merge-blocking except govulncheck. Precedent: wailsapp/wails's own v3 CI
287287
(native-OS matrix, no GoReleaser — wailsapp/wails#747 closed wont-fix).
288288
`LOCKED`
289+
- **Operating model: trunk-based locally, PR-per-goal to a
290+
ruleset-gated `main`, push at least per session — `LOCKED`,
291+
[ADR-0034](adr/0034-git-ci-operating-model.md)** (owner-ratified
292+
2026-08-11 after research found the ignored-pipeline failure mode on
293+
record in this repo's own run history: rapid direct pushes
294+
cancelling a 43-minute suite until pushing stopped). Direct pushes
295+
to `main` are blocked for everyone including the owner (bypass =
296+
"for pull requests only"; a green PR self-merges reviewer-free);
297+
required checks make green-before-main mechanical, not aspirational
298+
— GitHub can't gate a direct push on checks at all. Secret-scanning
299+
push protection enabled ahead of the 273-commit catch-up push. CI
300+
path filtering deliberately deferred (required-checks/skipped-status
301+
footgun — the ADR has the reasoning).
289302
- **Linux server-mode builds require `CGO_ENABLED=0` explicitly** — not
290303
optional, confirmed by actually building natively in a linux/amd64
291304
container, not assumed. Without it, Wails3's own
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# ADR-0034 — Git/CI operating model: trunk-based, PR-per-goal, ruleset-gated main
2+
3+
Status: accepted (owner-ratified 2026-08-11, "full go" on the researched
4+
recommendation).
5+
6+
## Context
7+
8+
CI (ADR-0002, `.github/workflows/ci.yml`) existed but was dormant: main
9+
went unpushed for 273 commits over 5 days, so the cloud pipeline gated
10+
nothing. The owner asked for researched precedent before resuming,
11+
naming a specific past failure: AI-agent projects where branch/PR
12+
sprawl grew until "we ended up always ignoring the pipeline."
13+
14+
The research pass found that failure ON RECORD in this repo's own
15+
history: of the last 11 Actions runs (2026-08-06), 5 were cancelled and
16+
3 failed — rapid direct pushes to main were outrunning a suite that
17+
took up to 43 minutes, `cancel-in-progress: true` killing each previous
18+
run, so the signal never meant anything; pushing then stopped entirely.
19+
The ignored pipeline was a mechanism, not a mood.
20+
21+
Load-bearing mechanical finding (GitHub's own docs + community
22+
confirmation): **required status checks cannot gate a direct push** — a
23+
check must already have passed on that exact SHA before the ref update
24+
is allowed, and GitHub never runs checks synchronously at push time. A
25+
never-seen SHA pushed directly at a check-protected branch is rejected
26+
as "no status". So "main never receives an un-CI'd commit" is only
27+
expressible through a branch/PR-shaped flow; there is no
28+
direct-push-with-checks configuration to prefer instead.
29+
30+
## Decision
31+
32+
- **Local discipline unchanged**: commit every verified change to the
33+
working branch, gated by lefthook's full local mirror (CLAUDE.md's
34+
standing rule). Trunk-based development's own guidance for
35+
very-small teams, already in effect.
36+
- **`main` is protected by a GitHub Ruleset** (not classic branch
37+
protection — legacy per GitHub's own docs): restrict deletions, block
38+
force pushes, require a pull request, require the CI status checks,
39+
with the owner on the bypass list as **"for pull requests only"**
40+
direct pushes are blocked even for the owner, but a green PR
41+
self-merges with no reviewer requirement (there is no second person).
42+
- **One PR per delivered goal** (`docs/goals/BACKLOG.md`'s own delivery
43+
unit): the PR is the CI gate plus a durable, skimmable change
44+
artifact, and feeds `release.yml`'s `--generate-notes`. Never
45+
PR-per-commit (ceremony), never omnibus.
46+
- **Push cadence: at least once per completed goal, ideally per
47+
session** — an unpushed batch is a bisect-blind spot (a multi-commit
48+
push produces ONE workflow run, on the head commit only; CI never
49+
evaluates the intermediate SHAs). No tool enforces cadence; the
50+
ruleset removes direct-push as the path of least resistance, and
51+
CLAUDE.md records the habit.
52+
- **Worktree branches stay short-lived** (Anthropic's own worktree
53+
lifecycle: branch → PR/merge → remote branch deleted → worktree
54+
recycled), never a second long-lived line of history.
55+
- **Secret-scanning push protection enabled before the catch-up push**
56+
(free on public repos; blocks recognized secret patterns before they
57+
reach the remote) — the catch-up carries 273 commits of history never
58+
passed through that net, plus a local heuristic sweep of the unpushed
59+
range first.
60+
- **Catch-up sequencing**: one final direct push of main (CI runs on
61+
its head), THEN the ruleset activates — so the gate never has to
62+
block the push that establishes it.
63+
64+
## Rejected / deferred
65+
66+
- **Merge queue**: GitHub's own positioning is concurrent-merger teams;
67+
one human merging one PR at a time has no queue to manage.
68+
- **CI path filtering** (skip the heavy matrix for docs-only changes) —
69+
deliberately DEFERRED, not adopted with the rest: `paths`-filtered
70+
workflows report no status at all for skipped runs, which makes
71+
required checks hang at "Expected" and block the PR forever — a
72+
documented footgun whose workarounds (no-op twin workflows,
73+
job-level change detection) add real complexity. Mill's same-change
74+
rule (docs ride with the code that motivates them) makes docs-only
75+
PRs rare, so the savings are small today. Revisit if PR wall-clock
76+
cost becomes a felt problem; the cancellation cascade this was meant
77+
to help with is already structurally fixed by per-ref concurrency
78+
plus the PR flow.
79+
- **Force-push/history rewrites**: unchanged — CLAUDE.md's existing
80+
"never without being explicitly asked" rule; now also enforced
81+
server-side by the ruleset.
82+
83+
## Consequences
84+
85+
- Agent sessions targeting a goal work on a short-lived branch (or
86+
worktree) and end by opening/merging the goal's PR once CI is green;
87+
quick fixes outside a goal ride the next goal PR or a small
88+
dedicated one.
89+
- CI must stay green-and-trusted to stay authoritative
90+
(trunkbaseddevelopment.com: a red-often build "is of greatly reduced
91+
value") — a persistently red or flaky job is a defect to fix, never
92+
to rerun-until-green.
93+
- The ruleset's live rejection behavior gets one manual dry-run
94+
verification (attempt a direct push after activation) rather than
95+
being trusted from docs alone.

0 commit comments

Comments
 (0)