Skip to content

chore: add upstream sync script and fork changeset - #46

Merged
brentyates-swx merged 16 commits into
swxtchfrom
sync-upstream-script
Sep 26, 2026
Merged

brentyates-swx merged 16 commits into
swxtchfrom
sync-upstream-script

Conversation

@brentyates-swx

@brentyates-swx brentyates-swx commented Sep 25, 2026 •

Copy link
Copy Markdown

Adds script/sync-upstream.ts, so an upstream sync is one safe command instead of a hand-run merge, plus a root CHANGESET.md that records every sync and every fork change, kept current by CI. It also typechecks the fork's scripts (closes #47).

git worktree add --detach ../swx-opencode-sync origin/swxtch
cd ../swx-opencode-sync && bun script/sync-upstream.ts

What the sync script does

  1. Requires a clean tree and both remotes, and refuses to run in the primary checkout.
  2. Fetches only upstream/dev and origin/swxtch, by explicit refspec with --no-tags --refmap=, so a fetch never moves a local branch or tag.
  3. Validates every abort condition before moving or pushing anything.
  4. Fast-forwards dev (a pure mirror) to the pinned upstream/dev commit with git fetch . <sha>:refs/heads/dev. That refuses a non-fast-forward and a branch checked out in any worktree, as one operation. It then pushes dev, reading every push URL back before and after.
  5. Stops with SYNC_UPTODATE if origin/swxtch already contains upstream.
  6. Creates sync-upstream-<UTC timestamp> off the freshly fetched origin/swxtch, so unpushed local commits can't ride in, and merges with --no-ff --no-commit. It then writes the sync's CHANGESET.md entry, naming the branch, inside that merge and commits with a conventional message.

It never pushes swxtch, opens a PR, or resolves conflicts. On conflicts the merge stays in progress with the entry staged. If CHANGESET.md itself conflicts, it's left unmerged and the entry is printed for the resolver.

Token Exit Meaning
SYNC_CLEAN 0 Merge committed on the sync branch, ready for review
SYNC_ABORT 2 No local branch moved or was left behind, and nothing was pushed (a fetch may update remote-tracking refs)
SYNC_CONFLICTS 3 Merge left in progress for a resolver
SYNC_UPTODATE 4 Nothing to merge; no branch created
SYNC_MERGE_FAILED 5 Something durable changed or may have (the mirror moved or was pushed, or a sync branch was left behind), then the merge didn't land

Every final line from the mirror step on states the mirror's state (current / pushed / failed / partial / unknown). Failure reports are read back from git rather than assumed: whether the branch was deleted, whether HEAD moved, whether the tree is clean. If merge --abort itself fails, the script stays on the sync branch rather than letting git carry the staged merge onto swxtch. The merge is committed only onto the sync branch: a checkout that doesn't land on it fails, and HEAD is re-checked before the commit.

Merge, not rebase. swxtch is published; a rebase would force-push it and rewrite commits that merged PRs reference (see 75eced9). A sync PR must land as a merge commit, never a squash: squashing drops the upstream/dev parent, and the next run would re-merge the same commits.

CHANGESET.md

  • Backfilled with the fork base, the 2026-09-22 sync (chore: sync swxtch with upstream anomalyco/opencode dev #38) and its resolutions, and all 20 fork changes, each with its status against upstream (fork-only / upstreamable / partly upstream).
  • script/changeset-check.ts (the "Check the fork changeset" step) fails a PR against swxtch that doesn't add its own entry, compared with the PR base (fetched via PR_BASE_SHA), in the right section.
    • A fork change needs - **#<number>** under "Fork changes".
    • A sync PR needs the entry naming its branch under "Upstream syncs". There's no name-based exemption, and reusing an old sync's name fails against the base.
    • Only Dependabot is exempt.
    • Entries may wrap onto indented lines.
    • Like every check here, it runs the PR's own code: it guards against forgetting, not a hostile author.

Typecheck the fork's scripts (closes #47)

script/ isn't a workspace package, so nothing typechecked it. script/tsconfig.json + bun run typecheck:script (a new typecheck.yml step) cover the files the fork authors or modified. The rest of script/ is identical to upstream, and fixing its errors would only add sync conflicts. It found 5 real noUncheckedIndexedAccess errors in fork files, fixed here.

CI

The new "Test the fork maintenance scripts" and "Check the fork changeset" steps sit outside the pinned fork-guard gate. The test.yml::unit and typecheck.yml::typecheck digests in check-workflow-guards.ts are updated deliberately.

Review

Ten rounds of cross-model review (GPT-6 Sol via swx-azure, GLM 5.3 via fireworks, both through opencode), run to convergence. Each round's findings and resolutions are in the PR comments. Declined, with reasons: a failed mirror push is not fatal; the checker runs the PR's own code; the base is fetched even for exempt PRs.

Verification

  • 258/258 across the script suites, on real git in throwaway repositories; new behavior is mutation-checked. Typecheck and workflow audit are clean.
  • Sandbox run of the final code against the real anomalyco/opencode, with a local bare clone as origin, so nothing was pushed to GitHub:
    • SYNC_CLEAN: 16 upstream commits merged without conflicts, and the entry was written inside the merge.
    • The merged tree passes bun install --frozen-lockfile and typecheck:script.
    • changeset-check accepts the generated sync branch.

🤖 Generated with Claude Code

https://claude.ai/code/session_01BUBanXuSck62cxd3dpZ1oA

@brentyates-swx

Copy link
Copy Markdown
Author

Session stamp

  • Session ID: c2a9965a-566a-4014-aa0e-e4afd6ff497c
  • Working repo (launch dir): /home/byates/swx-opencode
  • Worktree: /home/byates/swx-opencode-sync-upstream

@brentyates-swx
brentyates-swx marked this pull request as ready for review September 25, 2026 12:06
@brentyates-swx
brentyates-swx marked this pull request as draft September 25, 2026 12:58
@brentyates-swx brentyates-swx changed the title chore: add a safe upstream sync script chore: add upstream sync script and fork changeset Sep 25, 2026
@brentyates-swx
brentyates-swx marked this pull request as ready for review September 25, 2026 13:16
@brentyates-swx

Copy link
Copy Markdown
Author

Review round 1 → e9f477e3eb

Reviewers: GPT-6 Sol (swx-azure, effort high) and GLM 5.3 (fireworks), both through opencode run, plus a self-review. Every finding was taken except one, taken in part.

Finding From Resolution
The sync-branch exemption was forgeable by naming a branch like a generated one Sol (blocker), GLM Fixed. There is no exemption now: the sync entry names its branch, and a sync PR must carry that entry.
A conflicted CHANGESET.md was staged as resolved with markers in it Sol, GLM, self Fixed. It is left unmerged, and the resolver is handed the entry.
A filesystem or probe failure threw past cleanup mid-merge Sol, GLM, self Fixed. Failures go through the state-aware fail() with the merge aborted.
A sync could be reported ready without its changeset entry Sol Fixed. Failing to record now abandons the merge.
Race between the worktree probe and moving dev Sol Fixed. The move is git fetch . <sha>:refs/heads/dev, one operation that refuses a non-FF and a branch checked out anywhere (verified); the probe is removed.
A failed mirror push still reported SYNC_CLEAN Sol Partly taken. Not fatal: the sync PR carries the same commits, and stopping would block syncs over a stale mirror. The push failure is now in every final line.
fetch --tags was unused and a clashing tag failed every sync; SYNC_ABORT promised too much GLM, Sol Fixed. Dropped --tags; the guarantee now says a fetch may update remote-tracking refs.
Final lines did not always state the mirror state GLM Fixed. Every final line does.
Edge tests missing: marker at end of file, missing marker, name collision GLM Added. A mutation showed the first test could not see trailing newlines; it now reads the file raw.
"HEAD is never left on it" overpromised GLM Reworded.
exitCode can be null GLM Fixed (?? 1).
The sync based off local swxtch, so unpushed commits could ride into the PR unreviewed self Fixed. It bases off the freshly fetched origin/swxtch; cleanup returns HEAD to where the run started.
The branch name and changeset date read the clock twice self Fixed.

Tests: 231/231 across the script suites, typecheck and audit clean, and the new behaviors are mutation-checked.

@brentyates-swx

Copy link
Copy Markdown
Author

Review round 2 → b3642c44e6

Both reviewers confirmed the round 1 fixes. New findings:

Finding From Resolution
Staging the entry could fail after the write; merge --abort (reset --merge) keeps unstaged changes, leaving a dirty tree Sol, GLM Fixed. Restore the file from the index, read the abort back, and have the report say when the tree is not clean.
Reusing an old sync's branch name passes on the entry the base already has Sol Fixed. The entry must be new against the base, which CI fetches via PR_BASE_SHA.
A wrapped sync entry failed the check; entries weren't scoped to their section Sol, GLM Fixed. Parse list items with their continuation lines, per section.
The checker runs the PR's own code, so a PR can edit it GLM Declined. It is a guard against forgetting, not a hostile author; every check here (including the workflow-guard audit) runs the PR's code, and an edit is in the reviewed diff. The limit is now stated in the header.
The header and CI message didn't cover the case where CHANGESET.md itself conflicted; the mirror-state claim was overstated GLM, Sol Fixed.
Missing tests: MERGE_MSG failure, detached-start cleanup, non-numeric PR number GLM Added, plus stage-failure, failed-abort, and dirty-tree tests.

Found while fixing: the new failed-abort test showed that cleanup, with the merge still in progress, checked out swxtch (git allows it and carries the staged merge along) and deleted the sync branch. That leaves MERGE_HEAD live on swxtch, where the next ordinary commit silently merges unreviewed upstream code. Now, when the abort fails, the script stays on the sync branch and says so.

Tests: 242/242; the new checks are mutation-checked. The one surviving mutant is equivalent: a failed git show already has empty stdout.

@brentyates-swx

Copy link
Copy Markdown
Author

Review round 3 → fbdff24b7c

Both reviewers confirmed the round 2 fixes. New findings, all taken:

Finding From Resolution
A push can update origin/dev and still exit nonzero (e.g. a rejected follow-on tag), so a later failure could claim SYNC_ABORT falsely Sol Fixed. The mirror push is tag-free (--no-follow-tags), a nonzero exit is read back with ls-remote, and an unreadable outcome counts as a possible mutation (reported UNKNOWN, token SYNC_MERGE_FAILED).
The cleanup-time status probe ignored its exit code Sol Fixed. An uninspectable tree is reported as unverified.
The SYNC_ABORT row didn't mention the failed-abort case GLM Fixed.
A status-update PR would unexpectedly fail the check GLM Documented. That PR still adds its own entry.
Base-newness was only tested for sync entries GLM Test added for fork entries.

Tests: 246/246; each new path is mutation-checked.

@brentyates-swx

Copy link
Copy Markdown
Author

Review round 4 → d1cd93b310

GLM: NO FINDINGS, having verified every round 3 fix.

Finding From Resolution
Dropping --tags wasn't enough: a plain fetch still auto-follows tags, so an abort could leave new local tags and a clashing tag could fail the fetch Sol Fixed. Both fetches use --no-tags; a test covers a new upstream tag and a clashing local one.
readBaseChangeset read any git show failure as "no file" GLM (noted below its reporting bar) Fixed. It's cheap: only a base that provably lacks the file reads as empty.
The base is fetched even for exempt PRs; "push it by hand" is imprecise if the remote raced ahead GLM (below its bar) Left. One harmless shallow fetch that a passing exempt PR never reads, and an unreachable race.

Tests: 247/247.

@brentyates-swx

Copy link
Copy Markdown
Author

Review round 5 → 198690fa88

GLM: NO FINDINGS (second consecutive clean round).

Finding From Resolution
A CHANGESET.md that kept the marker but lost its ## Upstream syncs heading got the entry written and reported SYNC_CLEAN, then failed CI, which accepts sync entries only under that heading Sol (minor) Fixed. The marker's nearest heading must be that heading, and the heading is now a shared constant between the script and the check. The end-of-file test gains a real heading, plus a negative test for a misplaced marker.

Tests: 248/248.

@brentyates-swx

Copy link
Copy Markdown
Author

Review round 6 → 95efd0bac0

GLM: NO FINDINGS (third consecutive clean round).

Finding From Resolution
The push decision trusted the fetched origin/dev, which comes from origin's fetch URL; with a separate push URL, a stale push destination was reported current Sol Fixed. The push destination is read directly (ls-remote on the push URL) before the push and after a failed one. A test covers separate fetch and push URLs.

Consequence: a push destination that can't be reached is now reported as UNKNOWN, because it genuinely can't be verified. So the failed-push tests now use a reachable destination that rejects the push as a non-fast-forward.

Tests: 249/249.

@brentyates-swx

Copy link
Copy Markdown
Author

Review round 7 → 33043a3f48

GLM: NO FINDINGS (fourth consecutive clean round).

Finding From Resolution
A remote can have several push URLs, and git pushes to each; the round 6 fix read only the first Sol Fixed. Every push URL is read before and after the push. A current first URL no longer hides a stale second one. A push that lands on some URLs and is rejected by another is reported as partial and counts as a mutation. With no readable destination, a failed push is UNKNOWN rather than vacuously "pushed". Each path is tested and mutation-checked.

Tests: 252/252. No Fable 5 pass for this PR: it's reserved for the hardest problems, and this isn't one.

@brentyates-swx

Copy link
Copy Markdown
Author

Review round 8 → 3c3c392369

GLM: NO FINDINGS (fifth consecutive clean round).

Finding From Resolution
A configured refspec that writes a local branch (+refs/heads/dev:refs/heads/dev) could move dev during the fetch, before an abort Sol Fixed. Fetch only the two needed branches, by explicit refspec into remote-tracking refs, with --refmap= so git doesn't also apply configured refspecs as opportunistic updates. A mutation dropping --refmap= confirms that alone would still move dev. It also stops fetching every upstream branch.
A destination unread before a rejected push was misreported as "partial" Sol Fixed. It's now UNKNOWN.

Tests: 254/254.

@brentyates-swx

Copy link
Copy Markdown
Author

Review round 9 → 85f5a547ae

Sol: NO FINDINGS (first clean round).

Finding From Resolution
checkout -b creates the ref before switching the tree, so a checkout that fails while switching leaves behind a branch this run created, yet the report said it "was not created by this run" GLM Fixed. Nothing unowned is ever deleted, but the report now says the branch exists and this run can't prove who created it (or that it was never created). Tested and mutation-checked.
The PR description still described the pre-review design (the sync-branch exemption, the local-lag warning, basing off local swxtch, the 24-test step) GLM Rewritten to match the final behavior.

The sandbox run was re-done on this commit against the real upstream: SYNC_CLEAN, 16 commits, frozen lockfile and typecheck:script clean on the merged tree, and changeset-check accepts the sync branch. Tests: 255/255.

@brentyates-swx

Copy link
Copy Markdown
Author

Review round 10 → 8d158f6a5d

GLM: NO FINDINGS. It also verified the round 9 fix and the rewritten description.

Finding From Resolution
A post-checkout hook could switch HEAD back and exit 0; the merge would then be committed onto whatever HEAD was on (possibly local swxtch) and reported SYNC_CLEAN Sol Fixed. A checkout that doesn't land on the sync branch fails, and HEAD is re-checked before the commit, which touches nothing if HEAD has moved.
SYNC_ABORT promised no local branch moved, but a failed checkout can leave behind a sync branch this run created Sol Fixed. The token is decided after cleanup: a leftover sync branch makes it SYNC_MERGE_FAILED. The token docs and the PR table are updated.

Tests: 258/258; each new guard is mutation-checked. Sol's last four rounds of findings have all been hostile hooks or unusual git configuration (multiple push URLs, custom fetch refspecs, hooks that switch branches).

@brentyates-swx

Copy link
Copy Markdown
Author

Review converged at 8d158f6a5d

The final round was narrow, verifying only the round 10 fixes. Sol and GLM both returned NO FINDINGS.

  • 11 rounds in all. Every finding was fixed with a test, except three declined with reasons (recorded above): a failed mirror push is not fatal; the changeset checker runs the PR's own code; the base is fetched even for exempt PRs.
  • CI is green on this commit, and the branch is current with swxtch (0 behind).
  • Land this with a merge commit, not a squash.

Port swx-firstmate's bin/fm-sync-upstream.sh to script/sync-upstream.ts:
fast-forward the dev mirror to upstream/dev, then prepare a --no-ff merge of
upstream/dev onto a fresh branch off swxtch for review. Merge, never rebase.

Claude-Session: https://claude.ai/code/session_01BUBanXuSck62cxd3dpZ1oA
Ports swx-firstmate's bin/fm-sync-upstream.sh to script/sync-upstream.ts.
It fast-forwards the dev mirror to upstream/dev (FF only, pushed to
origin) and prepares a --no-ff merge of upstream/dev onto a fresh
sync-upstream-<timestamp> branch off swxtch. It never pushes swxtch,
opens a PR, or resolves conflicts; conflicts stay in progress for a
resolver. Merge, not rebase, for the reasons recorded in 75eced9.

Differences from the original:
- git is injected, so fault tests wrap the real runner instead of
  shimming PATH, and the primary-checkout guard is a separate option
  rather than a side effect of overriding the repository path.
- It operates on the repository containing the cwd, so there is no
  "ran the primary's copy" trap.
- The mirror push sends the pinned upstream commit, not the branch name.
- The branch name has no slash, per this repo's branch naming.

The tests run in a new CI step rather than the pinned fork-guard gate,
since this is not a guard; the unit job's audit digest is updated for it.

Claude-Session: https://claude.ai/code/session_01BUBanXuSck62cxd3dpZ1oA
Merging the pinned sha made git default to "Merge commit '<sha>'".

Claude-Session: https://claude.ai/code/session_01BUBanXuSck62cxd3dpZ1oA
script/ is not a workspace package, so `bun typecheck` never reached it,
and the root tsconfig has no Bun types. Nothing typechecked the fork's CI
guards or the new sync script.

script/tsconfig.json borrows Bun types from packages/script and includes
only the files this fork authors or has modified. The rest of script/ is
identical to upstream; fixing its 18 type errors here would only add
conflicts to every upstream sync. A new typecheck.yml step runs it through
`bun run typecheck:script`, and that job's audit digest is updated for it.

Fixes the five errors it found in fork files, all noUncheckedIndexedAccess.

Closes #47

Claude-Session: https://claude.ai/code/session_01BUBanXuSck62cxd3dpZ1oA
CHANGESET.md at the root records both halves of what swxtch is: every
upstream sync merged in, and every change the fork carries on top, each
with its status against upstream. Backfilled from history: the fork base,
the 2026-09-22 sync (#38) with its conflict resolutions, and the 20 fork
changes since.

It stays current mechanically rather than by memory:

- sync-upstream.ts now merges with --no-commit and adds the sync's entry
  (range, commit count, conflicted files) inside the sync merge itself.
  On conflicts the entry is staged with the in-progress merge, and the
  conventional message goes to MERGE_MSG so the resolver's commit uses it.
- script/changeset-check.ts fails a PR against swxtch with no
  `- **#<number>**` entry. Dependabot and generated sync-upstream-<timestamp>
  branches are exempt; a hand branch cannot opt out by prefix.

Both run in the unit job; its audit digest is updated.

Claude-Session: https://claude.ai/code/session_01BUBanXuSck62cxd3dpZ1oA
Findings from GPT-6 Sol and GLM 5.3 (via opencode) and a self-review:

- Base the sync on the freshly fetched origin/swxtch, not local swxtch, so
  unpushed local commits cannot ride into the sync PR unreviewed. Cleanup
  returns HEAD to where the run started.
- A conflicted CHANGESET.md is left unmerged and the entry handed to the
  resolver; staging it would mark the conflict resolved with markers in it.
- Failing to record the changeset entry (missing file, missing marker,
  unwritable file) or to write MERGE_MSG now abandons the merge through
  the normal state-aware report, instead of warning and committing an
  unrecorded sync, or throwing past cleanup mid-merge.
- Fast-forward the mirror with `git fetch . <sha>:refs/heads/dev`, which
  refuses a non-fast-forward and a branch checked out in another worktree
  in one operation, closing the window after the separate worktree probe
  (now removed).
- The sync-branch changeset exemption was forgeable by naming a branch
  like a generated one. There is no exemption now: the sync entry names
  its branch and a sync PR must carry it.
- Drop `fetch --tags` (unused, and a clashing local tag failed every sync),
  state SYNC_ABORT's guarantee accurately, report the mirror state on
  every final line, read the clock once, treat a null exit code as failure.

Each fix has a test; the new ones were mutation-checked.

Claude-Session: https://claude.ai/code/session_01BUBanXuSck62cxd3dpZ1oA
- Staging the changeset entry could fail after it was written, and
  `merge --abort` (reset --merge) keeps unstaged changes, so cleanup could
  leave a dirty tree. The file is restored from the index first, abort is
  read back, and a failure report says when the tree is not clean.
- A failed `merge --abort` no longer steps off the sync branch. Git lets a
  checkout carry the staged merge onto swxtch, where the next ordinary
  commit would silently merge unreviewed upstream work; a test caught the
  cleanup doing exactly that. It stays on the sync branch and says so.
- The changeset check requires the entry to be NEW against the PR's base
  (fetched in CI via PR_BASE_SHA), so reusing an old sync's branch name
  fails; scopes each entry to its section; and joins wrapped continuation
  lines, so a resolver can wrap a long sync entry.
- Declined: running the checker from the base instead of the PR. Like
  every check here, and the workflow-guard audit, it runs the PR's code;
  it guards against forgetting, and an edit to it is in the reviewed
  diff. The header now states that limit.
- Header and CI message cover the CHANGESET.md-itself-conflicted case;
  the mirror-state claim is scoped to where it holds.
- Tests: MERGE_MSG write failure, stage failure, failed abort, dirty-tree
  report, detached-start cleanup, non-numeric PR number, base reading.

Claude-Session: https://claude.ai/code/session_01BUBanXuSck62cxd3dpZ1oA
- A push can update origin/dev and still exit nonzero (for example a
  rejected follow-on tag), so a failed push was not proof nothing moved,
  and a later failure could report SYNC_ABORT falsely. The mirror push is
  now tag-free, a nonzero exit is read back with ls-remote, and a push
  whose outcome cannot be read counts as a possible mutation (reported
  UNKNOWN, token SYNC_MERGE_FAILED).
- The cleanup-time status probe's exit code is checked; an uninspectable
  tree is reported as unverified rather than implied clean.
- Docs: the SYNC_ABORT row covers a failed merge --abort; CHANGESET.md
  says a status-update PR still adds its own entry.
- Tests: push landed despite nonzero exit, push outcome unreadable,
  status probe failing at cleanup, fork-entry newness against the base.

Claude-Session: https://claude.ai/code/session_01BUBanXuSck62cxd3dpZ1oA
- Fetch with --no-tags. Dropping --tags was not enough: a plain fetch
  still auto-follows tags, so an abort could leave new local tags behind
  and a clashing local tag could still fail the fetch. (Sol)
- readBaseChangeset reads "" only for a base that provably lacks the
  file; any other read failure is unknown, so a reused sync branch name
  cannot pass against a base whose file failed to read. (GLM, noted
  below its reporting bar; cheap enough to close.)

GLM returned NO FINDINGS for round 4.

Claude-Session: https://claude.ai/code/session_01BUBanXuSck62cxd3dpZ1oA
- A CHANGESET.md that kept the marker but lost its "## Upstream syncs"
  heading got the entry written and reported SYNC_CLEAN, then failed the
  CI check, which only accepts sync entries under that heading. The
  marker's nearest heading must now be that heading, and the heading is a
  shared constant so the script and the check cannot drift. (Sol)

GLM returned NO FINDINGS for round 5.

Claude-Session: https://claude.ai/code/session_01BUBanXuSck62cxd3dpZ1oA
- The push decision trusted the fetched origin/dev, which comes from
  origin's fetch URL; with a separate push URL, a current fetch side hid
  a stale push destination and the run reported the mirror current. The
  push destination is now read directly with ls-remote on the push URL,
  before the push and after a failed one. (Sol)
- Consequence, now tested: an unreachable push destination is reported
  as UNKNOWN (it genuinely cannot be verified), so the "failed push"
  tests use a reachable destination that rejects the push as non-FF.

GLM returned NO FINDINGS for round 6 (third consecutive clean round).

Claude-Session: https://claude.ai/code/session_01BUBanXuSck62cxd3dpZ1oA
- A remote can have several push URLs, and git pushes to each; the
  round 6 fix read only the first. Every push URL is now read before and
  after the push. A current first URL no longer hides a stale second one,
  and a push that lands on some URLs and is rejected by another is
  reported as partial and counts as a mutation. With no readable
  destination, a failed push is UNKNOWN rather than vacuously pushed.
  (Sol)

GLM returned NO FINDINGS for round 7 (fourth consecutive clean round).

Claude-Session: https://claude.ai/code/session_01BUBanXuSck62cxd3dpZ1oA
- Fetch only the two branches the run needs, by explicit refspec into
  remote-tracking refs, with --refmap= so git does not also apply the
  configured refspecs as opportunistic updates. A configured refspec that
  writes a local branch (+refs/heads/dev:refs/heads/dev) could otherwise
  move dev before an abort; a mutation dropping --refmap= confirms the
  opportunistic update would still do it. Also stops fetching every
  upstream branch. (Sol)
- A destination that could not be read before a rejected push made the
  comparison report "partial"; it is now UNKNOWN. (Sol)

GLM returned NO FINDINGS for round 8 (fifth consecutive clean round).

Claude-Session: https://claude.ai/code/session_01BUBanXuSck62cxd3dpZ1oA
`git checkout -b` creates the ref before switching the tree, so a checkout
that fails while switching leaves behind a branch this run did create, and
the report said it "was not created by this run". Without ownership the
branch is still never deleted, but the report now says it exists and this
run cannot prove who created it, or that it was never created. (GLM, round
9; Sol returned NO FINDINGS.)

Claude-Session: https://claude.ai/code/session_01BUBanXuSck62cxd3dpZ1oA
- A post-checkout hook could switch HEAD back and exit 0; the script then
  merged and committed onto whatever branch HEAD was on, possibly local
  swxtch, and reported SYNC_CLEAN. A checkout that did not land on the
  sync branch now fails, and HEAD is re-checked before the commit, which
  touches nothing if it has moved. (Sol, round 10)
- SYNC_ABORT promised no local branch moved, but a failed checkout can
  leave behind a sync branch this run created. The token is now decided
  after cleanup, and a leftover sync branch makes it SYNC_MERGE_FAILED;
  the token docs say so. (Sol, round 10)

GLM returned NO FINDINGS for round 10.

Claude-Session: https://claude.ai/code/session_01BUBanXuSck62cxd3dpZ1oA
#49 merged ahead of this PR, so its fork-change entry lands here with the
file.

Claude-Session: https://claude.ai/code/session_01BUBanXuSck62cxd3dpZ1oA
@brentyates-swx

Copy link
Copy Markdown
Author

Rebased onto swxtch after #49 merged (88a46d5417), and added #49's CHANGESET.md entry, since the file lands in this PR. The rebase applied cleanly; there's no file overlap with #49.

@brentyates-swx
brentyates-swx merged commit 0de3445 into swxtch Sep 26, 2026
3 checks passed
@brentyates-swx
brentyates-swx deleted the sync-upstream-script branch September 26, 2026 09:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ci: typecheck the root script/ directory

1 participant