Quality checks that run automatically as Claude Code works.
chunk init generates .claude/settings.json with two hooks:
PreToolUse — matches the Bash tool. Each hook entry carries an if: "Bash(git commit*)" filter so only git commit commands trigger validation. If any command fails, the commit is blocked.
Stop — runs chunk validate after every session ends. Skips everything
when the working tree is clean. When there are changes, it runs all configured
commands so problems are surfaced before the agent stops working.
In hook mode only, a successful chunk validate run is cached. If the hook
fires again with nothing changed, the commands are skipped entirely:
chunk validate: skipped (no changes since last successful run)
Only successes are cached. A failing run is never stored, so the agent always gets a real re-run after a fix attempt.
The clean-tree skip above and the cache below read the same working-tree fingerprint, taken once per hook invocation.
The cache key covers:
- all of
.chunk/config.json— thecommandsblock, and theenvironmentblock that decides what those commands run against. A project that gitignores.chunk/still gets a re-run when either changes - the execution target — the configured sidecar snapshot image and the active sidecar's ID, so a result validated against one sidecar is never reused for another
- the HEAD commit SHA
- the contents of every file git reports as changed — tracked, staged, and untracked alike
Because contents are hashed rather than just the git status output, editing a
file that was already dirty invalidates the entry. Any edit that could change a
command's result produces a new key.
Two things deliberately stay out of the key:
- Gitignored files.
git statusdoes not report ignored paths, so nothing under.gitignoreparticipates in the digest —.env.local, generated code, vendored dependencies, local tooling config. Hashing them would mean walking trees likenode_moduleson every hook invocation. A command whose result depends on an ignored file can therefore report a hit after that file changes; touch a tracked file, or delete the cache directory, to force a re-run. - Environment variables passed with
--envor loaded from.env.local, for the same reason.
Caching is skipped, and commands always run, when:
- the run is not a hook invocation (a manual
chunk validatenever caches) --cmdsupplied an inline command- the working-tree state cannot be hashed reliably — not a git repo, a repo with no commits yet, or a changed path that cannot be read (an unreadable file, or a non-regular path such as a dirty submodule)
- the changed files total more than 64 MiB, the point past which hashing the tree costs more than re-running the commands
Those last two fail closed: without a trustworthy digest the key would depend on the config alone and would stay stable across code changes, so no cache is consulted at all. When the working tree cannot be hashed, the hook says so:
chunk validate: working tree state unavailable (hash sub: changed path is not a
regular file); running everything, caching nothing
That line is the only signal that a repo is getting no benefit from the cache, so
a repo that never prints skipped is not left unexplained.
Entries live outside the repo, under the per-project data directory:
$XDG_DATA_HOME/chunk/<sha256-of-project-path>/validate-cache/
Each entry is a small JSON file. Keys are content-addressed, so a superseded entry (an older commit, an earlier working-tree state) is never read again; each write sweeps entries older than 7 days, along with any partial file left behind by an interrupted run. Nothing needs cleaning by hand, though deleting the directory is always safe and forces a re-run.
The Stop hook uses CLAUDE_WORKING_DIR (the actual session working directory)
when available, falling back to CLAUDE_PROJECT_DIR. This means it correctly
targets the active worktree rather than the main repo root. No special
configuration is needed.
# 1. Install chunk (see README)
chunk --version
# 2. Initialize project (detects commands, writes settings.json)
chunk init
# 3. Edit .chunk/config.json to adjust commands if neededCommands are defined in the project config:
{
"commands": [
{"name": "format", "run": "task fmt", "timeout": 30},
{"name": "lint", "run": "task lint", "timeout": 60},
{"name": "test", "run": "task test", "timeout": 300}
],
"stopHookMaxAttempts": 3
}stopHookMaxAttempts controls how many times the Stop hook will re-signal the
agent when validation keeps failing for the same uncommitted changes. After that
many consecutive failures the hook exits 0 (ending the session) instead of
non-zero (which would ask Claude to try again). Defaults to 3 if unset.
Generated by chunk init:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{"type": "command", "if": "Bash(git commit*)", "command": "cd ${CLAUDE_PROJECT_DIR:-.} && task fmt", "timeout": 30},
{"type": "command", "if": "Bash(git commit*)", "command": "cd ${CLAUDE_PROJECT_DIR:-.} && chunk validate lint", "timeout": 60},
{"type": "command", "if": "Bash(git commit*)", "command": "cd ${CLAUDE_PROJECT_DIR:-.} && chunk validate test", "timeout": 300}
]
}
],
"Stop": [
{
"hooks": [
{"type": "command", "command": "chunk validate", "timeout": 420}
]
}
]
}
}The Bash(chunk:*) permission is also granted so chunk CLI commands run
without prompting.
| IDE | Status | Notes |
|---|---|---|
| Claude Code (CLI / terminal) | Fully supported | Canonical provider |
| Cursor | Supported | Reads .claude/settings.json directly |
| Codex | Supported | chunk init writes .codex/hooks.json when Codex is detected |
Temporarily disable the chunk validate Stop hook without modifying .claude/settings.json:
chunk hook disable # Creates .chunk/hooks-disabled sentinel file
chunk hook enable # Removes the sentinel file
chunk hook status # Shows "enabled" or "disabled"Stop-hook validation is also skipped when the CHUNK_HOOKS_DISABLED environment variable is set to any non-empty value. This does not suppress PreToolUse commit hooks generated by chunk init, because those commands run directly from .claude/settings.json.
The --project flag overrides the project root used to locate the sentinel file.
Use chunk validate to run checks manually (outside of hooks):
chunk validate # Run all configured commands
chunk validate test # Run a specific command
chunk validate --list # List configured commands
chunk validate --dry-run # Show commands without executing