open-swe/docs/upstream-sync/cherry-pick-runbook.md
Adam Moussa 6b73877889
docs(upstream-sync): add cherry-pick runbook
Repo-specific runbook for bringing upstream (langchain-ai/open-swe) commits
into the fork: triage-sync discovery, the git cp workflow, the triage ledger,
themed-branch layout, and conflict/regression handling.
2026-07-02 20:34:25 -04:00

4.7 KiB

Cherry-picking upstream into the fork

This repository is a long-lived fork of langchain-ai/open-swe (git remote upstream). Upstream changes are brought in one commit at a time with git cherry-pick, and every diverged commit is tracked in a triage ledger so a decision is made once and not revisited. dev is the integration branch; the broader strategy lives in the fork-maintenance section of CLAUDE.md.

Setup (once per clone)

make install-hooks

Installs the triage hooks and the git cp alias by pointing core.hooksPath at .githooks/. Because that shadows the machine-global hook directory (~/.config/git/hooks, which holds the mandatory security pre-push), .githooks/pre-push is a shim that re-execs the global hook, and the installer verifies that delegation before it changes anything. git never auto-adopts a repository's core.hooksPath, so this step cannot be skipped.

Note: core.hooksPath applies repo-wide, but .githooks/ is a tracked directory. The hooks (and the security shim) only run on branches that actually contain .githooks/. Keep it present on dev and main so no branch loses the security pre-push.

Finding what to pick

make triage-sync    # git fetch upstream, then append new dev..upstream/main commits
                    # to the ledger as `untriaged` (PR # + subject parsed from each)

triage-sync is the discovery step: it records every diverged commit as untriaged and bumps "Last synced" to the new upstream/main tip. Triage those rows (decide deferred / wont-merge and which branch), then pick the ones you want. The underlying views if you prefer raw git:

git fetch upstream
git log --oneline --no-merges dev..upstream/main   # everything diverged
git show <sha>                                      # inspect before deciding

Cross-check candidates against the ledger first — most diverged commits already carry a decision (already-in-dev, regression, deferred, or landed) and should not be re-examined.

Bringing in commits: git cp

git cp -x <sha>              # pre-check the ledger, cherry-pick -x, auto-reconcile
git cp -x <sha1> <sha2> ...  # several, applied in the given order
git cp --continue            # after resolving a conflict; also reconciles
git cp --force <sha>         # override a SHA the ledger marks "Won't merge"

git cp reads docs/upstream-sync/triage.jsonl before touching the tree and refuses a known-reject SHA (override with --force). On success it runs make triage-reconcile, which moves each applied SHA to Landed in the ledger and stages triage.jsonl + triage.md for you to commit.

Apply commits in upstream chronological order (oldest first), not the order you happen to list them — a later commit often depends on an earlier one, and out-of-order picks conflict needlessly:

git log --reverse --topo-order --format=%h dev..upstream/main

The triage ledger

docs/upstream-sync/triage.jsonl is the source of truth: one JSON row per upstream SHA, keyed on the SHA (stable, unlike the local SHAs cherry-pick rewrites). docs/upstream-sync/triage.md is generated from it and must not be hand-edited. Dispositions are landed, wont-merge, deferred, untriaged.

scripts/triage.py set <sha> --disposition deferred --branch slack-tooling --reason "..."
make triage-render      # regenerate triage.md from the jsonl
make triage-check       # CI gate: fail if triage.md is stale

A SHA marked wont-merge is hard-blocked by both git cp and the prepare-commit-msg hook. Override for a one-off re-evaluation with git cp --force, SH_CHERRYPICK_ALLOW_REJECT=1, or git config sh.cherrypick.blockRejects false.

Branch layout

Never cherry-pick onto dev directly. Work on a themed branch off dev and open a PR into dev; the ledger's branch column records where each deferred commit is meant to land (for example slack-tooling, gateway-routing, plan-approval, durable-dispatch). Keep each PR to one theme so conflict resolution stays within one subsystem.

Raw git cherry-pick

The hooks fire on a plain git cherry-pick -x <sha> too: post-commit journals each applied pick and prepare-commit-msg blocks known-rejects. Run make triage-reconcile once at the end to land the picks in the ledger, then commit triage.jsonl + triage.md.

Conflicts

# resolve the files, then:
git add <files>
git cherry-pick --continue    # or: git cp --continue
git cherry-pick --abort       # bail out of the whole pick
git cherry-pick --skip        # drop just this commit and continue the batch

A commit that conflicts because dev already carries a newer version of the same code is a regression, not a merge — skip it and record the decision as wont-merge in the ledger rather than forcing it in.