---
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. selects the issue's configured parent and working branch, or one unambiguous relationship proven by native GitHub links when no branch pair has been configured;
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.

## Relationship ownership and historical notice recovery

A configured `parentBranch` and `workingBranch` identify the issue's current
relationship. A different PR mentioning `#79`, “depends on #79”, or even a closing
keyword that GitHub has not linked does not acquire that issue's branch status.
An explicit native link to another PR does not replace an existing configured
pair. Without a configured pair, fallback needs one unambiguous local branch/PR
relationship; fork heads, foreign issues with the same number, incomplete links,
and conflicting pairs are withheld. Invalid or partial configuration must be
repaired before weaker evidence can be used.

For example, “Keep #79 disabled; closes #80” in a PR for #80 must not announce
that #79 is synchronized or ask #79 to synchronize #80's branch. A release issue's
configured `release/1.5.2` → `develop` pair also takes precedence over a maintenance
PR targeting `master`.

On a later eligible push to the actual parent or working branch, Copilot can
retire old bot-owned status cards for other pairs. Their existing comment becomes:

```markdown
## Previous branch advisory retired

The current relationship for issue #89 is `bugfix/89-authorization`, with parent `develop`.

This previous card concerns a different relationship and no longer provides instructions for this issue. Retirement does not certify synchronization of either branch.
```

The current issue's own status card remains independent. Retiring old advice
neither merges branches nor resolves current drift. Copilot checks the live issue,
relationship, card author and exact body before editing, then verifies the final
card. Human edits, ambiguous authority or unavailable evidence stop repair. Up to
20 historical cards are processed per issue/observation; a larger inventory stops
instead of silently editing a subset. Existing links remain valid and equivalent
replays stay quiet. Old transition notifications remain historical links to these
status cards.

Recovery happens on eligible observations after adopting the fixed runtime; it is
not a repository-wide sweep. An unrelated branch push is insufficient. No new
configuration is required. A sync command is unnecessary for retiring old advice;
use the status card for the verified current branch when deciding whether to sync.

## 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.

## Catalog compatibility during a merge

When a merge needs to refresh `specs/CATALOG.md`, Copilot first identifies the
catalog format. SDK version-1 catalogs retain the bundled validation contract.
Legacy combined-evidence, repository-native, mixed and unknown formats stop
with a bounded field/entry diagnostic before automatic conflict resolution.
Conflicted catalog JSON requires compatible immutable originals on both sides;
a clean, reviewed SDK migration can use the merged metadata directly. A conflict
in JSON alone also requires preflight and full validation after resolution.
Readable metadata changes are checked even when another file is conflicted.
An index outside the original write scope is validated read-only: if it would
need regeneration, synchronization stops instead of adding another editable or
staged path. Review and repair the catalog/index pair before retrying.

The response distinguishes unsupported profiles from invalid SDK metadata and
reports whether the local abort call completed. It attempts no synchronization
commit or push and never converts evidence or invents dates. Follow
[catalog compatibility recovery](/security-operations/operations/troubleshooting#branch-sync-catalog-compatibility)
to preserve historical evidence in a reviewed migration or supported adapter.
After adopting the runtime and correcting the contract, retry from fresh heads;
the application verification commands still determine whether publication is
allowed. A merge unrelated to catalog refresh keeps its existing behavior.

## 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

In the Copilot SDK repository, checked-in ncc bundles and API declarations use
`merge=binary` to expose two-sided generated changes as explicit conflicts. The
fixer resolves source intent and regenerates only the admitted artifacts with
the existing offline build before the Action stages them. Ordinary source merges
and one-sided generated updates retain their normal Git behavior. Consumer
repositories choose their own generated-artifact policy; setup does not install
this repository-specific attribute.

If verification produces additional generated changes, synchronization stops
without publishing. Inspect the original conflict list and canonical build;
retry after correcting the source or merge policy. Do not stage post-verification
changes or repeatedly run the unchanged synchronization.

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 a precommit boundary fails, Copilot attempts to abort the prepared merge and does not push. A completed commit is preserved when later publication fails; see the recovery states below. 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 before commit, with a publishable reason and no provider-secret details;
- committed locally but publication rejected or unverified; or
- the exact committed candidate independently confirmed on the remote after a failed push response, without a second push.

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.

### Recover a committed candidate after publication failure

A successful commit ends the abortable merge phase. If the push fails, Copilot
keeps that workspace and makes one authenticated observation of the existing
working branch. It reports the original, candidate and observed commit identities,
whether push was attempted, and one of these results:

| Observation | Meaning | Next action |
| --- | --- | --- |
| Original head | Candidate not observed as published | Inspect the rejection before a fresh managed synchronization. |
| Exact candidate | Publication independently confirmed | Command completes; no second push is sent. |
| Other head | Another current head exists; ancestry is not established | Inspect native branch history before retrying. |
| Unavailable | Publication remains unknown | Establish the current remote state before retrying. |

An unavailable candidate identity stops the push. Runner-local candidates are
temporary, not durable backups. Recovery does not abort/reset a committed
workspace, force-push, create backup branches or modify credentials.

An explicit remote workflow-write rejection becomes nonretryable
`authorization.denied`, with localized instructions to review the actual
publisher credential. A workflow path alone does not prove insufficient scope.
See [workflow-write recovery](/security-operations/operations/troubleshooting#branch-sync-workflow-write-recovery)
for credential-specific guidance. Existing branch ownership, verification,
security and coverage gates still apply.

### Validate the synchronized pull request

The synchronization report confirms its configured verification commands and
published merge commit. Review, issue progress, CI and Codecov remain separate
results that must refer to the current PR head and target base before merge.

If the repository's bot-actor admission rule skips Commit or PR analysis for the
synchronization push, a skipped run does not certify those results. On the PR,
`/copilot recheck` requests a read-only Bugbot review of the current revision; it
does not calculate issue progress or replace CI and coverage checks. Progress
assessment runs on the next ordinary contributor push or an explicitly
configured `check_progress_action` single action. Inspect each applicable
current-head result, including unresolved findings and any human approval gate;
a clean synchronization is not merge approval.

Progress assessment separates human acceptance text from a recognized persisted
recommendation-state block. The full original issue body still controls input
bounds and freshness; saved operational state does not create extra requirements.
For a scope-capacity failure, inspect the reported phase, clause kind, count and
original source line, keeping the human requirements intact. Follow the
[scope recovery guide](../security-operations/operations/troubleshooting#progress-scope-rejects-a-generated-plan-or-exceeds-32-clauses)
before starting another assessment; Bugbot review has its own independent
completeness and capacity checks.

## 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.


### Candidate dependencies and blocked verification

An installation on the default checkout does not prepare a different temporary merge. Copilot's source workflow installs the frozen current workspace at each verification boundary, including after eligible conflict resolution. Consumer projects keep their explicit same-job provisioning or reviewed optional repository setup.

If synchronization stops during verification, its existing result card identifies the phase, safe command label/ordinal, closed cause and original head. `restored` confirms the original local head after abort; `unverified` requires operator inspection before retrying. No sync commit/push is attempted after verifier failure. Repair the reported dependency graph, same-job setup/runtime or verifier, then rerun `/copilot sync-branch`; repeated blind retries do not repair missing workspace packages. See [verification and source readiness](../security-operations/operations/verification).


## Ineligible conflict sets

A protected-path conflict or a set exceeding 20 paths stops before the fixer,
repository verifiers and synchronization push. The error identifies the refusal
with safe counts and a supported recovery action. Existing remote heads remain;
the message does not claim that a failed local abort restored the checkout.
Inspect the original conflict paths in the linked job, complete an authorized
maintainer recovery or reviewed source update, then use `/copilot sync-branch --dry-run`. Repeating the unchanged command cannot alter these safety policies.
