git-span

CI & git integration

CI gate: the enforcement backstop

Agent integration covers the in-session touch hook and commit advisor, but those only run inside a hooked agent session — a human-authored commit, a session with hooks disabled, or (on Codex) a permissionDecision: 'deny' that doesn't actually block can all still land span debt. git span drift's default exit code is the backstop, checked before merge:

git span drift

git span drift exits 1 when it finds drift and 0 on a clean scan, so it drops into any CI pipeline as a plain step:

- uses: actions/checkout@v4
  with:
    fetch-depth: 0 # spans compare against real git history
- run: npm install -g git-span
- run: git span drift

For any other CI system:

git span drift || { echo "drifted spans found — run 'git span drift' locally to see them"; exit 1; }

For a report-only job that should never fail the build, add --no-exit-code:

git span drift --no-exit-code

--no-exit-code only suppresses the exit code for ordinary drift. An interior-anchor integrity violation still exits 1 even with --no-exit-code set.

For machine consumption, use --format json or --format porcelain:

git span drift --format json
git span drift --format porcelain

--format json always emits a document — a clean scan carries "clean": true (schema_version 3) rather than empty stdout, so a parser can rely on an envelope on every run and read clean for the verdict; informational RESOLVED_PENDING_COMMIT findings may still be listed, so clean does not imply findings: []. --format porcelain still prints nothing on a clean scan (no header row); empty stdout plus exit 0 is the clean signal there. The human format (the default) prints a summary line on a clean scan.

See Command reference for the full set of drift flags, and Concepts for what counts as drift.

Wiring into a validate script

Add git span drift as its own step alongside lint/typecheck/test, run after them — span debt is a documentation-coupling concern, not a correctness one, so it should never mask an earlier, more actionable failure:

echo "Checking spans..."
git span drift || { echo "git span drift found drifted anchors — see above"; exit 1; }

Local pre-commit hook

For a workflow without agent hooks at all (a human editor, or a harness git-span doesn't yet plug into), add a plain git span drift call to .git/hooks/pre-commit:

#!/bin/sh
git span drift
chmod +x .git/hooks/pre-commit

With no drift, the hook exits 0 and the commit proceeds. With drift, git span drift exits 1, which fails the hook and blocks the commit — fail-closed by default. A developer can still bypass it with git commit --no-verify, which is standard git behavior for any hook, not something git-span adds or removes.

Merge driver

Registering git span merge-driver lets .span/ files merge structurally instead of falling back to a textual diff3 merge. Add the attribute:

.span/** merge=span

And the driver in .git/config:

[merge "span"]
	name = git-span structural span merge driver
	driver = git span merge-driver %O %A %B %L

The driver resolves whatever is structurally derivable from the three sides without trusting the worktree. Genuine same-anchor divergence — both sides changed the same anchor's range or hash, or the why text diverged with no common merge base — it can't resolve on its own; it writes minimal conflict markers into the span file and exits non-zero so that a later git span drift --fix can finish the resolution. Anything still unresolved surfaces in git span drift with a — conflict suffix in human output (CONFLICT in the machine formats).

See the reconcile drifted spans guide for what to do with a span left in that state.