One question about your own code, at the end of a Claude Code session.
Project description
grask
One question about your own code, when you finish coding.
You can't tell the difference between understanding something and having watched it happen. Shipping code with an agent used to force the issue — you couldn't ship what you didn't understand, because it wouldn't run. That forcing function is gone.
grask watches your Claude Code sessions end, decides whether anything in one was worth
asking about, and — usually not — stays silent. When something was, it mints a single
multiple-choice question about the mechanism you shipped, and asks it the next time you
run /grask.
Most sessions produce nothing. That is the system working, not a failure to find something.
Install
Requires Python 3.12+ and the Claude Code CLI already
installed and authenticated. grask has no runtime dependencies and needs no second API
key — it shells out to the claude binary you already use.
git clone https://github.com/imkp1/grask && cd grask
uv tool install .
This puts two commands on your PATH: grask (ask a question) and grask-hook (the
capture trigger). pipx install . works the same way. Not on PyPI yet — install from a
checkout.
Installing as a tool rather than uv sync is deliberate: both surfaces below invoke
grask by name, so it has to resolve without a path. Use uv sync only for working on
grask itself.
Wire up the hook
Capture runs when a session ends. Add to your Claude Code settings.json:
{
"hooks": {
"SessionEnd": [
{
"hooks": [
{ "type": "command", "command": "grask-hook" }
]
}
]
}
}
If you installed with uv sync instead, this has to be the absolute path to
.venv/bin/grask-hook — and it breaks the moment you move the checkout.
The hook reads the payload, spawns a detached worker, and returns immediately — it never
blocks the end of your session, and it never speaks. Failures go to
~/.claude/grask/grask.log, never to your terminal.
Install the skill (optional)
grask skill --install # writes ~/.claude/skills/grask/SKILL.md
This gets you /grask inside Claude Code, using the native question UI. --dir targets a
different skills directory — .claude/skills next to a repo, for a project-level install.
Without the skill, grask on the command line does the same job in the terminal.
grask skill with no flags prints the file instead of writing it, if you want to read it
before it lands.
Use
grask # ask the next pending question, or say there's nothing
You get one question, three or four options, and one line of orientation about which
session it came from. Pick a letter. Grading is mechanical — the answer key was minted
when the question was, so there is no model call, no latency, and no judge to argue with.
enter skips. /wrong rejects the premise if the question misreads what happened.
Questions expire after 7 days. A probe about work you did last week is a quiz.
Privacy — read this before installing
grask reads your Claude Code transcripts, and it is not scoped to one project:
- It reads every transcript under
~/.claude/projects/, across all your repositories. - It sends transcript content to a model — your prompts, the agent's replies, and the
before/after text of edits — by shelling out to
claude -p. That call runs under your existing Claude Code authentication and is subject to whatever data policy your account already has. No data goes anywhere else, and grask adds no telemetry. - It stores what it extracts locally, in a SQLite database at
~/.claude/grask/, including verbatim quotes of things you typed.
Controls:
GRASK_HOMErelocates the database and log.- The corpus tools take
--excludeto skip projects by name substring, and--rootto point at a different transcript directory.
If any of your repositories are covered by an agreement that prohibits sending source to a model, do not install the hook.
Nothing derived from a real transcript belongs in this repository. The tools write
under GRASK_HOME by default for exactly that reason, and .gitignore is a second line of
defence. This project has made that mistake once already.
How it works
Four stages, cheapest first. Each one filters, so only what survives pays for the next.
| Stage | Module | Cost | Job |
|---|---|---|---|
| 0 — extract | transcript.py |
free | Pull the developer's own turns out of a session log. Tool results, file snapshots, and injected skill text are not the developer thinking. Sessions with no human turns stop here. |
| 1 — triage | triage.py |
one call | List every moment worth asking about, each anchored to a verbatim quote and the turn it came from. Sees turns and file paths, never file contents. Most sessions yield nothing. |
| — select | select.py |
free | Rank the moments and pick one. Deliberately code, not prompt: a model asked to both find and choose picks arbitrarily, and the topic changed run to run on an unchanged session. |
| 2 — seed | seed.py |
one call | State, as a falsifiable claim, what the developer may have accepted without understanding. Stored, so a better stage-3 prompt can re-ask the whole corpus later. |
| 3 — probe | probe.py |
one call | Write one multiple-choice question about the mechanism, with the answer key and an explanation. |
Two rules are enforced in code rather than prompted for, because instruction is not a control:
- The evidence rule. A triaged moment whose quote does not appear in the turn it names is demoted to silence. Same for a seed quote that appears nowhere the developer typed.
- The one-question rule. A stem with two questions in it cannot have one correct option, so it is rejected and regenerated, up to three attempts.
The question must also teach something portable. A question whose answer is "because this file says so" is answerable only by whoever sat through the session and is worth nothing once they close the file.
Model selection
grask names no model. It calls claude -p with no --model flag, so every stage runs on
whatever you currently have selected, and there is no second credential to manage. Tools
are disallowed and slash commands disabled — grask sends one self-contained prompt and
wants one JSON object back, so letting it wander the repo would just cost money.
Cost
grask's own prompts are small. The real cost is the Claude Code context inherited by
shelling out to claude -p at all, which scales with your configuration rather than with
grask — --disable-slash-commands exists in llm.py for precisely this reason and cuts a
call substantially on a large setup.
Stages 2 and 3 only run on sessions triage kept, which is a minority. The corpus tools
print an estimated range and refuse to spend without --go.
Development
uv sync # working on grask itself, rather than installing it
uv run pytest # 221 tests, no network, no model calls, sub-second
uv run ruff check .
uv run mypy
The suite never calls a model. Every stage is injected as a plain callable, so the whole
pipeline is drivable by test doubles — that seam is why the tests are free and fast.
Tests marked calibration do call the real model and cost money; they are deselected by
default and run deliberately with pytest -m calibration.
Corpus tools
Diagnostics for working on grask itself, not part of normal use. All of them write under
GRASK_HOME, never the working directory.
uv run python -m grask.survey # what's in the local transcript corpus (free)
uv run python -m grask.triage_run # run stage 1 over the corpus; costs money
uv run python -m grask.capture_run # run the full pipeline over past sessions; --go to spend
capture_run skips grask's own project by default. The sessions that end from now on are
overwhelmingly grask's own, and waiting for the queue to fill measures grask on grask.
Status
Alpha, and honest about it. The capture pipeline, storage, both delivery surfaces, and mechanical grading all work end to end.
Not built: resurfacing a missed question, and cross-session dedup — two sessions can currently produce near-identical probes.
Design notes and the reasoning behind each decision are in
docs/design/grask-design.md; IDEA.md covers
what this is and why it might not work. docs/README.md says which of
those documents are current and which are records.
Contributing
Issues and pull requests are welcome — see CONTRIBUTING.md for the three rules that are easy to break by accident, and CODE_OF_CONDUCT.md.
A question grask asked that was bad — wrong premise, wrong key, or testing nothing portable — is the single most useful thing you can report. There is an issue template for exactly that.
For anything where grask leaked, over-collected, or wrote outside GRASK_HOME, see
SECURITY.md and report it privately rather than in a public issue.
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file grask-0.1.0rc1.tar.gz.
File metadata
- Download URL: grask-0.1.0rc1.tar.gz
- Upload date:
- Size: 48.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
33ef8427adee96a8b1b7448a1ffa37a2c2d19fc6ea1831e5f5c9d5d45d5f666f
|
|
| MD5 |
95000ee4372d47bf264408d865fcc733
|
|
| BLAKE2b-256 |
21b88c18a90c362f691671ed1c89bf99128f22e3ee0bf3f5b666999e0310569e
|
Provenance
The following attestation bundles were made for grask-0.1.0rc1.tar.gz:
Publisher:
release.yml on imkp1/grask
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
grask-0.1.0rc1.tar.gz -
Subject digest:
33ef8427adee96a8b1b7448a1ffa37a2c2d19fc6ea1831e5f5c9d5d45d5f666f - Sigstore transparency entry: 2219476370
- Sigstore integration time:
-
Permalink:
imkp1/grask@dfeee7cc68d64c419a2f0a768b92febfad195db1 -
Branch / Tag:
refs/tags/v0.1.0-rc1 - Owner: https://github.com/imkp1
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@dfeee7cc68d64c419a2f0a768b92febfad195db1 -
Trigger Event:
push
-
Statement type:
File details
Details for the file grask-0.1.0rc1-py3-none-any.whl.
File metadata
- Download URL: grask-0.1.0rc1-py3-none-any.whl
- Upload date:
- Size: 57.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5cabe3ffd0f4466f8996cdc858c57c8bfef675b86996ff606f3de5a50d635355
|
|
| MD5 |
ebc47d682fbf9581d2444e5388441abb
|
|
| BLAKE2b-256 |
50c13d6f2587e8458d659ec363b3683ad507da4f27bd6b8d4e2b28567806b0d1
|
Provenance
The following attestation bundles were made for grask-0.1.0rc1-py3-none-any.whl:
Publisher:
release.yml on imkp1/grask
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
grask-0.1.0rc1-py3-none-any.whl -
Subject digest:
5cabe3ffd0f4466f8996cdc858c57c8bfef675b86996ff606f3de5a50d635355 - Sigstore transparency entry: 2219476415
- Sigstore integration time:
-
Permalink:
imkp1/grask@dfeee7cc68d64c419a2f0a768b92febfad195db1 -
Branch / Tag:
refs/tags/v0.1.0-rc1 - Owner: https://github.com/imkp1
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@dfeee7cc68d64c419a2f0a768b92febfad195db1 -
Trigger Event:
push
-
Statement type: