fix(sync): use git merge-tree for false-positive-free conflict detection#16
Merged
fix(sync): use git merge-tree for false-positive-free conflict detection#16
Conversation
The previous `Detect merge conflicts` step ran `git merge --no-commit --no-ff origin/main 2>/dev/null` and branched on exit code to set `conflict=true|false`. Two problems: 1. `2>/dev/null` hid git's stderr entirely, so whenever the step misfired we got no diagnostic — just a `(conflicts)` label on a PR that was actually clean. 2. `git merge --no-ff` has degenerate behavior when one side is an ancestor of the other. After a dev→main merge PR, `main`'s tip is a merge commit whose second parent is `dev`'s old tip, so `dev` is an ancestor of `main`. `git merge --no-ff` in this configuration can exit non-zero without an actual conflict, which the workflow then mislabels. This was observed live on PR #13 (the sync-back from the initial dev→main bootstrap): the PR had 0 files changed and was fully mergeable, but arrived labeled `merge-conflict` and titled "(conflicts)". Replace with `git merge-tree --write-tree origin/dev origin/main`, which does an in-memory trial merge without touching the working tree or index. Exit semantics: - 0 = clean merge, output is the merged tree SHA - 1 = conflicts, output contains conflict markers - anything else = real error (propagate) Stderr is now captured into a variable rather than dropped, so any future misbehavior is visible in the run log. This pattern is lifted from the current `vig-os/devcontainer/assets/workspace/.github/workflows/sync-main-to-dev.yml` upstream — confirming this isn't a novel fix but an already-corrected upstream implementation that our stale scaffolded copy never picked up. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This was referenced Apr 15, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Replaces the broken `Detect merge conflicts` step in `sync-main-to-dev.yml` with `git merge-tree --write-tree`, matching the current upstream
vig-os/devcontainerimplementation.Not actually a new upstream bug: the current
vig-os/devcontainertemplate already uses `git merge-tree`. Our scaffolded copy is stale and still runs the old `git merge --no-commit --no-ff` pattern. No upstream issue to file — this is a stale-template sync.What was wrong
On PR #13 (the auto-created sync-back after the initial dev→main bootstrap), sync-main-to-dev opened a PR labeled `merge-conflict` and titled `(conflicts)`, but the PR had `mergeable: MERGEABLE`, 0 files changed, and all CI checks green. Locally, `git merge --no-commit --no-ff origin/main` on dev worked fine.
Root cause: `git merge --no-ff` has degenerate behavior when one side is already an ancestor of the other. After a dev→main merge PR, main's tip is a merge commit whose second parent is dev's old tip, so dev is an ancestor of main. In that topology `git merge --no-ff` can exit non-zero without producing a real conflict, which the workflow then mislabels. The `2>/dev/null` redirect compounds the problem by hiding all diagnostic output.
Fix
```yaml
if merge_out="$(git merge-tree --write-tree origin/dev origin/main 2>&1)"; then
echo "conflict=false" >> "$GITHUB_OUTPUT"
else
merge_rc=$?
if [ "${merge_rc}" -eq 1 ]; then
echo "conflict=true" >> "$GITHUB_OUTPUT"
...
else
echo "::error::git merge-tree failed with exit code ${merge_rc}"
exit "${merge_rc}"
fi
fi
```
`git merge-tree --write-tree` does an in-memory trial merge without touching the working tree or index. Exit 0 = clean (outputs merged tree SHA), exit 1 = conflicts, anything else = real error. stderr is now captured instead of dropped, so future misbehavior is visible.
Test plan
Out of scope
The upstream workflow also adds container-based execution, a `resolve-image` action, retry wrappers, and a `git push` flow for branch creation. Those require infrastructure (`resolve-image` action, the vigOS devcontainer image) that we don't have locally. This PR is the surgical fix; a broader template resync is a separate task.
🤖 Generated with Claude Code