Stop pushing broken code to CI: Wire Chunk sidecars into agent hooks
Content Marketing Manager
AI agents can write code faster than any developer. But for most teams, the feedback loop hasn’t kept pace. The agent generates code, pushes it to CI, and minutes later a full pipeline run catches a simple linting error or a failing unit test. By then the agent has moved on. Getting back to a working state means rebuilding context from scratch and burning tokens just to fix something that should never have shipped in the first place.
Chunk sidecars short-circuit that loop by catching failures before they ever reach CI. A sidecar is a lightweight Linux microVM that mirrors your CI environment, running your test suite in seconds so simple errors get fixed inside the agent’s own workflow. That way, your pipeline stays reserved for the integration and deployment work it’s actually built for.
In Run your first microbuild in 5 minutes, you learned how to set up a sidecar and trigger the validation process manually with chunk validate. In this tutorial, you’ll see how Chunk’s agent hooks can trigger that same validation automatically, at the right moments in the agent’s workflow, without any manual prompting.
Automating validation steps with agent hooks
Agent hooks are scripts an agent runs at fixed checkpoints in its loop, such as before a tool call or when a turn ends. You can use them to wire up custom automated commands like linters, formatters, or test suites so they fire on their own whenever those events occur.
Chunk sidecars use hooks to attach a fast, local test gate on every git commit and an environment-wide validation right before the agent tries to wrap up its work. This enables the agent to catch and fix small failures inside its own inner loop.
In our microbuild tutorial, you installed the Chunk CLI, cloned a sample project, and ran chunk auth set circleci and chunk init to get your workspace set up. That process not only wrote a .chunk/config.json to handle your core sidecar environment, it also generated .claude/settings.json with two hooks already wired in:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "cmd=$(jq -r '.tool_input.command // empty'); case \"$cmd\" in \"git commit\"*|*\" git commit\"*) cd ${CLAUDE_PROJECT_DIR:-.} && { CI=true npm test; } || exit 2 ;; esac",
"timeout": 300
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "chunk validate",
"timeout": 330
}
]
}
]
}
}
Let’s look at what happens when each hook fires.
What the two hooks do
Chunk’s agent hooks establish clear checkpoints to govern the agent’s inner loop.
The commit gate (PreToolUse). This hook triggers right before the agent executes a tool. It matches every Bash call, checks whether the command is a git commit, and if so runs the test suite locally first. If the tests fail, the hook returns an `exit 2` status, which blocks the commit and hands the output back to the agent. Feedback is immediate and the agent can continue working with its existing context.
The finish gate (Stop). When the agent is ready to wrap up its turn, the Stop hook steps in. It executes the chunk validate command, which syncs the agent’s uncommitted working tree to a remote sidecar and runs your entire test suite inside a clean Linux microVM that perfectly mirrors your production CI environment. If the validation tests fail, the hook exits with code 2, keeping the agent’s turn wide open so it gets another shot to fix its mistakes. This auto-retry loop is safely capped by the stopHook MaxAttempts setting in .chunk/config.json (which defaults to 3 attempts).
Together they leave two checkpoints in place: a fast local check when the agent commits, and a CI-grade validation on the sidecar before it’s allowed to finish.
Watch them work
Let’s see these gates in action. Imagine we give our agent a basic task: add a quick text helper function called truncate(text, maxLength) to manage long card titles.
The repo’s test specification, src/utils/truncate.test.js, explicitly dictates that the ... ellipsis counts toward the maxLength, meaning the final string can never overshoot that limit.
The agent’s first pass ignores the ellipsis when counting characters:
export function truncate(text, maxLength) {
if (text.length <= maxLength) return text;
return text.slice(0, maxLength) + '...';
}
Calling truncate(‘hello world’, 8) on this version returns ‘hello wo…’, which is 11 characters long. The spec says the output should never exceed 8.
What happens next depends on how the agent tries to deliver the code. Because your hooks split the work between a fast local check and an isolated microVM check, they catch errors at different points in the loop depending on the agent’s path.
Path A: When the agent commits
If the agent writes this flawed code and immediately attempts to stage and commit its changes, the local check steps in.
$ git add src/utils/truncate.js && git commit -m "Add truncate helper"
The commit gate fires our PreToolUse hook before the commit runs. It pulls the command out of the tool call, sees the git commit, and runs the tests locally first:
Exit 2 on a PreToolUse hook blocks the tool call outright. Claude Code surfaces it as a “hook error,” but the effect is that the commit never runs and the output goes back to the agent. It reads the failure, fixes the slice to slice(0, maxLength - 3), and commits again. This time the tests pass, and the broken version never reaches your git history.
Path B: When the agent tries to end its turn
Local checks catch logic errors instantly, but they have two limitations: they miss OS-specific bugs, and they won’t fire at all if the agent tries to hand the turn back to you without running a commit command. This is where the stop hook and Chunk sidecars come in to complete the inner loop.
If the agent writes that same flawed code but attempts to wrap up its turn without committing it, the Stop hook fires and runs chunk validate, which spins up a sidecar, syncs the uncommitted file, and runs the full test suite on the remote environment:
[chunk validate]: No active sidecar found, creating a new sandbox...
✓ Created sandbox (72fca210)
Synchronising local → remote: ./workspace/circleci-demo-javascript-react-app … ✓ Synced
running on sidecar: test → CI=true npm test
FAIL src/utils/truncate.test.js
● truncate › counts the ellipsis toward the limit when truncating
Expected: "hello..."
Received: "hello wo..."
● truncate › never returns a string longer than the limit
Expected: "he..."
Received: "hello..."
Tests: 2 failed, 3 passed, 5 total
Exit 2 again. The agent reads the sidecar’s output, applies the same slice(0, maxLength - 3) fix, and chunk validate runs again: 5 of 5 green. Only then can the turn end.
In practice, the two gates often work in sequence. The agent writes code, tries to commit, and the local check catches a syntax error or a failing test. It fixes that and commits successfully. Then when the turn ends, the sidecar catches something the local environment missed: a dependency that behaves differently on Linux, or a test that only fails in a clean install without cached node modules. The agent gets that feedback too and fixes it before the turn closes.
Your CI pipeline will still run its own integration tests, security scans, and whatever else you’ve configured. The difference is it’s not wasting those cycles on code that would have failed a basic unit test.
Using Chunk sidecar hooks with other agents
Every agent has its own config file and events: Claude Code uses .claude/settings.json, Cursor uses .cursor/hooks.json, Codex uses .codex/hooks.json, each with its own event names.
But the good news is that chunk validate is just a plain old CLI command. While chunk init hooks it into Claude Code automatically out of the box, you can easily copy-paste this workflow into any other agent. Just point that specific agent’s pre-commit or stop hook to chunk validate. The sidecar works the same regardless of which agent triggers it.
Try it on a real project
The feedback loop between your agent and your CI pipeline doesn’t have to be the bottleneck. With two hooks and a sidecar, basic failures get caught before they leave your machine, and your pipeline only sees code that’s ready for the harder checks. Setting it up takes one chunk init command, and microbuilds are completely free on every CircleCI plan.
Grab a free account, wire up your hooks, and stop pushing broken code to CI.