gitmotive docs
git blame tells you who. git motive tells you why. This page is generated from the CLI's own help text and the event schema, so it matches the version it ships with.
Install
gitmotive needs Python 3.10 or newer and git. It is pure standard library, with no runtime dependencies, no network calls and no telemetry.
With pipx
pipx install gitmotive
pipx puts gitmotive and git-motive on your PATH. Because git runs git-motive for git motive, both spellings work.
With the install script
curl -fsSL https://gitmotive.tech/install.sh | sh
The script checks for Python 3.10 or newer. If pipx is present it runs pipx install gitmotive. Otherwise it creates a venv at ~/.gitmotive/venv and writes gitmotive and git-motive wrappers to ~/.local/bin. Any arguments after sh -s -- are passed to gitmotive install:
curl -fsSL https://gitmotive.tech/install.sh | sh -s -- --agent claude --scope user
From a clone
git clone https://github.com/rvasqz86/gitmotive
cd gitmotive
sh install.sh
install.sh installs this checkout into ~/.gitmotive/venv and writes the same two wrappers to ~/.local/bin. Arguments are passed to gitmotive install, for example sh install.sh --agent claude,cursor --scope user.
Check the install:
gitmotive --version
Quickstart
- Hook up your agent, from the root of a git repository:
gitmotive install --agent claude
This writes the capture hooks to .claude/settings.json, adds a post-commit hook that runs stamp, and adds .gitmotive/ to .git/info/exclude, so nothing gitmotive records is committed.
- Use the agent as usual. Every prompt you submit and every file edit it makes is appended to
.gitmotive/events.jsonl. - Commit:
git commit -am "add retry with backoff"
The post-commit hook stamps the new edits with the commit they landed in.
- Ask any line why it exists:
git motive path/to/file.py:42
gitmotive why path/to/file.py:42 is the same command. It prints the line, the commit from git blame, the edit that wrote it, the prompt behind that edit, the agent's reasoning and the other files changed for the same prompt. When nothing was recorded for a line it says (none recorded) and exits 1. It never guesses.
- List what was recorded:
gitmotive log
gitmotive log path/to/file.py
Each prompt is shown with its edit count, the commits its edits landed in and the files it touched.
Agent setup
gitmotive install writes hook entries into each agent's own config file. Pick agents with --agent, as a comma-separated list or all. The default is claude.
- Claude Code (
--agent claude): supported. - Cursor (
--agent cursor): supported. - GitHub Copilot CLI (
--agent copilot): experimental. - oh-my-pi (
--agent omp): experimental, and needs the omp-hooks plugin (omp install omp-hooks).
Experimental agents record prompts and edits only, because their hook payloads are not fully documented yet. gitmotive install prints a note for each one.
Repo scope and user scope
--scope repo is the default. Hook configs are written inside the repository, events go to <repo>/.gitmotive/events.jsonl, and a post-commit hook stamps edits with their commit.
--scope user writes nothing into any repository. Hook configs go under your home directory, the hook commands are prefixed with GITMOTIVE_STORE=user, and events go to ~/.gitmotive/repos/<hash>/events.jsonl, one directory per repository. Set GITMOTIVE_STORE=user in your shell too, so why and log read the same store. There is no git hook in user scope: why and log attach edits to commits when they read them.
Where the hooks go
- Claude Code:
.claude/settings.json(user scope:~/.claude/settings.json), onUserPromptSubmitand onPostToolUseforEdit|Write|MultiEdit. - Cursor:
.cursor/hooks.json(user scope:~/.cursor/hooks.json), onbeforeSubmitPromptandafterFileEdit. - GitHub Copilot CLI:
.github/hooks/gitmotive.json(user scope:~/.copilot/hooks/gitmotive.json), onUserPromptSubmitandPostToolUse. - oh-my-pi:
.omp/settings.json(user scope:~/.omp/agent/settings.json), onUserPromptSubmitand everyPostToolUse.
Hook commands
Each hook runs a command of this form, where <run> is how the hook starts gitmotive:
claude : <run> capture
cursor : <run> capture --agent cursor --event <beforeSubmitPrompt|afterFileEdit>
copilot: <run> capture --agent claude --source copilot
omp : <run> capture --agent claude --source omp
When gitmotive is installed into a site-packages directory (pipx, a venv, the install script), <run> is <python> -I -m gitmotive, with the absolute path of that Python. -I keeps a hook whose working directory is inside a gitmotive checkout from importing the checkout instead of the installed copy. When gitmotive runs from a source tree, <run> is PYTHONPATH=<pkg> <python> -m gitmotive. The post-commit stamp line uses the same <run>. In user scope every command starts with GITMOTIVE_STORE=user.
Copilot and oh-my-pi send Claude-shaped payloads, so they are parsed with --agent claude. --source records which product actually produced the event.
After a Python or pipx change
The hook commands contain an absolute Python path. After you upgrade Python, reinstall with pipx, or move the venv, run gitmotive install again with the same --agent and --scope. It upserts: each existing gitmotive hook command and the post-commit stamp line are replaced in place, so stale paths heal and there is still exactly one gitmotive entry per hook event. Your other hooks are left alone.
CLI reference
Generated from the CLI itself. This is what gitmotive help prints:
git blame tells you who. git motive tells you why.
git motive <file>:<line> why does this line exist (also: gitmotive why
<file>:<line>)
gitmotive log [file] prompts recorded, with files they touched
gitmotive stamp tag uncommitted edit events with HEAD (run
from post-commit)
gitmotive install [--agent claude|cursor|copilot|omp|all] [--scope repo|user]
copilot (experimental), omp (experimental).
repo: hooks in this repo. user: hooks under
~/, zero repo footprint
gitmotive capture [--agent A] [--event E] [--source S]
(internal) hook entrypoint, reads payload on
stdin. --source S labels the product (e.g.
copilot, omp) over the payload agent
gitmotive help [cmd] | -h | --help
show this help, or one command's entry.
gitmotive --version | -V prints the version
Experimental agents: copilot, omp (experimental). They record prompts and edits only; their hook payloads are not fully documented.
git motive / gitmotive why
git motive <file>:<line>
why does this line exist (also: gitmotive why <file>:<line>)
gitmotive log
gitmotive log [file]
prompts recorded, with files they touched
gitmotive stamp
gitmotive stamp
tag uncommitted edit events with HEAD (run from post-commit)
gitmotive install
gitmotive install [--agent claude|cursor|copilot|omp|all] [--scope repo|user]
copilot (experimental), omp (experimental). repo: hooks in this repo. user: hooks under ~/, zero repo footprint
gitmotive capture
gitmotive capture [--agent A] [--event E] [--source S]
(internal) hook entrypoint, reads payload on stdin. --source S labels the product (e.g. copilot, omp) over the payload agent
gitmotive help
gitmotive help [cmd] | -h | --help
show this help, or one command's entry. gitmotive --version | -V prints the version
gitmotive --version
gitmotive --version | -V
print the version and exit
Data format
Everything gitmotive records is one file, events.jsonl, with one JSON object per line. Each object is an event: a prompt you submitted, or an edit the agent made to a file.
Store locations
The store is found in this order:
GITMOTIVE_STORE=useris set:~/.gitmotive/repos/<hash>/events.jsonl, where<hash>is derived from the repository's absolute path.<repo>/.gitmotive/events.jsonlexists: use it.~/.gitmotive/repos/<hash>/events.jsonlexists: use it.- Otherwise
<repo>/.gitmotive/events.jsonl.
GITMOTIVE_HOME moves ~/.gitmotive somewhere else. gitmotive install adds .gitmotive/ to .git/info/exclude, so a repo-scope store (<repo>/.gitmotive/) is never committed. The entry applies to repo scope only; the user-scope store lives outside the repository.
Append-only
Hooks only ever append a line, so concurrent hook runs never corrupt the file. The one exception is stamping: gitmotive stamp, why and log fill in the commit field of edits that have landed in a commit. They write a complete new file and atomically replace the old one. No event is ever removed.
The agent field
agent is the product that produced the event (claude, cursor, copilot or omp), not the payload shape. Copilot CLI and oh-my-pi send Claude-shaped payloads but are recorded as copilot and omp. Events captured by older versions have no agent or model.
Event fields
Unknown keys are ignored on read and missing optional keys take their defaults; lines without kind, session or ts are skipped. Older and newer files stay readable.
| Field | Type | Meaning |
|---|---|---|
kind | str | event type: prompt or edit |
session | str | agent session or conversation id |
ts | float | unix timestamp when the event was captured |
id | str | unique event id |
prompt | Optional[str] | user prompt text (prompt events) |
file | Optional[str] | edited file, relative to the repo root (edit events) |
tool | Optional[str] | tool that performed the edit |
old | Optional[str] | replaced text, if any |
new | Optional[str] | new text, or full content for whole-file writes |
reasoning | Optional[str] | assistant text immediately preceding the edit |
prompt_id | Optional[str] | id of the prompt event this edit belongs to |
commit | Optional[str] | commit the edit landed in, stamped after commit |
agent | Optional[str] | product that produced the event (claude, cursor, copilot, omp) |
model | Optional[str] | model that produced the event, when known |
raw | Optional[dict] | original hook payload, for forward compatibility |