Skip to main content

One question about your own code, at the end of a Claude Code session.

Project description

grask

CI Python 3.12+ License: MIT Status: alpha

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_HOME relocates the database and log.
  • The corpus tools take --exclude to skip projects by name substring, and --root to 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

grask-0.1.0rc1.tar.gz (48.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

grask-0.1.0rc1-py3-none-any.whl (57.5 kB view details)

Uploaded Python 3

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

Hashes for grask-0.1.0rc1.tar.gz
Algorithm Hash digest
SHA256 33ef8427adee96a8b1b7448a1ffa37a2c2d19fc6ea1831e5f5c9d5d45d5f666f
MD5 95000ee4372d47bf264408d865fcc733
BLAKE2b-256 21b88c18a90c362f691671ed1c89bf99128f22e3ee0bf3f5b666999e0310569e

See more details on using hashes here.

Provenance

The following attestation bundles were made for grask-0.1.0rc1.tar.gz:

Publisher: release.yml on imkp1/grask

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for grask-0.1.0rc1-py3-none-any.whl
Algorithm Hash digest
SHA256 5cabe3ffd0f4466f8996cdc858c57c8bfef675b86996ff606f3de5a50d635355
MD5 ebc47d682fbf9581d2444e5388441abb
BLAKE2b-256 50c13d6f2587e8458d659ec363b3683ad507da4f27bd6b8d4e2b28567806b0d1

See more details on using hashes here.

Provenance

The following attestation bundles were made for grask-0.1.0rc1-py3-none-any.whl:

Publisher: release.yml on imkp1/grask

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page