Verify that a human actually understands an AI-assisted diff before it merges.
Not git commit --signoff (the Developer Certificate of Origin trailer), and
not an approval button: a sign-off button records that someone clicked, a GSA
attestation records that someone understood — and it lives in your git
history, not in a hosted dashboard.
/git-signoff flips the usual review direction: instead of you interrogating the
AI's code, the AI interviews you — then records the outcome as a
machine-parsable, tamper-evident Git Signoff Attestation inside your
repository.
You (or your AI assistant) changed some code. Before that change merges — or
before its output goes into a paper, a report, or a decision — run /git-signoff.
The agent reads the full diff, then asks you a short series of pointed
questions across four fixed axes:
- Mechanics & intent — what changed, and why this design?
- Trade-offs & edge cases — what approximations were made, and are they intentional?
- Boundary conditions & failure loudness — where does it break, and does it break loudly or silently?
- Ownership — do you explicitly accept responsibility for the results and risks?
Vague answers don't pass. If you hand-wave, the agent pauses, explains the
relevant mechanics, and re-probes with a concrete scenario. When you do pass,
it writes an empty signed commit plus a mirrored git note
(refs/notes/signoff) recording who understood what, when, and at exactly
which state of the code — a record that survives squash merges and branch
deletion. The goal is preventing cognitive surrender: rubber-stamping AI
output nobody actually understands.
Software engineers — the default interview profile emphasizes algorithmic complexity, data-structure invariants, and API contract changes.
Scientists, physicists, and mathematicians — research code fails
differently: it rarely crashes, it produces plausible-but-invalid results.
An ML parameterization that quietly leaks energy, a unit slip between hPa and
Pa, a trend that vanishes at higher grid resolution — none of these throw an
exception. The shipped domain-science profile emphasizes unit and
dimensional validity, surrogate-vs-ground-truth boundaries, numerical
stability, statistical validity (leakage, multiple comparisons), uncertainty
quantification, and reproducibility (seeds, environments, data provenance).
And at the research frontier there is no oracle to check against —
/git-signoff deliberately does not claim to verify that the science is right;
it verifies that you know the assumptions, the validity regimes, and how
you'd notice drift outside them. That is exactly the part a human must own.
Want to understand the change first? Copy the whole skills/git-signoff/
folder into your repository's .claude/skills/git-signoff/ (Claude Code) or
.agents/skills/git-signoff/ (other supported harnesses). Install on the base
before starting a feature when possible, so the skill installation does not
dominate your learning diff. No workflow, badge, ruleset, or PR is required.
/git-signoff --explaingives a guided walkthrough of what changed and why./git-signoff --practicerehearses the interview, with hints, answers, and an advisory scorecard using the same profile and tier rules.
Both accept a branch name; without one they inspect tracked working files,
including dirty work on main. Untracked files are reported as excluded.
Neither creates an attestation or changes your staged work. Named targets
are fetched. Explanation can precede a real interview in the same conversation
on explicit request; practice requires a fresh conversation before real
signoff. Starting practice writes a small local session guard in Git metadata,
shared across linked worktrees; no readable transcript is needed when a
session identity is available. See the learning-mode design.
Want the merge gate and record? Use the initializer:
Inside your repository root, run the zero-touch initializer (Python 3.10+ stdlib only — zero dependencies):
# Standard software engineering profile:
curl -fsSL https://raw.githubusercontent.com/jerrylin96/git-signoff/init-v10/init.py -o /tmp/signoff-init.py && python3 /tmp/signoff-init.py
# Scientific & research computing profile (math, physics, bio, climate, ML):
curl -fsSL https://raw.githubusercontent.com/jerrylin96/git-signoff/init-v10/init.py -o /tmp/signoff-init.py && python3 /tmp/signoff-init.py --profile domain-scienceThe script automatically:
- Detects your repo, branch, and stack (suggests science or general software profile).
- Vendors the
/git-signoffskill into.claude/skills/git-signoff/and/or.agents/skills/git-signoff(cross-client convention, auto-detected from repository markers, or explicitly chosen via--skill-target {auto,claude,agents,both}) — committed with your repo, it loads for every collaborator, across Claude Code, Antigravity, Codex, Cursor, and other agent harnesses alike, with nothing account-scoped to install. - Scaffolds
.github/workflows/git-signoff.ymland.git-signoff/profile.md. - Injects the attested by humans badge into your
README.md. - Automates GitHub Ruleset protection (via
ghCLI or a 1-click settings link). - Creates feature branch
git-signoff/initwith your scaffolded setup ready for you to run/git-signoffand merge.
AI: [Axis 1 — Mechanics] What changed in this diff, and why did you choose this design?
You: Added an automatic fallback to /dev/tty when stdin is connected to a curl pipe.
AI: [Axis 2 — Edge Cases] What happens if this runs in a headless CI environment without a TTY?
You: It catches OSError cleanly and gracefully degrades to non-interactive default choices.
AI: [Axis 3 — Ownership] Do you accept accountability for these failure modes and CI configurations?
You: Confirmed, my email is dev@example.com.
AI: ✅ Attestation commit [SIGNOFF a1b2c3d] created! Your badge is green.
- Install — run the 60-second initializer above, or pick the one row for your harness below.
- Open the PR — review the diff as usual (when the branch's commits are well-structured, reading them one at a time shows what changed when and why far better than one squashed diff); the
verify-signoffcheck runs red until the branch ends in a valid attestation. Attest after the diff is final: the attestation must be the last commit on the branch, so pushing anything after it turns the check red again (just re-run/git-signoff). - Run — from the branch you want to merge, type
/git-signoff; or, from wherever you sit (typically the integration branch),/git-signoff <branch>— the attestation is pushed to that branch and your checkout is not touched. With no branch named on the integration branch, the agent lists the branches awaiting review for you to pick. (Adaptive default auto-selects intensity from the diff;--deepfor skeptical rigor,--quickfor low-risk diffs subject to safety clamps.) - Answer, confirm, merge — respond in your own words, acknowledge the named trade-offs and risks, confirm your email. The attestation commit and note are created and pushed with your branch; when
verify-signoffturns green, merge as usual.
Git signoff attestations bind the interview transcript's SHA-256 digest (Signoff-Transcript-Digest) into immutable git history and notes. Transcripts remain locally on the reviewer's laptop for privacy and security. Anyone — leads, reviewers, compliance auditors — can verify or audit the transcript at any time using the verifier CLI.
Auditor (Lead / Compliance) Reviewer (Employee)
│ │
│ 1. Request transcript snapshot │
│ (quotes commit or PR) │
├─────────────────────────────────────────>│
│ │ 2. Export snapshot:
│ │ verify_signoff.py --audit HEAD
│ │ --export transcript.jsonl
│ 3. Send transcript.jsonl │
│<─────────────────────────────────────────┤
│ │
│ 4. Verify against git trailers: │
│ GIT_SIGNOFF_TRANSCRIPT_FILE=transcript.jsonl
│ python3 verify_signoff.py --audit HEAD
│ │
│ Output: ✅ VALID MATCH │
The auditor identifies the attestation on the commit or PR (e.g. [SIGNOFF 979cb45]) and asks the reviewer to export the session transcript.
On the machine where the signoff interview occurred, the reviewer runs the verifier CLI with --audit and --export:
python3 .claude/skills/git-signoff/verify_signoff.py --audit HEAD --export /tmp/transcript.jsonlThe verifier resolves the local transcript for the harness (claude-code, antigravity-cli, codex-cli, etc.), checks that the first Signoff-Transcript-Digest trailer, and writes the snapshot to the specified path:
✅ VALID MATCH: Transcript SHA-256 matches sha256:1675b6...
Harness: claude-code
Conversation ID: 979cb45-session
Bytes verified: 8432
Exported snapshot to: /tmp/transcript.jsonl
The reviewer sends /tmp/transcript.jsonl to the auditor.
The auditor points GIT_SIGNOFF_TRANSCRIPT_FILE at the received file and audits the target commit:
GIT_SIGNOFF_TRANSCRIPT_FILE=/tmp/transcript.jsonl python3 .claude/skills/git-signoff/verify_signoff.py --audit HEADThe verifier recomputes the SHA-256 digest and confirms it matches the git attestation byte-for-byte.
One channel, everywhere: the skill is a self-contained folder — the
interview prompt (SKILL.md), the deterministic producer (attest.py) and
the verifier (verify_signoff.py), both standard-library Python — that lives
in the repository under review. Committed once, /git-signoff works for
every collaborator — no plugins, no marketplaces, no downloads, nothing
account-scoped, nothing to pip-install.
| Where you work | One-time action |
|---|---|
| Any repository (Zero-touch) | curl -fsSL https://raw.githubusercontent.com/jerrylin96/git-signoff/init-v10/init.py -o /tmp/signoff-init.py && python3 /tmp/signoff-init.py (use --skill-target {auto,claude,agents,both} to control destinations) |
| Any repository (manual) | Copy this repo's skills/git-signoff/ folder to <your-repo>/.claude/skills/git-signoff/ (Claude Code) or <your-repo>/.agents/skills/git-signoff/ (Antigravity, Codex, Cursor, etc.) and commit before running the initializer; an untracked skill destination now aborts as an unrelated working-tree change. Update by re-copying (or re-running the initializer) on new releases. |
| Other harnesses (Antigravity, Codex, Cursor, …) | Same folder, cross-client convention: copy skills/git-signoff/ into .agents/skills/git-signoff (or .claude/skills/git-signoff) and set the transcript adapter env vars — full matrix in HARNESSES.md. |
Note
Initializer Flags & Policy A:
--skill-target {auto,claude,agents,both}: Selects target client destinations (defaults to auto-detect based on repo markers).--integration-branch NAME: The branch pull requests merge into (default: detected, confirmed interactively). Written once to.git-signoff/config.jsonand read by the workflow, the ruleset, andattest.py, so adev-based team never has three components disagreeing about the base.--allow-dirty: Permits unrelated unstaged/untracked work to remain in place. It still refuses any pre-staged change, any uncommitted or ignored state under paths the initializer manages, and all Policy A violations (symbolic links, parent-path collisions, unrelated non-empty directories, and destination-level.gitignorerules). This boundary prevents user work from being swept into the scaffold commit or overwritten during vendoring/rollback.
The four axes above are fixed for everyone. What you customize is the interview profile — a single, clearly delimited text block that weights the questions toward your domain's failure modes. It is the sole customization point of the skill; profiles can add domain emphases but can never remove axes or lower pass criteria, so a customized interview is never a weaker one.
Generic software engineering questions (
- Numerical Stability & Conditioning: Catastrophic cancellation in floating-point diffs, ill-conditioned matrices, underflow/overflow in log-space computations, gradient explosion/vanishing.
- Physical Invariants & Conservation: Leakage of mass, energy, momentum, or probability across time steps; violation of positivity constraints (e.g. negative tracer concentrations); CFL condition violations when time-stepping.
-
Statistical Validity & Data Leakage: Contamination between spatial/temporal training and test splits, lookahead bias in climate/financial series, uncorrected multiple hypothesis testing (
$p$ -hacking), circular feature engineering. - Surrogate Boundaries & Validity Regimes: Out-of-distribution neural network surrogates used outside their training domain, unquantified epistemic uncertainty, lack of physics-informed fallback.
- Provenance & Reproducibility: Floating-point non-determinism across GPU architectures, unseeded RNG streams, dataset version drift.
See skills/git-signoff/profiles/README.md for the complete authoring guide and ready-to-use templates for:
- Fluid Dynamics & Climate Simulation (CFL stability, discrete conservation laws, grid interpolation)
- Bioinformatics & Computational Genomics (multiple testing correction, reference genome versions, batch effects)
- AI for Science & Neural Operators (PDE surrogates, spectral bias, physical boundary condition compliance)
- Numerical Linear Algebra & Optimization (condition numbers, matrix decompositions, convergence tolerances)
- In the repo you want reviewed, scaffold or create
.git-signoff/profile.md:python3 /tmp/signoff-init.py --profile domain-science
- Or paste in a shipped profile block —
domain-sciencefor research code,software-generalfor classic engineering — or adapt a discipline template fromskills/git-signoff/profiles/README.md. - Done. Every
/git-signoffrun on that repository now uses your profile — for every collaborator, on every install channel, surviving skill updates.
Write your own profile for your lab or team: copy a shipped profile as a
template, set your own Profile-ID: (lowercase, hyphens), and rewrite the
emphasis bullets to name your failure modes — conservation properties of a
new parameterization, grid-resolution sensitivity of a reported trend,
CFL-limited timestep choices, leakage between reanalysis training and
evaluation periods, what the ensemble spread does and doesn't capture —
whatever "wrong but plausible" looks like in your field.
Science is probed by default: even with no customization at all, any diff that touches scientific computation — numpy/scipy/jax-style imports, notebooks, RNG seeding, unit-bearing constants, netCDF/GRIB/zarr datasets — automatically triggers the science questions on top of the active profile.
Every attestation records which question set actually ran: the profile ID
plus, for repo-supplied profiles, a content digest
(Signoff-Agent: ... interview=standard/<your-profile-id>/sha256:<digest>),
so downstream readers can always see which questions the human was held to —
and a diluted profile is distinguishable from a shipped one.
Other knobs: GIT_SIGNOFF_PROFILE_FILE=<path> overrides everything for one
machine; editing the block inside the vendored SKILL.md also works, but
re-vendoring on update overwrites such edits — prefer the repo-local
.git-signoff/profile.md. Full details:
HARNESSES.md.
[SIGNOFF <short-sha>]: human comprehension and risk attestation
Signoff-Spec-Version: 1.0
Signoff-Status: VERIFIED_BY_HUMAN
Signoff-Base-SHA: ...
Signoff-Reviewed-Commit-SHA: ...
Signoff-Reviewed-Tree-SHA: ...
Signoff-Harness-ID: claude-code
Signoff-Transcript-Digest: sha256:...
Signoff-Tradeoff: ...
Signoff-Risk: ...
Signoff-Verified-By: you@example.com
Signoff-Agent: harness=claude-code/2.x model=... reasoning=... interview=standard/software-general
Verification survives squash merges via the reviewed tree SHA and the notes mirror — lookup order in gsa-core.md §5.
Show it: the badge & CI gate. A two-minute GitHub Actions check turns attestations into a visible, enforceable claim — PRs fail until the branch ends in a valid attestation, and your README carries an attested by humans badge (the one at the top of this file):
# .github/workflows/git-signoff.yml
name: attested by humans
on:
pull_request:
push:
branches: [ main ]
permissions:
contents: read
pull-requests: read # scan-refs: auto asks which merged pull request produced the pushed merge
jobs:
verify-signoff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # full history — attestations live in it
- uses: jerrylin96/git-signoff/verify@verify-v1.7Supports standard merge strategies: 2-parent PR merges (verifies clean merge tree & attested PR head in head mode), fast-forward merges (head mode), squash merges (history mode; in head mode when base is unchanged), and rebase merges (history mode; in head mode, re-run /git-signoff after rebase). Enforce strictly with preconfigured ruleset.json. Full setup & badge markdown: verify/.
The protocol is harness-, model-, and vendor-neutral, and nothing is
installed by name: the initializer is a curl-run script, and the skill folder
carries the interview prompt plus two standard-library Python files. The
agent conducts the interview; attest.py does every mechanical step
deterministically — it resolves the SHAs, snapshots the transcript once,
requires an approval marker naming the reviewed commit to be present in that
snapshot (so a stale or wrong session file is refused instead of hashed),
derives the status from the bytes, writes the commit and the notes, and runs
the verifier on its own output before reporting success. What this does
not close is a malicious agent or human with push rights writing a false
attestation; that needs an identity the agent does not hold and stays
deferred (see docs/roadmap.md).
- Protocol spec:
skills/git-signoff/specs/gsa-core.md - Producer and verifier reference (flags, exit codes, env vars):
docs/reference.md - Reproducible walkthrough:
docs/walkthrough.md - Project roadmap:
docs/roadmap.md - Per-harness install & portability guide:
skills/git-signoff/HARNESSES.md - Skill entry point:
skills/git-signoff/SKILL.md
Distribution is deliberately boring: one folder committed to the repository under review, loaded by the harness from disk. Earlier account-scoped channels (a Claude Code plugin marketplace and a release-zip skill upload) were retired in v0.4.0 — the vendored folder replaced them on every surface; docs/roadmap.md#phase-4-amendment-2026-08-30 records the history.
v0.5.0 ships the researcher-facing feature set described above —
repo-local profiles, the default-on science guard, and profile provenance
digests — plus the deterministic producer attest.py with approval-marker
binding, on a single distribution channel (the vendored skill folder),
verified end-to-end by scripted mechanics checks plus live interview runs:
this repository signs off its own branches, and the resulting attestations
are in its history (git log --grep='SIGNOFF').
Phase 5 (tracked in docs/roadmap.md)
adds the production surface: a project website,
the attested-by-humans badge + CI verifier,
automated refs/notes/signoff recovery, an openly licensed
protocol specification with
conformance vectors for third-party implementations, and a reviewed
transcript-escrow spec whose
privacy baseline is user-owned storage with client-side encryption.
Cloud escrow implementation remains next.
pip install pytest ruff # the only development dependencies; nothing from this repo is installed
ruff check . && pytestTests live in scripts/tests/ (the producer attest.py, the verifier, skill
contracts, conformance vectors, notes recovery, the site) and tests/ (the
repository initializer). See CONTRIBUTING.md.
Releases are archived on Zenodo under the concept DOI
10.5281/zenodo.22683016, which represents all versions and always resolves
to the newest. It is the DOI recorded in CITATION.cff and the one to cite:
a release cannot know its own version DOI, since Zenodo mints that only after
the release is published. Version DOIs are listed on the Zenodo record
(v0.5.0 is 10.5281/zenodo.22683017) for citing one exact snapshot. CITATION.cff in the repository root carries the citation
metadata (GitHub's "Cite this repository" button reads it), and
docs/reference.md documents the protocol version each
release implements.
Lin, J. (2026). git-signoff: Git Signoff Attestation (GSA) (v0.5.0) [Computer software]. Zenodo. https://doi.org/10.5281/zenodo.22683016
All software in this repository — init.py, the vendored skill folder
(attest.py, verify_signoff.py, SKILL.md, profiles), the composite
action under verify/, the conformance vectors, tests, and site — is
MIT licensed. The three protocol documents under
skills/git-signoff/specs/ are licensed under the
Apache License 2.0, which adds an
explicit patent grant for anyone implementing the protocol. Both licenses
are OSI-approved, and both texts ship inside the vendored skill folder so
every adopter's copy carries its notices.