---
title: Branch synchronization
description: >-
  Detect parent-branch drift on every push and safely align issue or
  pull-request branches.
---
> ## Documentation Index
> Fetch the complete documentation index at: https://docs.page/vypdev/copilot/llms.txt
> Use this file to discover all available pages before exploring further.

# Branch synchronization

Copilot can watch parent-to-child branch relationships and keep the related open issue's branch status current when its working branch falls behind. Detection is intentionally separate from the normal commit workflow: `copilot_branch_sync.yml` listens to pushes on **all ordinary branches** (including bot and installer pushes), excluding the reserved `copilot-permission-test-*` refs used for setup permission checks, performs GitHub metadata and comparison requests, and never loads Bugbot or a code-changing agent. English is the default. English and Spanish notices use bundled catalogs; another configured BCP-47 locale may make one schema-constrained language request for the complete branch-notice catalog. Invalid or unavailable localization falls back atomically to English before any comment is written.

This separation keeps pushes to `main`, `master`, `develop`, release branches, and intermediate feature branches observable without paying the cost or accepting the noise of the full commit-analysis pipeline.

## Detection flow

On each non-deletion push, the observer:

1. reads open issues and open pull requests;
2. resolves branch relationships from Copilot's durable issue configuration first, then from GitHub linked branches and PR base/head data;
3. selects relationships where the pushed branch is either the parent or the working branch;
4. compares each working branch with its parent; and
5. reconciles one bot-authored synchronization status card on the issue.

The card is stateful. The first stale observation creates only that card. Further parent pushes update it only when the rendered comparison changes. When the working branch catches up—including after an automated synchronization—the same card is marked resolved. If a later push changes that resolved card back to stale, Copilot also creates one short action notification linked to the card so the newly required action is not hidden by an edit. Replays of the same source head create nothing, and concurrent duplicates are removed or compacted to a link to the oldest notification.

A PAT-authored sync push is observed too, so a chain such as `develop → feature/A → feature/B` can propagate recommendations to the next level.

Notice prose uses the issue locale inherited from the repository locale unless `issues-locale` overrides it. Commands, branch names, comparison URLs, markers, and machine-readable state remain unchanged in every language.

## Status and notification lifecycle

| Previous state | New comparison | Conversation effect | Why |
| --- | --- | --- | --- |
| No card | Stale | Create one status card | The card itself is the initial notification. |
| Stale | Same rendered comparison | No mutation | Equivalent pushes should be silent. |
| Stale | Changed comparison | Update the card | The current fact changes without creating timeline noise. |
| Stale | Aligned | Update the card to resolved | The action is complete; GitHub does not need another notification. |
| Aligned | Stale | Update the card and create one short linked notification | Human action has become newly necessary. |
| Aligned | Stale replay at the same source head | No additional notification | The transition fingerprint is immutable and idempotent. |

With the default locale, the transition notification is intentionally compact:

```markdown
Branch synchronization needs attention: `feature/42` is behind `develop`. [Open the current status](https://github.com/example/project/issues/42#issuecomment-8).
```

With `issues-locale: es-ES`, the same semantic transition is rendered in Spanish:

```markdown
La sincronización de la rama necesita atención: `feature/42` está por detrás de `develop`. [Abrir el estado actual](https://github.com/example/project/issues/42#issuecomment-8).
```

Long branch names use a shorter localized notification with the same status link. Open that card to see both complete refs and the command to run. Notifications stay within the fixed copy budget without displaying internal truncation markers.

The visible wording never participates in notification identity. Changing a translation therefore does not recreate or rewrite an already issued notification. The workflow Job Summary records whether the notification was created or reused and reports bounded duplicate-cleanup evidence without copying the message body.

<Info>
  A manually created linked branch needs an open PR whose head is that branch to establish its parent. Branches created by Copilot already persist `parentBranch` and `workingBranch`, so they do not require this fallback.
</Info>

## Install the lightweight workflow

`copilot setup` installs the observer together with the commit capability. For a manual installation, copy `setup/workflows/copilot_branch_sync.yml` or use this equivalent:

```yaml
name: Copilot - Branch Sync

on:
  push:
    branches:
      - '**'

jobs:
  branch-sync:
    runs-on: ubuntu-latest
    timeout-minutes: 120
    permissions:
      contents: read
    steps:
      - uses: vypdev/copilot@v3
        with:
          single-action: check_branch_sync_action
          token: ${{ secrets.PAT }}
```

Do not add the normal `COPILOT_BOT_LOGIN` actor gate to this workflow. A synchronization push made by the bot must be allowed to run the observer so dependent child branches are checked. The explicit PAT performs GitHub reads and issue-comment writes; the implicit `GITHUB_TOKEN` remains read-only.

## Align a branch

Prepare the project runtimes in the job that handles the command before enabling
verification. Kotlin/JVM checks need Java in that job even with `--no-agent`;
another CI job's Java setup does not carry over. Add the reviewed
[verification prerequisites](/security-operations/operations/verification#repository-verification-prerequisites)
declaration for an actionable version check and a safe abort before a missing
runtime can execute the configured repository command.

An authorized maintainer can run this command in the related issue or PR:

```text
/copilot sync-branch
```

The natural-language form is also supported:

```text
@vypbot update the issue's branch
```

Options:

| Option | Effect |
| --- | --- |
| `--dry-run` | Prepares the merge and reports whether it is clean or conflicted, then aborts without calling the agent or pushing. |
| `--no-agent` | Allows a clean merge but aborts safely if conflicts occur. |
| `--from <branch>` or `--from=<branch>` | Uses an explicit parent branch after validating it as a Git ref. |

The command fetches the current remote heads, checks out the working branch, and prepares a no-fast-forward merge. A clean merge proceeds without an agent. Before the push, Copilot runs the configured `bugbot-fix-verify-commands`, verifies that those commands did not mutate the prepared merge, and confirms that neither remote head changed while the operation was running.

Captured Git reads retain their exact file paths without printing NUL-delimited
records into the Actions log. Verification and abort diagnostics remain visible
in the GitHub log viewer.

If `--no-agent` encounters conflicts, the reply identifies that option as the
blocker and explains that no synchronization commit was pushed. Run
`/copilot sync-branch` to allow the configured fixer to resolve eligible conflicts,
or use `--dry-run` to inspect the merge. If no fixer is configured, the reply
instead asks you to configure a supported fixer before retrying. These are
distinct recovery states; neither response claims that a remote branch changed.

## Conflict resolution boundary

When Git reports conflicts, the configured fixer agent may resolve them under a narrow contract:

- no more than 20 conflicted paths;
- no credentials, private keys, environment files, or workflow files;
- edits restricted to the paths Git identified as conflicted;
- no agent-controlled checkout, reset, merge, commit, or push;
- no unresolved markers, unexpected paths, changes to non-conflicted index entries, or changes to `HEAD`/`MERGE_HEAD`; and
- all configured verification commands must pass in the credential-free untrusted-command environment.

The agent resolves files without staging them. Copilot checks the original
conflict paths, merge identity, protected index entries and remaining markers,
then stages only those resolved conflicts through trusted Git code. It rejects
later changes introduced by verification rather than staging them again.

The three checked-in `ncc` bundles (`build/cli/index.js`, `build/api/index.js`,
`build/github_action/index.js`) retain their generator's whitespace. Source and
all other paths retain strict whitespace checks. Conflict markers are rejected in
all files, including those bundles, before staging and in the staged result.
This changes no content, allowed edit paths or verification requirements.

If `specs/CATALOG.md` is conflicted, Copilot treats it as a generated index:
after the catalog metadata is resolved, the Action validates that metadata and
regenerates the index with its bundled catalog renderer before verification.
The fixer must preserve both branches' specification ownership and evidence in
`specs/catalog.json`. The Action also refreshes a stale index when a clean merge
already changes both catalog files. It never adds an unrelated file to the merge
or loads a generator from the merged branch. Invalid metadata, missing evidence,
external links, or a later verification change still abort the synchronization.

When the communication test-budget ledger is an original merge conflict,
Copilot derives its totals from the retained test-file allocations and validates
the complete ledger before staging. It preserves the minimum budgets and checks
that every allocation has real test evidence. Invalid input produces
`communication-budget-invalid`; inspect the closed validation phase in the run,
correct the resolution and retry. A later verification change is rejected and
cannot trigger another normalization.

If verification prevents a normal Git abort, Copilot restores the captured
child only while both prepared merge identities remain unchanged. A moved
HEAD or parent is retained and requires inspection of the run.

If any boundary fails, Copilot aborts the merge and does not push. Immediately before committing, it fetches both branches again and rejects stale work. Git credentials are scoped only to trusted fetch/push subprocesses and are never exposed to the agent or verification commands.

## Result report

Every command finishes with a comment in the conversation. The report distinguishes:

- already aligned;
- dry-run clean or conflicted;
- merged cleanly without an agent;
- merged after fixer-agent conflict resolution; or
- safely aborted, with a publishable reason and no provider-secret details.

When workspace validation rejects a resolution, the reply and workflow log
include a closed diagnostic code such as `sdd-catalog-invalid`,
`unstaged-changes` or `unexpected-paths`. Inspect that code before retrying;
Copilot preserves the remote branches and does not publish the rejected merge.
Private paths and provider output are excluded from the public diagnosis.

For `sdd-catalog-invalid`, the operator log also records the closed catalog
validation phase before abort restores the worktree. This distinguishes
remaining metadata conflict markers or invalid JSON from evidence-path or
catalog-contract failures. It includes no metadata values, paths, parser errors
or validator output. Correct the failing conflict resolution and retry the
same command; the Action retains all validation and remote-head checks.

Successful and no-change reports use the issue or pull-request surface locale,
are correlated to the requesting comment, and are reconciled at most once on
event replay. For example, an already aligned branch receives one sentence:

```markdown
No changes were needed — `feature/42` already contains `develop`.
```

A completed merge names the branches, pushed commit, fixer use when applicable,
and number of verification checks. It never repeats internal execution steps or
adds a generic action summary to the conversation.

Successful reports include the parent and working branches, verification count, and merge commit SHA in the structured run result. The subsequent push observer resolves the prior stale-branch card.

## Why the normal commit workflow remains limited

`copilot_commit.yml` is an issue-centric, agent-capable pipeline: it can
calculate size/progress and run Bugbot. Pushes themselves stay silent; a
purpose-specific progress or review surface is updated only when that outcome
exists. Its default exclusion of `master` and `develop` avoids irrelevant issue
lookup, agent provisioning, duplicate PR analysis, higher API/model usage, and
bot feedback loops on integration branches.

The lightweight observer provides the useful part of ordinary-branch visibility without broadening that expensive workflow. Repositories may still remove the commit-workflow exclusions deliberately, but branch synchronization does not require doing so.

This repository runs agent-capable synchronization on the shared Mac/Ubuntu
`codex` pool. Windows services omit `codex`; no new Windows Codex synchronization
is scheduled. Verification retains `test:coverage`, its two-worker limit and all
coverage/acceptance budgets. A failed verification retains the managed remote
head; inspect the failure before retrying `/copilot sync-branch`.

Commands accept leading whitespace, line breaks and repeated whitespace between
`/copilot` and the command name. This repository prepares its verification
toolchain for those requests and addressed bot mentions before the Action runs.
Preparation is only a conservative scheduling hint; the Action's parser, actor
permissions, original conflict set and dry-run/no-agent rules remain authoritative.
The fixer must check existing offline tests for affected modules and typecheck
when tools are available; mandatory Action verification still runs independently.


A completed issue synchronization returns the issue to working, including an
already-aligned retry after a previous failure. The Action verifies the retained
completion facts before that transition. Dry-runs and unavailable results keep
existing state; another error in the same run still blocks it. Synchronizing a
branch does not establish PR approval or current SDD readiness.

A conflict resolved to the working branch's unchanged content or deletion may
disappear from Git status after trusted staging. Copilot uses that resolved
workspace as the verification baseline while still rejecting added files, lost
non-conflicted parent changes, and later changes to the resolved path set.


For `unexpected-paths`, the run also shows a fixed validation phase and counts
of added paths, removed conflict/protected paths and added canonical bundles.
Use that distinction to inspect the resolution or verification step before
retrying. Copilot omits private filenames and preserves the same merge guards.


## When the issue closes

After a managed PR merges, the Action retires existing synchronization cards
for its closed issue. The same card and link now explain that the recommendation
is historical and no synchronization is required for that closed work. Closing
an issue does not prove that its branches are synchronized. Earlier timeline
alerts remain historical; follow their status link to read the current card.

The observer also checks native issue state before publishing advice. A reopened
issue can receive a fresh comparison and recommendation. If retirement cannot
verify the issue or its existing card, the run reports the incomplete cleanup
and retains native closure and any updates already completed. Retry the verified
merged-PR operation after resolving the provider error. Retirement does not
create branches, run a fixer, change labels or publish another notification.


Closed-issue retirement uses bundled English or Spanish copy. Other issue locales receive complete English fallback; retirement never starts a language model. Active branch observations retain the configured catalog-localization behavior described above.

Manual closures and the inactivity bot use the same retirement rule through the
closed-issue event. If that cleanup fails, retry the closed event or verified
merged-PR operation. Cleanup preserves other metadata and does not launch open
work, change labels, run a model or rewrite hidden configuration. Reopening
allows the next branch observation to replace historical closure with its fresh
aligned or stale comparison.

Later edits and label changes on an already-closed issue do not repeat retirement
or restart its work. Retry the original close event when cleanup needs recovery.

Cleanup reads the comment inventory once, then verifies each card directly by its
comment ID before editing it. A failed or changed card read leaves that card
unchanged and reports incomplete cleanup; earlier successful edits are retained.

The observer checks that an issue is still open before requesting locale copy.
If it closes while that copy is being resolved, the observer retires its existing
card and skips the branch comparison and new advice.

When generated bundles conflict, the fixer must regenerate the admitted bundles
before returning its resolution. The Action stages that result and verifies it
again. A rebuild that changes the staged resolution during verification stops
the synchronization with `unstaged-changes`; no synchronization commit is pushed.
