Sync sends Time's merge back to the copy; CHANGELOG conflicts keep both sides #204

Open
teahubxyz wants to merge 10 commits from ceo/sync-back-union into main
pull from: ceo/sync-back-union
Owner

Two gaps showed up when the cloud lane's #172 (PR #203) was approved but conflicted with main on one CHANGELOG line.

sync.py, back to the copy. An imported branch has one writer, the copy. Time also writes it in one case: update_branch merges the base into an approved PR that fell behind. On the next run, sync saw the home's branch differ from the copy's and forced the copy's older tip over it, which undid the merge. Now:

  • if the copy's tip is already in the home's branch (the home is ahead), sync pushes the home's branch to the copy fast-forward. It imports nothing and announces no revision, and the copy's next session builds on the merge. The log line shows back [...].
  • if both sides have commits of their own, the copy still wins and Time merges again on a later beat.

time.py, union merge. Before merging, update_branch writes CHANGELOG.md merge=union to the clone's .git/info/attributes, so two PRs that each add a line at the same spot keep both lines. A new repo .gitattributes says the same to anyone else merging, including cloud sessions. Branches cut before it lack it, which is why the clone writes it too.

time.py, conflict hint. A conflict on a PR that sync.py opened (its body says tm.IMPORTED, "brought in by sync.py") now says the fix goes to the copy. It no longer suggests /time retry, whose worker would commit to the home and be overwritten by the next sync.

Tests, all green locally (test_time.py, test_housekeeping.py, test_seed.py; the havre npm step was skipped because npm in my sandbox can't reach the registry, and CI runs it):

  • 83: Time's merge goes back to the copy; a session's commit on top imports as a revision; when both sides wrote, the copy wins; sync's PR body carries IMPORTED.
  • 123: a CHANGELOG conflict merges with both lines kept (real git); the imported-branch hint replaces /time retry.

🤖 Generated with Claude Code

https://claude.ai/code/session_017K4vyY3yUD3GsvoZaAtKkD

Two gaps showed up when the cloud lane's #172 (PR #203) was approved but conflicted with main on one CHANGELOG line. **sync.py, back to the copy.** An imported branch has one writer, the copy. Time also writes it in one case: `update_branch` merges the base into an approved PR that fell behind. On the next run, sync saw the home's branch differ from the copy's and forced the copy's older tip over it, which undid the merge. Now: - if the copy's tip is already in the home's branch (the home is ahead), sync pushes the home's branch to the copy fast-forward. It imports nothing and announces no revision, and the copy's next session builds on the merge. The log line shows `back [...]`. - if both sides have commits of their own, the copy still wins and Time merges again on a later beat. **time.py, union merge.** Before merging, `update_branch` writes `CHANGELOG.md merge=union` to the clone's `.git/info/attributes`, so two PRs that each add a line at the same spot keep both lines. A new repo `.gitattributes` says the same to anyone else merging, including cloud sessions. Branches cut before it lack it, which is why the clone writes it too. **time.py, conflict hint.** A conflict on a PR that sync.py opened (its body says `tm.IMPORTED`, "brought in by sync.py") now says the fix goes to the copy. It no longer suggests `/time retry`, whose worker would commit to the home and be overwritten by the next sync. Tests, all green locally (`test_time.py`, `test_housekeeping.py`, `test_seed.py`; the havre npm step was skipped because npm in my sandbox can't reach the registry, and CI runs it): - 83: Time's merge goes back to the copy; a session's commit on top imports as a revision; when both sides wrote, the copy wins; sync's PR body carries `IMPORTED`. - 123: a CHANGELOG conflict merges with both lines kept (real git); the imported-branch hint replaces `/time retry`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_017K4vyY3yUD3GsvoZaAtKkD
teahubxyz added 5 commits 2026-10-19 21:52:23 +00:00
teahubxyz added 1 commit 2026-10-19 22:15:19 +00:00
teahubxyz added 1 commit 2026-10-19 22:15:27 +00:00
teahubxyz added 1 commit 2026-10-19 22:15:50 +00:00
teahubxyz added 1 commit 2026-10-19 22:15:53 +00:00
teahubxyz added 1 commit 2026-10-19 22:15:56 +00:00
Author
Owner

An independent review (a fresh agent that hadn't seen the work) requested changes. Its findings and what changed:

  1. Medium, fixed. The ancestor test couldn't tell Time's merge apart from the copy rewinding its own branch. A session that force-pushed A1 over A2 would have had A2 sent back to it, and the home would have landed a commit its writer withdrew. Any other push at the home (a human, a worker) would also have gone out to GitHub.
    • Now times_merges() sends the home's commits back only when every first-parent commit between the copy and the home is a two-parent merge authored by TIME_GIT_NAME.
    • The push carries --force-with-lease on the copy's branch at the sha sync read, so a rewind in between pushes nothing.
    • New tests: a push at the home and a rewind on the copy both import the copy's tip instead of going back. With the old check, section 83 fails at the push-at-home case.
  2. Low, fixed. The home fetch no longer aborts the whole run if the branch has just been deleted (check=False, and it falls through to import).
  3. Low, acknowledged. The union driver keeps a bullet that one side reworded or deleted next to the other side's new one, and nothing flags it. The comment and CHANGELOG now say so. The pattern is anchored to the root /CHANGELOG.md.
  4. Low, fixed. The conflict hint now says who acts. For a card the cloud lane built, its launcher on hb-5 (outside this repo) sends a session back with the hunks; otherwise a human merges on the copy.

The reviewer confirmed that each new test fails with its hunk reverted. The suites are green locally: test_time.py, test_housekeeping.py, test_seed.py.

An independent review (a fresh agent that hadn't seen the work) requested changes. Its findings and what changed: 1. **Medium, fixed.** The ancestor test couldn't tell Time's merge apart from the copy rewinding its own branch. A session that force-pushed A1 over A2 would have had A2 sent back to it, and the home would have landed a commit its writer withdrew. Any other push at the home (a human, a worker) would also have gone out to GitHub. - Now `times_merges()` sends the home's commits back only when every first-parent commit between the copy and the home is a two-parent merge authored by `TIME_GIT_NAME`. - The push carries `--force-with-lease` on the copy's branch at the sha sync read, so a rewind in between pushes nothing. - New tests: a push at the home and a rewind on the copy both import the copy's tip instead of going back. With the old check, section 83 fails at the push-at-home case. 2. **Low, fixed.** The home fetch no longer aborts the whole run if the branch has just been deleted (`check=False`, and it falls through to import). 3. **Low, acknowledged.** The union driver keeps a bullet that one side reworded or deleted next to the other side's new one, and nothing flags it. The comment and CHANGELOG now say so. The pattern is anchored to the root `/CHANGELOG.md`. 4. **Low, fixed.** The conflict hint now says who acts. For a card the cloud lane built, its launcher on hb-5 (outside this repo) sends a session back with the hunks; otherwise a human merges on the copy. The reviewer confirmed that each new test fails with its hunk reverted. The suites are green locally: `test_time.py`, `test_housekeeping.py`, `test_seed.py`.
All checks were successful
ci / test (pull_request) Successful in 45s
This pull request has changes conflicting with the target branch.
  • CHANGELOG.md
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin ceo/sync-back-union:ceo/sync-back-union
git checkout ceo/sync-back-union
Sign in to join this conversation.