---
description: "Update a span's anchors after a deliberate code change — in place, or to a new range."
---

# Re-anchor after an edit (/docs/guides/re-anchor-after-an-edit)

## When to use this

> [!NOTE]
> Under an agent session, a pure line-shift (the span still describes the same content, just at a new range) is healed automatically — the touch hook re-anchors it the moment the edit lands, and no manual step or extra commit is needed. This guide is for what that hook can't do on its own: content itself changed in a way that needs a human or agent judgment call about whether the coupling still holds, or you're re-anchoring outside a hooked session (CI, a human-authored commit, `drift --fix` run by hand).

Use this when you've deliberately changed the code a span anchors — moved a function, changed its size, or otherwise made its old line range wrong — and `git span drift` now reports it as drifted. This guide covers the manual re-anchor step. If you haven't triaged the drift yet, start with [Reconcile drifted spans](/docs/guides/reconcile-drifted-spans).

## Find the new range

Open the file and locate the code the span should now point at. Before writing an `#Lstart-Lend` address, check the file's current line count:

```bash
wc -l path/to/file.ts
```

Anchor line numbers are 1-indexed and inclusive, and the end line can't exceed the file's line count — `git span add` rejects it:

```
error: invalid anchor: end=200 exceeds file line count (7)
```

> [!WARNING]
> A reversed range (`L6-L4`, end before start) doesn't get a clear "end before start" error. It fails generic anchor parsing instead, with a message that misleadingly claims the address is missing its `L` prefix. If you see that error on an address that looks right, check whether start and end are swapped.

If the whole file is the right unit to anchor (not a specific range), use the bare path with no `#L` suffix — that's a whole-file anchor.

## Same range, new content

If the code changed in place — the anchor's line range is still right, only the content differs — re-add the same address. `git span add` recognizes the existing anchor and re-hashes it against the current content:

```bash
git span add checkout-flow web/checkout.tsx#L4-L6
```

```txt
Added 0 anchors and resolved 1 in place to span `checkout-flow`.

- resolved in-place: `checkout-flow` `web/checkout.tsx#L4-L6` (hash changed)
```

"Added 0 anchors" is not a failure — the in-place resolution is the point. The next `git span drift` run reports the anchor clean. This is also how you confirm an intentional content change that `drift` flagged: re-adding the anchor declares "yes, the new content is what the span should track."

Before re-adding, check the span's *other* anchors still agree with the new content — the re-hash records the current bytes as the baseline with no semantic check. Conform a side only when confirmed authority or a satisfied gate decides it; otherwise surface the ambiguity instead of re-anchoring over disagreement. Without a confirmed contrary authority, a doc that lags deliberate committed code follows the code.

## Moved range: swap the anchor identity atomically

If the code moved to a different range or path, swap the drifted address in one command:

```bash
git span replace checkout-flow web/checkout.tsx#L4-L6 web/checkout.tsx#L6-L8
```

`replace` retires exactly the old identity and installs the new one in a single locked transaction — either both happen or nothing changes, so there is never an intermediate state with both anchors or neither. The new address passes the same validation as `add` (including the line-count check above). The swap is refused — leaving the declaration untouched — if the span doesn't track the old identity exactly, if the new identity is already tracked, or if the two addresses are the same identity (an in-place hash refresh is `add`'s job).

`add` rejects an anchor that provably supersedes an existing same-path anchor — a whole-file anchor versus a range, in either direction — naming the conflict and printing the exact `git span remove` command to run. Disjoint same-file ranges remain valid. Remove the exact old address first, then retry the add.

Check the result:

```bash
git span show checkout-flow
```

Confirm the span now lists the new range and no longer lists the old one.

## Inherit the why only while it remains true

Re-anchoring doesn't touch the `why`. Inherit it only while the relationship and lifecycle state remain true; otherwise revise or retire it:

```bash
git span why checkout-flow "The browser initiates the charge request that the server validates; the server contract is authoritative for accepted fields."
```

When a satisfied gate authorizes a behavior change, complete it, revise or
retire the why, retire every superseded anchor, and run the required code
checks before requiring scoped zero drift.

## Commit

```bash
git add .span && git commit
```

Before committing, run scoped `git span drift checkout-flow` and require zero drift. `add` refreshes only the exact path/range specified; it does not retire a different superseded anchor — `replace` is the one-command identity swap — and an anchor that provably supersedes an existing same-path anchor (whole-file vs range, either direction) is rejected with the exact `git span remove` command printed.

## See also

* [Reconcile drifted spans](/docs/guides/reconcile-drifted-spans) for the full triage workflow, including when `drift --fix` can do this automatically.
* [Concepts](/docs/concepts) for the definitions of span, anchor, and why.
