Repository navigation
#48 fix: refuse to start when main is ahead of origin/main - #49
Merged
Merged
Conversation
Unpushed commits on `main` are precisely the "code that is on no shared branch" case #37 objects to, and #37's table did not name them at all — the guard shipped warning on them, with the call deferred to this issue. Refuse. The staleness objection that keeps *behind* a warn does not transfer: `origin/main` is moved by `git fetch` *and* by a successful `git push` from this repository, so a never-fetched ref can hide behind-ness but cannot manufacture ahead-ness for commits this checkout pushed. Ahead is local evidence, assertable without the network call an ExecStartPre must not make. The only false positive needs the identical SHAs published from another clone; through a PR merge the SHAs differ, so the tree reads as diverged — refused too, with the behind warning printed alongside so both sides of the reconcile get named. A missing `origin/main` ref now warns rather than passing in silence: with ahead refusing, silence would make `git remote remove origin` a bypass of the refusal without touching the guard. The cost is one `git push` before the restart, in a flow that already meant to push — the lifecycle tables now say push first. The network-partition case keeps REPLICATOR_ALLOW_ANY_CHECKOUT=1, named in DEPLOYMENT.md as the override's incident-time use so reaching for it there is a documented path rather than guard erosion. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CR #17 — deploy/replicator.service's own comment still said the guard "warns only on a stale, unpushed, or dirty tree". That is the file an operator reads while a start is failing, now describing the opposite verdict for the condition that refused them. Needs the cp to /etc/systemd/system/ like any unit edit, comment-only though it is. CR #18 — docs/STYLE.md's journald inventory listed unpushed among the warn-tier lines. It is a refusal line now, which is the half of that inventory that matters: refusals appear exactly when something is already wrong. CR #19 — the deploy sequence front-loaded `git push`, but the usual path is a PR merged on GitHub, after which this checkout is *behind* and the push is a no-op. A deploy line whose first command routinely prints "Everything up-to-date" is one an operator learns to skip. Both merge paths now get their own row: pull when it merged on GitHub, push when it merged here (AGENTS.md, README.md, docs/DEPLOYMENT.md, docs/COMMANDS.md). CR #21 — the manifest's deployment-topology description still called the guard branch-only. CR #22 — the dirty-tree warning moved above the origin comparison, so every warn prints before the one refusal that can still fire. An operator who fixes the ahead refusal should not then discover a dirty tree on the next start; same reasoning as printing `behind` before the `ahead` refusal. Pinned by a test. CR #20 (re-index the context manifest) is deliberately deferred to after the merge — codebase_context_index has to run from the main checkout. 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 #48 — the decision it asks for, plus the sub-decision it surfaces.
The call: refuse
Unpushed commits on
mainare the "code that is on no shared branch" case #37 objects to. The staleness objection that keeps behind a warn does not transfer to ahead:origin/mainis updated bygit fetchand by a successfulgit pushfrom this repository, so a never-fetched ref can hide behind-ness — remote commits this checkout cannot see — but it cannot manufacture ahead-ness for commits this checkout pushed. Ahead is local evidence, and needs no network call to be sound, which is what the fetch-less guard can afford to assert.The only false positive needs the identical SHAs published from another clone; through a PR merge the SHAs differ, so the tree reads as diverged rather than merely ahead — refused too, with the behind warning printed alongside it so an operator is not told to push into a rejection.
Option 3 (gate the refusal on
.git/FETCH_HEADfreshness) is rejected in the issue thread: that measures inbound knowledge, the behind axis, and says nothing about whether the local commits were pushed.Sub-decision: a missing
origin/mainwarns out loudWith ahead refusing, silence on a missing ref would make
git remote remove origina bypass of the refusal without touching the guard. Not fatal — HEAD is already proven to bemain, so it is absence of evidence — but named.Changes
scripts/check_main_checkout.sh— ahead refuses, naming both remedies (git push/git reset --hard origin/main), the staleness hypothesis (git fetch), and the override; missing-ref warn; every warn now prints before the refusal, so one failed start names every condition; the two-tier comment rewritten as the decision.tests/test_check_main_checkout.py— ahead refuses, override lets unpushed commits through, diverged refuses and names both sides, missing ref warns and starts, a refused start still names the dirty tree.test_being_behind_origin_main_only_warnsunchanged — that is the point.deploy/replicator.service— the guard's own comment, which described unpushed as a warn. Comment-only, but the installed unit is a copy:sudo cp deploy/replicator.service /etc/systemd/system/ && sudo systemctl daemon-reloadafter merge.docs/DEPLOYMENT.md,docs/STYLE.md— verdict table, the asymmetry argument, the journald line inventory, andREPLICATOR_ALLOW_ANY_CHECKOUT's incident-time use (a hotfix committed while the remote is unreachable).AGENTS.md,README.md,docs/COMMANDS.md, the DEPLOYMENT lifecycle table — the deploy sequence now splits the two merge paths:git pull --ff-onlywhen the merge happened on GitHub,git pushwhen it happened locally. A line whose first command routinely printsEverything up-to-dateis one an operator learns to skip..socraticodecontextartifacts.json— both descriptions that named the guard. Re-index (codebase_context_index) after merge, from the main checkout.After merge
git pull --ff-onlyon the VM's main checkout, then thecpabove, then restart. The branch also carries62ce64c("chore: update skills submodules"), which was committed to localmainand never pushed — merging this PR publishes it, which is what makes the pull a fast-forward.Reviewed as CR #17–#23; findings applied in
2867cbb. Tests:uv run pytest— 777 passed.🤖 Generated with Claude Code