docs(adr): ADR-0010 — repo settings convention + executable sweep (rebase-default merges) - #297
Merged
Merged
Conversation
Closes the settings half of #296. Settings had diverged into three different configurations across four repos, none matching what CLAUDE.md claimed. `Homelab` had squash DISABLED, so the documented merge command failed outright ("Squash merges are not allowed on this repository") — found only when merging #294. SmartHome and BuildLab were still at GitHub defaults from their extraction (#280/#281): merge commits allowed, merged branches never deleted (Homelab accumulated 42 stale remote branches before a prune — exactly what delete_branch_on_merge prevents). Decision: rebase by default, squash as the escape hatch, merge commits never, delete-on-merge everywhere. Both rebase and squash give a linear history — the real axis is commit granularity, so the author picks per PR: keep the commits when each tells part of the story, squash when the branch is WIP-heavy. The convention is a SCRIPT, not a paragraph (scripts/align-repo-settings.sh). A fresh GitHub repo starts at defaults and this project creates one per stack extraction, so a prose rule drifts on the next extraction by construction. Idempotent, read-then-write (a no-op run makes zero mutating calls), and it skips archived repos — Komodo and ServArr are read-only by design, which is correct state rather than drift. Applied: 6 live repos aligned, 2 archived skipped, re-run reports all OK. Two portability bugs worth noting, both hit while writing it: `mapfile` needs bash 4 (macOS ships 3.2), and a literal U+2192 arrow directly after `$have_rebase` got its first UTF-8 byte absorbed into the identifier by byte-oriented bash, failing with "unbound variable" — same class as the CP1252 provisioner corruption found during the BuildLab VM work. Refs #296 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #296.
What was wrong
Repo settings had diverged into three different configurations across four repos, none of which matched what
CLAUDE.mdclaimed ("all homelab repos squash-merge with auto-delete"). The full sweep found 8 of 8 live repos drifted — not one was correct.Two concrete consequences:
Homelabhad squash disabled entirely, so the documented merge command simply failed —gh pr merge 294 --squash→ "Squash merges are not allowed on this repository." Found only by attempting a merge.Homelabitself had accumulated 42 stale remote branches before a prune — exactly whatdelete_branch_on_mergeprevents.Root cause is structural: a fresh GitHub repo starts at defaults, and this project creates a repo every time a stack is extracted (ADR-0008). A convention that lives only in prose drifts on the next extraction by construction.
Decision (ADR-0010)
Rebase by default, squash as the escape hatch, merge commits never, delete-on-merge everywhere.
One correction to the framing that shaped this: squash and rebase both produce linear history — neither makes a merge commit. The real axis is commit granularity. So the author picks per PR: keep the commits when each tells part of the story (#294's four commits — feature / live-testing fixes / keepalives / docs — collapsed into one under squash-only), squash when the branch is WIP-heavy.
The convention is a script, not a paragraph
scripts/align-repo-settings.shasserts it across the org, idempotently:Read-then-write, so a no-op run makes zero mutating API calls.
--dry-runreports drift only.Already applied — 6 live repos aligned, re-run confirms idempotent.
Two findings worth recording
403 Repository was archived. That's correct state for a retired stack, not drift, so the script skips them instead of failing. They get noCLAUDE.mdeither.mapfileneeds bash 4 (macOS ships 3.2), and a literal→directly after$have_rebasehad its first UTF-8 byte absorbed into the identifier by byte-oriented bash, failing withunbound variable. Same class as the CP1252 provisioner corruption from the BuildLab VM work — worth remembering that any multibyte char abutting a$varin a shell string is a latent bug.Companion PRs —
CLAUDE.mdfor every stack repoNo stack repo had a
CLAUDE.mdat all, so an agent opened directly in one had no guidance: not the PR-only workflow, not the add-only guardrails, not even that converge runs from the superproject. Five PRs add a thin pointer + stack specifics file (layered, not copied — seven full copies would drift the same way the settings did):Each states only what's genuinely local — ID block, VLAN, members, and that stack's real gotchas (e.g. BuildLab's no-Joliet ISO and VS2026
18/stablechannel; Azure'skeyctl-cannot-be-set-by-API-token note, which the podman work cites).Note
Does not touch #295 (the Actions/runner breakage from the same migration) — that needs org-admin and is tracked separately.
🤖 Generated with Claude Code