Command line¶
grip and git grip are the same program.
grip --help lists the everyday commands first (init, quiz, whiteboard, status,
skip, resume, last), then setup and upkeep, then study and platform, with a few
examples. The commands git hooks, editors and coding agents call are left out of that list
but work as always: see For tools and agents.
grip quiz¶
Run the quiz now, without a hook.
grip quiz # staged changes
grip quiz --unpushed # commits not on any remote
grip quiz --branch # the branch since it left main: its pull request
grip quiz --range main..HEAD # explicit range
grip quiz --range main...HEAD # HEAD since it forked from main (merge base), like a PR
grip quiz --passing-score 90 --difficulty hard
grip quiz --provider fake # offline dry run
grip quiz --report out.json # also write the report here
grip quiz --fresh # new questions, even for a diff quizzed recently
grip quiz --force # quiz a rename, whitespace or skip_paths diff anyway
--staged, --unpushed, --branch and --range pick one scope; giving two of them is a
usage error (exit 2). The same goes for grip ask, grip check and grip whiteboard.
A diff with nothing to explain (only renames or mode changes, only whitespace, only
skip_paths, or fewer lines than
min_changed_lines) is not quizzed: grip says why and exits 0. --force quizzes it. It
does not override a [[paths]] rule with skip = true.
Trying again on the same diff reuses the questions from the last attempt (see
cache_hours), so only the grading calls the model. --fresh asks for
new ones. grip whiteboard reuses its board the same way. With
retry_questions = "new", a failed attempt forgets them, so the next
try gets new ones.
--branch diffs from the merge base with the default branch: what origin/HEAD points
at, else origin/main or origin/master, else a local main or master. It is the
"quiz my PR" command, whatever has or has not been pushed yet.
--range takes git's two spellings. BASE..HEAD compares the two tips, so whatever BASE
gained after HEAD forked from it shows up too, as if reverted. BASE...HEAD diffs from
their merge base, so only HEAD's side counts, as in a pull request. A missing HEAD
(main.., main...) means HEAD.
When the diff is empty, grip says what there is instead and what to run, and exits 0:
grip: nothing to quiz: nothing staged. 3 untracked files and 1 modified file: `git add`
them first; 2 unpushed commits: try `grip quiz --unpushed` or `--branch`.
Answer each question on one line and press Enter. To answer in paragraphs, paste them: a pasted answer keeps its line breaks and ends at the next empty line, or at the empty line the paste itself ends with. (On Windows each pasted line still answers the next question.)
A change where every file is binary (images, archives) has nothing to ask about: the quiz
and the hooks skip it with grip: only binary files changed, nothing to quiz.
Exit codes: 0 passed or skipped, 1 failed the quiz, 2 error, 130 stopped with
Ctrl-C (nothing is recorded).
grip quiz needs an interactive terminal. Without one (stdin redirected, a script, an IDE
task) it exits 2 rather than skipping, and points to grip ask / grip grade (JSON in,
JSON out) or grip whiteboard. The hooks still skip in that case unless require_tty is
set.
grip whiteboard¶
Draw the change as a whiteboard and defend it in the browser. The parts of the change, how
they connect and five questions pinned to them come from your diff. Answers are graded like
grip quiz, and a pass counts for the push hook. See the whiteboard.
grip whiteboard # staged changes
grip whiteboard --unpushed # commits not on any remote
grip whiteboard --branch # the whole branch since main
grip whiteboard --range main..HEAD # explicit range
grip whiteboard --range main...HEAD # HEAD since it forked from main (merge base)
grip whiteboard --interview # rubrics visible to the person asking; nothing recorded
grip whiteboard --export board.html # standalone page, no rubrics, nothing graded
grip whiteboard --no-open --port 8765 # print the address instead of opening a browser
It takes the same --provider, --model, --difficulty, --passing-score and --report
options as grip quiz. The page is served on 127.0.0.1 behind a random token and waits
while it is open in a browser, and up to twenty minutes after it is closed. Exit codes: 0 defended, skipped or exported, 1 below the pass
mark, 2 error (including Ctrl-C or the idle timeout, which record nothing).
grip init¶
Sets a repository up in one go: writes a commented .grip.toml, installs the pre-push hook
(chaining after an existing hook rather than failing) and runs grip agents for the
coding agents it detects.
In a terminal it asks four questions: which model writes and grades the questions, the
pass mark, the difficulty (easy, normal or hard), and whether to quiz before
pushes, commits or both. Press Enter to keep a default. A flag answers its question up
front, --yes takes every default without asking, and outside a terminal (scripts, CI)
nothing is asked.
The provider defaults to auto, and .grip.toml is meant to be committed, so each
teammate gets the coding agent they have installed (Claude Code, Codex or Gemini CLI),
else anthropic. grip init prints what auto resolves to on your machine. It writes a
concrete provider only when you pick one in the menu or pass --provider. It then says
what is still missing for that provider, such as ANTHROPIC_API_KEY.
grip init # ask, write, install
grip init --yes # every default, no questions
grip init --provider ollama --passing-score 80 --difficulty hard
grip init --stage pre-commit --stage pre-push
grip init --no-hook --no-agents # only .grip.toml
grip init --force # overwrite an existing .grip.toml
grip agents¶
Writes the file each installed coding agent reads so it runs the quiz before pushing:
AGENTS.md, CLAUDE.md and .claude/settings.json, GEMINI.md, .cursor/rules/grip.mdc,
.windsurf/rules/grip.md, .clinerules/grip.md, .github/copilot-instructions.md.
Detection looks for the agent's binary on PATH or its directory under your home.
grip agents --list # detected agents and what each would get
grip agents # write for the detected ones
grip agents --all # write for every known agent
grip agents --agent gemini --dry-run
grip agents --remove # strip the blocks, leave the rest of each file
See Coding agents.
grip install / grip uninstall¶
grip install # pre-push
grip install --stage pre-commit --stage pre-push
grip install --append # chain after an existing hook
grip install --force # replace an existing hook
grip uninstall # remove from every stage
Hooks go to .git/hooks/ or to core.hooksPath when it is set. Files grip wrote start
with a marker line so uninstall never touches a hook it does not own. The hook runs the
grip that installed it by absolute path, so it works from IDEs and GUI clients whose PATH
lacks grip, and falls back to PATH when that grip is gone. After moving or reinstalling
grip, run grip install again to update the path.
grip status¶
Shows which stages have grip installed (hook paths relative to the repository root, such
as .git/hooks/pre-push; grip missing when the grip a hook was installed with no
longer exists), whether a foreign hook is in the way, the last quiz (score, pass or fail,
how long ago), the average time and tokens per quiz over the last 20, whether hooks are
paused, a short configuration summary (provider, pass mark, difficulty, hook mode;
grip config has the rest), and for each of the staged
changes, the unpushed commits and the branch against the default branch, where it stands:
time and tokens (last 20) 9.8 s and 3,140 tokens per quiz, over 12 quizzes
staged changes nothing
unpushed commits nothing
branch vs origin/main not quizzed (4 files)
passed 98/100 at 17:52, remembered, failed 40/100 at 17:40 and not quizzed read as
they say. A branch whose commits are all on the remote can still be unquizzed:
grip quiz --branch covers it.
grip skip / grip resume¶
GRIP_SKIP=1 is awkward from an IDE's push button. grip skip skips the next hook run
once; grip skip --hours 2 pauses every hook run for two hours; grip resume cancels.
Only the hooks and the agent push gate look at it, a manual grip quiz never does. The end
of a pause is shown in local time with the time zone, such as paused until 2026-10-25
18:40 CET.
grip skip && git push # one push without the quiz
grip skip --hours 1 # a demo, a pairing session
grip resume
grip config¶
Prints every option with its effective value and where the value came from: default,
pyproject.toml, .grip.toml, an environment variable (env GRIP_TIMEOUT) or a flag
(flag --difficulty). Below the table it lists the configuration files it read, lowest
precedence first. See Configuration for the order.
grip config # what a hook run here would use
grip config --provider fake # the same, as `grip quiz --provider fake` would see it
It takes --provider, --model, --passing-score and --difficulty, the flags most often
passed to grip quiz.
grip last¶
Shows the last graded quiz in this repository: how long it took and how many tokens it
used (took 9.8 s · 3,140 tokens (questions 6.1 s, grading 3.7 s)), each question with its
focus, your answer, the points and the feedback, then the verdict. Rubrics are not shown.
--json prints the same payload as grip grade (which includes seconds and tokens)
plus summary, questions, answers, calls (each model request with its time and
token counts, an appeal's included), appeals and at; the /grip-explain skill builds a
commit message from it. Token counts are null for providers that do not report them,
such as the coding-agent CLIs. See
the FAQ to turn them into a
cost.
grip appeal¶
A grade can be wrong: the model misread the code, or the rubric missed a correct answer.
grip appeal has one question of the last graded quiz graded again, with your case for it.
The number is the question's number in grip last. The provider and model that graded the
quiz grade your original answer again, with the diff, the rubric, the first grade and your
justification; the justification goes to the model as data, like your answers.
- What only the justification says earns nothing: it can explain what your answer meant, or show that the rubric misread the code, but you have seen the feedback by now.
- An appeal never lowers a grade.
- Each question can be appealed once per quiz. A new quiz on the same diff can be appealed again.
- When the new score reaches the quiz's pass mark, the pass is remembered exactly as a passed quiz is, and the push hook lets the diff through. Appealing a quiz that already passed updates its score but does not restart the time its pass is remembered.
grip: appeal upheld. Q3 (edge case): 6 -> 18/20.
The answer names the bound; the first grade missed that max_attempts caps the loop.
Grip Score 58 -> 70/100 PASS (pass mark 70)
grip: passed on appeal; the commit or push can go through.
The appeal (question, old and new score, justification and feedback) is saved in
.git/grip/last-report.json and appended to the history, and grip last shows it under
the question. Exit codes: 0 the quiz now passes, 1 it still fails, 2 error (no graded
quiz, a question appealed already, an empty justification). Appeals need the diff the quiz
was graded on, which grip keeps in .git/grip/last-diff.json; a quiz graded before this
version cannot be appealed.
grip forget¶
Clears remembered passes so the next commit or push is quizzed again, and says how many it
cleared (no remembered passes when there were none).
grip study export / grip study status¶
Every graded quiz is appended to .git/grip/history.jsonl and the repository is noted in
~/.local/share/grip/repos.json ($XDG_DATA_HOME/grip, or GRIP_DATA_HOME). export
collects that history from every repository on the machine into one anonymised JSON file
you can upload to a study platform yourself; grip never uploads anything.
grip study status # how many repos and quizzes, and the exact field list
grip study export # last 45 days -> grip-export-<YYYYMMDD>.json
grip study export --since 0 --out ~/all-quizzes.json
The export carries, per quiz: an opaque id, an opaque per-repository hash, the time,
stage, provider, model, pass mark, score, pass/fail, and for each question its focus
label and points. It never contains the diff, file paths, repository names, question
text, rubrics, your answers, feedback or the verdict. grip study status prints both
lists so you can check before sharing.
grip platform¶
The Keep A Grip commands. grip platform login TOKEN [--url URL] verifies
and stores an API token from your dashboard (logout forgets it, whoami shows the
account). problems [--category SLUG] lists the catalogue. solve SLUG [--dir PATH] sets
up a repository with PROBLEM.md and the problem's pass mark, and remembers the base
commit. submit [--no-tests] [--dry-run] runs the problem's test command, quizzes you on
everything since that base commit (uncommitted work included) and reports the score;
--dry-run prints the payload instead of sending it.
grip platform login kag_…
grip platform solve cmd-vel-safety-filter
cd cmd-vel-safety-filter # solve it, commit as you go
grip platform submit --provider claude-code
The old top-level names (grip login, grip logout, grip whoami, grip problems,
grip solve, grip submit) still work, print a one-line note on stderr that they moved,
and will be removed in a later release.
For tools and agents¶
These commands are what git hooks, editors and coding agents call. grip --help does not
list them, to keep it short, but they are stable and grip <command> --help describes
each one.
grip hook STAGE [ARGS...]¶
The entry point installed hooks and pre-commit call. STAGE is pre-commit or
pre-push; extra arguments are whatever git passes to the hook. You rarely run this
yourself, but it is handy for debugging:
printf 'refs/heads/main %s refs/heads/main %s\n' "$(git rev-parse HEAD)" "$(git rev-parse origin/main)" \
| grip hook pre-push origin
Accepts the same --passing-score, --provider, --model, --difficulty and
--report flags as grip quiz.
grip ask / grip grade / grip check¶
The quiz in three non-interactive steps, for coding agents. ask prints the questions as
JSON (rubrics stay in .git/grip/pending.json), grade scores a JSON list of answers and
remembers a pass, check exits 0 when the diff already passed. Same --staged,
--unpushed, --branch and --range selectors as grip quiz. Raw line breaks inside an
answer's JSON string are accepted and kept. grade takes --provider, --model and
--report; the questions already exist, so it has no --fresh or --difficulty, and it
grades at the difficulty ask wrote them at. See Coding agents. (check is
also handy by hand, so grip --help lists it.)
grade always uses the configured pass mark: it takes no --passing-score. A pass only
counts for the hooks and check if its score reaches the current pass mark and, unless
fake is the configured provider, it was not graded by fake. See
Remembering passes.
ask uses the staged changes when no selector is given. check without a selector checks
what the installed hooks quiz: the unpushed commits for the pre-push hook (the default
grip install), the staged changes for a pre-commit hook, both when both are installed,
and the staged changes when no hook is installed. It says what it checked:
grip: ok (unpushed commits).
grip ask --unpushed
grip grade --answers - <<'EOF'
["answer 1", "answer 2", "answer 3", "answer 4", "answer 5"]
EOF
grip check --unpushed && git push
grip agent-hook claude-code [--gate push|commit|both]¶
Claude Code PreToolUse hook. Reads the hook payload from stdin and denies git push
(and git commit with --gate both) until the diff has passed a quiz. Installed by the
plugin. Like the git hooks, it lets the command through
when GRIP_SKIP=1 is set for it (GRIP_SKIP=1 git push), after grip skip, or in CI.
grip statusline¶
One short line for an editor or Claude Code status line: grip 92/100 pass, with
paused appended while grip skip is in effect. Outside a repository or before any quiz
it prints nothing and exits 0. It reads an optional JSON payload on stdin and uses its
cwd, which is what Claude Code sends:
grip statusline --json prints the score, pass mark, time and skip state for other tools.