Merging is how work reaches main, and it's the only way. A merge takes a whole branch β every checkpoint on it β and lands it on main as one squash commit whose message is the branch description.
Merging is a human decision. Agents should push their branch and stop; a person reviews and merges. (Nothing enforces that β it's the workflow Oak is built around, and what the agent skill teaches.)
The merge, step by step#
oak desc "Cache tile lookups in the renderer" # this becomes the commit message on main
oak push # publish the branch
oak pull # bring main's newer commits into your branch
oak diff --branch # last look at everything the branch changes
oak merge --dry-run --json # would it merge cleanly? (local prediction, changes nothing)
oak merge # squash onto main and close the branch
What each step is for:
oak pullfirst. It merges the currentmaininto your branch, so conflicts surface on your branch, where you can resolve them calmly β not halfway through landing onmain.oak merge --dry-runpredicts the merge locally without fetching, pushing, or touching files.oak mergeasks the server to squash the branch onto its parent (main) and close it. If the repo has CI, the merge gate applies.
You can merge a branch you aren't on: oak merge fix-login-redirect. And you can merge from the browser β every branch's page on oak.space has a Merge button, and the branches list has Merge selected for landing several non-overlapping branches at once. The web merge checks for conflicts before you click.
Merging doesn't run inside a mount. Merge from a regular clone (a sparse one is fine) or from the web.
What lands on main#
One commit:
- Message: the branch description (or the branch name, if it has none).
- Contents: the branch's final tree, merged with
main. - Ancestry: the squash commit keeps a pointer (
merge_parent_hash) to the branch's last checkpoint, so the full pre-squash history stays reachable for tooling, even thoughmainshows one line per task.
A merge to main fires the merge event for CI and webhooks.
Resolving conflicts#
A conflict can come up during oak pull (merging main into your branch), during oak merge, or during oak pull inside a mount. They all work the same way.
oak pull
# CONFLICT in src/render/tiles.rs
oak conflict status # what's in progress and how much is left
oak conflict show # per-file details for every conflicted path
$EDITOR src/render/tiles.rs # resolve by hand: remove the <<<<<<< ======= >>>>>>> markers
oak conflict take src/render/tiles.rs --theirs # β¦or take one side of every block in a file
oak pull --continue # finish
oak pull --abort # β¦or back out; the branch is left as it was
During a merge, the same commands finish or cancel it: oak merge --continue, oak merge --abort.
- Markers are line-level. Oak wraps only the diverging lines, not whole files.
--ourskeeps your branch's side;--theirskeepsmain's (or the remote's).- Every
oak conflictcommand takes--json, so handing resolution to an agent is a supported path.oak diff --checkcatches markers you forgot to remove. - While a conflict is in progress, commands that would change the branch exit with code
5.
When a pushed branch conflicts#
If an agent's branch has gone stale against main, you don't need to redo the work. Mount the branch itself, pull, resolve, and push:
oak mount acme/web ./fix-auth --branch fix-auth--a1b2c3d4
cd fix-auth
oak pull # conflicts appear here
# resolve, or hand the mount to an agent to resolve
oak pull --continue
oak commit && oak push
The CI merge gate#
If the repo has CI workflows, a merge onto main is refused while the branch head's CI is failing or still running. Specifically: for the commit at the tip of the branch, the latest run of each workflow must have concluded success (or been skipped). A tip with no runs at all isn't gated.
oak ci status # the gate's verdict for your branch head β exit 0 pass, 1 fail, 3 still running
oak merge --wait # wait for CI to finish (default 30 min; --wait=60 for longer), then merge if it passed
oak ci logs <run-id> # read the failure
oak ci rerun <run-id> # re-run it, if the failure was infrastructure rather than code
oak merge --force # deliberately override the gate β after you've read why it's red
A forced merge is recorded in the organization's audit log. Over the API, the gate is HTTP 412, and ?force=1 overrides it β see the API reference.
Undoing a merge#
Open the commit on oak.space and click Revert: Oak lands a new commit on main that applies the inverse of that commit's changes, like git revert. If any file the commit touched has changed since, the revert is refused rather than guessed at. The API equivalent is POST /api/{owner}/{name}/commits/{hash}/revert.
When a merge is refused#
| What you see | Meaning | What to do |
|---|---|---|
| CI gate (412) | CI on the branch head is red or still running. | oak ci status, oak merge --wait, or fix and push. |
Conflicts (409 with conflict_paths) | The branch and main changed the same lines. | oak pull, resolve, push, merge again. |
main_moved / branch_moved (409, retryable) | Someone landed on main, or pushed to the branch, during the merge. Nothing was merged. | Retry. |
| "already closed" (400) | The branch was already merged or closed. | Nothing β check oak branch show <name> --remote. |
| "Repository isn't linked to an organization yet" | The repo has never been pushed. | oak push --repo <org>/<name> first. |