Here's the workflow I'd recommend given your setup - a fork tracking upstream/dev where you care about a subset of packages and have intentional divergences (removed sst, added your own deps, packages/sessions is yours, etc.).
Merge workflow
Before you start
Make sure your working tree is clean. Stash or commit anything in progress.
The merge
bun ./script/sync-upstream.ts
That handles fetch, merge, lockfile, install, conflict scan, and typecheck. When it hits conflicts during the merge, git will pause and you deal with them before the script continues. If you want to do it in stages instead, the manual steps are:
git fetch upstream dev
git merge upstream/dev
Resolving conflicts
Files you always take from upstream (theirs):
bun.lock
packages/app/**
packages/opencode/**
packages/sdk/**
packages/plugin/**
packages/script/**
packages/ui/**
packages/util/**
These are packages you track but don't modify. During a conflicted merge:
git checkout upstream/dev -- bun.lock
For any file in those packages where you have no local changes, same thing:
git checkout upstream/dev -- packages/app/src/some-conflicted-file.tsx
Files you always keep yours (ours):
.gitignore
.scratch/**
.opencode/**
package.json (root - you've removed sst, added your deps)
packages/sessions/** (your package, doesn't exist upstream)
packages/web/** (your fork diverges here)
For these:
git checkout HEAD -- packages/sessions/
Files that need manual merge:
- Root
package.json - take upstream's catalog/version bumps, keep your additions/removals
packages/opencode/package.json - if you've added deps there
- Anything in
packages/web/ where you have local changes
After resolving conflicts
# Take upstream lockfile, reinstall
git checkout upstream/dev -- bun.lock
bun install --frozen-lockfile || bun install
# Scan for leftover conflict markers
grep -rn '<<<<<<' --include='*.ts' --include='*.tsx' --include='*.json' packages/
# Typecheck
bun turbo typecheck
# Regenerate SDK types
bun ./script/generate.ts
# Commit
git add -A && git commit
How often to merge
The upstream moves fast. The merge we just fixed had mangled function bodies because of large diffs accumulating between syncs. I'd recommend:
- Weekly minimum - keeps diffs manageable
- After upstream releases -
git log upstream/dev --oneline | head -5 to spot release: vX.Y.Z commits, those are natural sync points
- Before starting new work on any upstream-tracked package
What not to do
- Don't
bun update after a merge. It bumps versions beyond what upstream tested.
- Don't
bun install without --frozen-lockfile first. If frozen works, you're in sync. If it doesn't, that's your signal to inspect why.
- Don't merge with a dirty tree. The three-way merge on
.tsx files with uncommitted changes is how you get the kind of mangled functions we just cleaned up.
- Don't try to merge
bun.lock by hand. Always take theirs and reinstall.
Git config to help
You can tell git to always take upstream's lockfile during merges:
# In .gitattributes
echo 'bun.lock merge=ours' >> .gitattributes
Actually, that's backwards for your case. You want theirs. The cleaner approach is just the git checkout upstream/dev -- bun.lock step in the script, which you already have.
The script handles the happy path. The discipline part is: when you hit conflicts, categorize each file as "theirs", "ours", or "manual" using the lists above, and resolve accordingly. Don't let git's auto-merge silently mangle function bodies in files you're taking from upstream - if in doubt, take the whole file from upstream with git checkout upstream/dev -- <path>.