Skip to content

git-workflow

Recipe card from the charly-internals plugin (Development — contributor internals).

This card has additional detail pages:

git-workflow — branch-per-change, PR-only agent-validated landing

Section titled “git-workflow — branch-per-change, PR-only agent-validated landing”

Every change to an OpenCharly repo lands through ONE discipline: a pull request that a FRESH pr-validator agent independently validates and merges. A direct push to main is FORBIDDEN and mechanically disabled — GitHub branch protection (enforce_admins) + the pre-push-gate block it in every repo. The R10 pass authorizes OPENING the PR, never a self-merge: the two-step landing separates the author (who opens the PR) from the fresh evaluator (who validates, merges, tags). This skill is the mechanics; the project rulebook “Post-Execution Policies” (AGENTS.md / CLAUDE.md) carries the mandate, /charly-internals:cutover-policy the one-phase rule, /charly-build:migrate the schema-version/tag coupling, and plugins/internals/agents/pr-validator.md the evaluator’s own spec.

  • No direct push to main (the project rulebook’s PR-only landing mandate — see “Post-Execution Policies”). Enforced by GitHub branch protection (the charly/pr-validator status + a PR + linear history + enforce_admins) and the pre-push-gate locally. Organization-wide apply/verify is owned only by opencharly/.github/scripts/branch-protection.sh.
  • Never force-push or amend a pushed branch (mandate, same rulebook section). The flow never needs one: feat/ advances only by ADDING commits (the author’s change, any review-round fix commits, then the evaluator’s merge-time version stamp), and the squash-merge collapses them; a stale feat/ catches up with gh pr update-branch (a merge, NOT a rebase-force); tags are add-only. Amend only before the first push.
  • R10-gated; the merge requires the fresh pr-validator’s green status. R10 PASS authorizes opening the PR (with pasted evidence); a rule violation or R10 FAIL means no green charly/pr-validator status and no merge — fix in the same tree, re-run R10, re-push; the status resets and the evaluator re-runs.
  • Zero warnings is part of R10 (project rulebook R1). A version-mismatch warning clears with charly box reconcile; any other warning gets /charly-internals:root-cause-analyzer then a real fix — “warning” is never an accepted end state.
  • Atomic on main, never on feat/. The evaluator’s --squash folds the author’s change, any review-round fix commits, and the merge-time CalVer rewrite (see “CalVer” in references/validator-and-calver.md) into one commit whose message the evaluator composes; the feat/ branch may freely accumulate fix commits across review rounds. Two separate cutovers must never share one PR.
  • Update the PR; never close-and-recreate (except for work that will not land at all — a disproven premise, an abandoned approach). When a review demands changes, append a commit and push it fast-forward — the status resets and the evaluator re-runs. This is what makes the no-force-push rule livable: because main gets a squash, a branch carrying five fix commits still lands as one.
  • Tree-safety before destructive actions (R6). Check git status + git stash list before any destructive working-tree action — git stash discards in-progress work; rm on a tracked file is destructive. When the sandbox blocks an action, find a non-destructive alternative rather than working around it. The stash/pop cycle can itself silently un-stage a git rm: a stash taken while a deletion is staged restores the deletion as unstaged on pop, so a git status right after the cycle that shows the deleted file back as a plain unstaged change (rather than the staged deletion you left) has quietly lost the staging — re-stage it (git rm <path> again, or git add -u) before committing. A stash/pop round-trip is never a no-op on a mixed add+rm working tree.
  • Right worktree — pin one absolute path for the whole edit→commit→push sequence. Before branching, staging, or committing, confirm the worktree you are driving is the same one your edits landed in: git -C <path> rev-parse --show-toplevel must equal the path you edited, and git -C <path> status --short must list those edits. Under symlinked or near-twin sibling worktrees — a parent dir that is itself a symlink (~/projects~/Sync/projects), or look-alike names such as …/charly vs …/<other-worktree>cd-ing to the wrong sibling makes git switch -c + git commit run against a clean tree and report “nothing to commit”, silently landing nothing (or landing in the wrong repo). Never change the path spelling mid-sequence. An unexpected “nothing to commit” right after editing a file is the signature of this mistake — stop and re-verify --show-toplevel before retrying (blind retry is an R1 violation).
  • The universal PR-gate — audit before any PR action, unconditionally. Before opening, updating, or merging any pull request, run the aggregate audit: gh pr list across every touched repo + git worktree list + the live teammate/agent roster. This is a standing preflight, run first every time — never reached for only once something already looks off — because skipping it risks a duplicate PR for scope already covered in flight, a branch update from a stale worktree, or a merge over a still-running validator’s verdict. Full operational detail: /charly-internals:agents “The universal PR-gate”.
  • Post-commit staging verification. After every commit, re-run git status --short (expect it empty, or only unrelated untracked paths) and git show --stat (confirm every intended file is actually listed) — a multi-path git add naming several paths where one is mistyped or stale can commit only the files that did resolve while git commit still succeeds, producing a commit that would not even compile. Habitually re-checking git show --stat — not any tooling that fails loudly on its own — is what catches this.
  • Check-coverage is part of R10. The change must ship the test coverage that proves its functionality (check: checks for new/changed layers & images, Go tests for charly code) AND the live run must have exercised it. A change whose new functionality has no test that would fail without it is not landable.
  • Every repo is tagged at merge. The superproject, every box/<distro>, plugins, and pkg/* all mint v<YYYY.DDD.HHMM> on their own merged HEAD — the tag marks the MERGE, decoupled from any charly.yml version: schema field, so a repo needs no charly.yml to be tagged. The sole exception is the sdk contract repo, which tags under its own Go-module scheme v0.<YYYYDDD>.<HHMM with all leading zeros stripped> (B2 step 0) — not an exemption but a hard Go-module requirement: v<YYYY.DDD.HHMM> is not a valid Go module version (semver forbids a leading-zero segment — 0733733 — and a major ≥ 2 would force a /vN module-path suffix that breaks every import github.com/opencharly/sdk), so the stripped v0.<…> form is mandatory, not a choice. A skipped tag is a defect, not an exemption: the orchestrator verifies the tag landed after each merge (git ls-remote --tags origin v<VER> non-empty) and, if the evaluator skipped it, backfills it add-only on the merged HEAD (git tag -a v<VER> -m "<subject>" <merged-HEAD> + git push origin refs/tags/v<VER>) — tags are immutable, so a backfill only adds one, never moves an existing tag.
Topic File
B1 (the two-step branch-per-change loop, concurrent landings, the cross-repo WIP landing sequence) and B4 (sync to upstream + prune) references/branch-and-pr-loop.md
B2 (multi-repo/multi-worktree coordination, per-module verification), B3 (agent teams on one shared tree), B6 (cross-repo @github landing), and B7 (multi-worktree landing + refresh, the canonical end-to-end) references/multi-repo-coordination.md
B5 (the fresh evaluator + fork/PR path, the two-gate autonomous-landing model), CalVer generation, post-landing cleanliness + report format, and the validation-FAILS recovery sequence references/validator-and-calver.md
  • the project rulebook “Post-Execution Policies” — the mandate this skill operationalizes.
  • plugins/internals/agents/pr-validator.md — the fresh evaluator’s full spec.
  • opencharly/.github/scripts/branch-protection.sh — the sole organization-wide branch-protection apply/verify owner.
  • /charly-internals:cutover-policy — one-phase, atomic-commit, R10-at-the-end.
  • /charly-build:migrateversion: ↔ tag coupling, per-merge tags, push order.
  • /charly-build:reconcile — cross-repo @github pin alignment used by B6.
  • /charly-check:check — the check-coverage gate (R10) every change must satisfy.
  • /charly-internals:root-cause-analyzer — run on any FAIL before re-trying.

Invoke before any git / gh action that commits, branches, pushes, opens a PR, or drives the pr-validator merge/tag — and whenever syncing to upstream, applying branch protection, or pruning branches/worktrees across the main repo and its submodules.