---
description: "Find drifted anchors with git span drift, auto-resolve what's mechanically safe, and manually re-anchor the rest."
---

# Reconcile drifted spans (/docs/guides/reconcile-drifted-spans)

> [!NOTE]
> Under an agent session, most positional drift never reaches this guide — the touch hook heals a pure line-shift the moment the edit lands, and the commit advisor re-runs a scoped `drift --fix` before classifying a changeset. What follows is for what neither can heal automatically: genuine semantic drift (content that no longer matches what a span asserts), a held command's checklist, or reconciling outside a hooked session entirely (a human-authored commit, CI, or a harness without the hooks wired up). See [Agent integration](/docs/agent-integration) for the hook mechanics.

## Run the scan

Run `git span drift` to check every span in the repo for drift:

```bash
git span drift
```

With no drift, it prints a summary line (`0 drift across N spans (K anchors checked)`) and exits 0. When drift is found, it exits 1 and lists each drifted span with its anchors:

```
## checkout-flow
- api/charge.ts#L3-L5 — changed in the working tree
- web/checkout.tsx#L4-L6 — moved to web/checkout.tsx#L6-L8

Checkout submit flow that carries a charge attempt from browser to server.
```

A drifted span's listing includes every anchor on that span, not just the drifted ones — an anchor with no drift is printed with no status suffix — and closes with the span's why. Read each finding before acting: `changed in the working tree` means the anchored content itself differs; `moved to <path>#L<start>-<end>` means the same content shifted to a new range. A whole-file anchor only ever reports `changed` — any edit to the file, including adding or removing lines, counts as a content change.

## Auto-resolve what's safe

Run `git span drift --fix` to re-anchor what's mechanically safe:

```bash
git span drift --fix
```

`--fix` re-anchors `Moved` anchors — the same content at a shifted line range or a renamed path — and whitespace-equivalent `Changed` anchors, cases where the anchored content was reformatted (indentation, whitespace) but is otherwise identical. The equivalence is judged on the whole anchor: if the range mixes a reformat with even one non-whitespace edit, the anchor counts as meaning-altering and `--fix` leaves all of it alone. Those anchors report `resolved, pending commit — auto-updated` on the `--fix` run itself, and `resolved, pending commit` on later `drift` runs until you commit the `.span/` rewrite. That status is not drift — it's tallied with fresh anchors and does not affect the exit code.

> [!WARNING]
> `--fix` never touches meaning-altering `Changed` drift — content that differs beyond whitespace is left for you to resolve by hand, and re-running `--fix` again does not help. `--fix` only works with `--format human`, and it never commits: it just rewrites `.span/` in the worktree.

After running `--fix`, re-run `git span drift` and treat anything still listed as drift as needing manual re-anchoring. See [Re-anchor after an edit](/docs/guides/re-anchor-after-an-edit) for the `git span add` / `git span remove` steps.

## Resolve what's left by hand

For each anchor still drifting after `--fix`, decide which side of the coupling is authoritative, then act:

* **The anchors still agree.** The edit changed the content without breaking the coupling. Re-anchor with `git span add`: if the content changed in place, re-add the same address — `add` resolves the existing anchor in place ("Added 0 anchors and resolved 1 in place"); if the range moved, swap the old anchor for the new one with `git span replace`. Follow [Re-anchor after an edit](/docs/guides/re-anchor-after-an-edit).
* **The anchors now disagree.** Conform the side that a confirmed authority or satisfied gate decides, then validate any code change. Without a confirmed contrary authority, docs follow deliberate committed code. After a gate transition, revise or retire the why and every superseded anchor. Surface ambiguous authority instead of recording a contradiction as the new baseline, and require scoped zero drift either way.
* **Coupling is gone.** Remove just that anchor with `git span remove`, or delete the whole span with `git span delete` if none of its anchors are still meaningful.

Each `git span add` (and `git span why` write) ends with a scoped post-write check over the touched span, so the mutation output itself carries the reconciliation verdict: the requested-address lines (`added` / `resolved in place` / `unchanged`) state only the local fact, then the span-wide line asserts the whole-span state — `0 drift across ...`, `N anchors drifted — ...`, `state indeterminate (index changed during check)`, or `state unverified`. The exit code matches drift's contract: 0 = clean, 1 = drift remains or the check errored, 2 = indeterminate (index changed during check) — retryable, so re-run the command. Old anchors the mutation leaves drifted are printed with their canonical address and a runnable `git span remove` next action. Read the mutation's span-wide line before committing; the trailing `git span drift <name>` stays the final confirmation.

Before deciding, recover the original intent:

```bash
git span why checkout-flow
git span history checkout-flow
```

`why` shows the current decision and may name an authority. `history` walks the span's commit-by-commit timeline, including each anchor's recorded content and live drift. Use both to establish intent and provenance; a commit explains a change but does not override an explicit, still-valid authority.

## Commit the result

Review what `--fix` and your manual re-anchors changed, then stage and commit — the `.span/` rewrite together with the source edits that caused the drift, usually in the same commit:

```bash
git diff .span/
git add .span <changed source files> && git commit
```

An anchor that `--fix` resolved keeps showing `resolved, pending commit` until both sides are committed — committing `.span/` alone while the drifted source content stays uncommitted leaves the status in place.

## If a commit was denied

Under a hooked agent session, `git commit`/`git push` can come back denied instead of landing, with a checklist describing exactly the debt above. Resolve it the same way — the checklist names each drifted span or uncovered file — then retry the identical command. Both denial kinds are consider-once: semantic-drift and uncovered-writes denials each deny once per distinct debt state, and an identical retry proceeds. Editing a span's anchors changes the findings and earns one fresh deny. See [Agent integration](/docs/agent-integration) for the full denial-message shapes.

## Merge conflicts in `.span/`

A merge can leave a span file with conflict markers. `git span drift` reports the span itself with a `— conflict` suffix in human output (`CONFLICT` in the machine formats), and `git span show` refuses to render a span in that state.

`git span drift --fix` resolves what it can structurally — splitting the conflict markers into ours/theirs and merging anchors that don't genuinely disagree — but only when every source file the span references is itself free of conflict markers (the clean-source precondition). It also resolves a renamed or deleted source path automatically: if an anchor's path is missing on one side and exactly one anchor on the other side shares its exact line range at a different, readable path, `--fix` drops the dead anchor and keeps the live one, re-pointed and re-hashed against the worktree — no manual step needed. Three cases stay manual:

* If the span's `why` text diverged between the two sides with no common merge base, `--fix` writes the anchors clean but leaves the why's conflict markers as residue, and the file stays unmerged for you to sort out by hand.
* Any anchor whose source file still contains live `<<<<<<<` markers is reported as a conflict and left untouched — resolve the source file first, then re-run `--fix`.
* A renamed or deleted source path with no readable same-line-range counterpart on the other side, or more than one, is reported as a conflict — the warning names the unreadable path (and, when ambiguous, the candidate paths it could not choose between).

> [!WARNING]
> After a `--fix` during a merge, always re-run `git span drift` — a `--fix` exit code of 0 does not guarantee the span came out fully clean. Also re-check `git span why <name>`: a merge resolution can leave the why empty even when both sides carried the same text; if it's blank, re-set it with `git span why <name> "..."`. Git still expects you to finish the merge with `git commit`.
