Build Watchers That Keep AI Happy
Create universal build watchers that feed real-time context to AI agents. Faster feedback loops, fewer broken builds, happier AI.
Claude Code can write code, but it cannot see your build output unless you tell it what happened. Every time you run a build, get an error, switch to Claude, and paste the error message, you are doing manual work that a build watcher can automate. Build watchers are small scripts that monitor your development tools and feed results directly into Claude Code's context.
The concept is simple: watch for changes, run the relevant tool, and surface the results where AI can see them. The implementation is straightforward once you understand the patterns.
Key Takeaways
- Build watchers close the feedback loop between your dev tools and Claude Code, eliminating copy-paste of error messages
- A universal watcher pattern works across frameworks -- the same structure handles Webpack, Vite, esbuild, and any other build tool
- Piping structured output (JSON) instead of raw terminal output gives Claude better context for diagnosing issues
- Combining build watchers with file watchers creates a continuous integration loop on your local machine
- The best watchers are selective -- they only surface failures and warnings, not the noise of successful builds
The Problem Build Watchers Solve
Here is the workflow without a build watcher:
- You ask Claude to modify a component
- Claude makes the change
- You switch to your dev server terminal and see a build error
- You copy the error message
- You paste it into Claude Code
- Claude fixes the error
- You check the dev server again
- Repeat until it works
Steps 3 through 5 are wasted time. They break your focus, require context switching, and introduce a delay between the error occurring and Claude knowing about it. With a build watcher, the flow becomes:
- You ask Claude to modify a component
- Claude makes the change
- The build watcher detects the error and surfaces it
- Claude sees the error and fixes it automatically
The difference is not just speed. It is cognitive overhead. Every context switch costs you mental energy. Eliminating them lets you stay in the creative zone of directing Claude rather than being a human clipboard.
The Universal Watcher Pattern
Every build watcher follows the same structure regardless of the specific tool it monitors.
interface WatcherConfig {
command: string // The build/lint/test command to run
watchPaths: string[] // Directories to monitor for changes
outputFormat: 'json' | 'text' // How to parse output
filterSuccess: boolean // Only report failures?
debounceMs: number // Wait for changes to settle
}
function createWatcher(config: WatcherConfig) {
const watcher = watch(config.watchPaths, {
debounce: config.debounceMs,
})
watcher.on('change', async (changedFiles) => {
const result = await runBuildCommand(config.command)
if (config.filterSuccess && result.exitCode === 0) {
return // Skip successful builds
}
const output = config.outputFormat === 'json'
? parseJsonOutput(result.stdout)
: result.stderr || result.stdout
surfaceToAgent(output, changedFiles)
})
}
The surfaceToAgent function is where the magic happens. It formats the build output in a way that Claude Code can parse and act on without additional prompting.
Building a TypeScript Watcher
TypeScript projects are the most common use case. The TypeScript compiler produces structured error output that maps cleanly to actionable information for Claude.
# Run TypeScript check from the command line
npx tsc --noEmit --pretty false
The parser extracts structured data from the compiler output:
interface TSError {
file: string
line: number
column: number
code: string
message: string
}
function parseTSErrors(output: string): TSError[] {
const errorRegex = /(.+)\((\d+),(\d+)\): error (TS\d+): (.+)/g
const errors: TSError[] = []
let match
while ((match = errorRegex.exec(output)) !== null) {
errors.push({
file: match[1],
line: parseInt(match[2]),
column: parseInt(match[3]),
code: match[4],
message: match[5],
})
}
return errors
}
function formatForAgent(errors: TSError[]): string {
if (errors.length === 0) return ''
return [
`## TypeScript Errors (${errors.length})`,
'',
...errors.map(e =>
`- **${e.file}:${e.line}** [${e.code}]: ${e.message}`
),
].join('\n')
}
The key design decision here is the output format. Raw TypeScript compiler output is verbose and includes information Claude does not need (like the full path to node_modules type definitions). The parser extracts only the actionable information: which file, which line, which error.
Building a Test Watcher
Test watchers work differently because you often want to see both failures and successes. A test that was failing and now passes is important context.
interface TestResult {
suite: string
test: string
status: 'pass' | 'fail' | 'skip'
duration: number
error?: string
}
function formatTestResults(results: TestResult[]): string {
const failed = results.filter(r => r.status === 'fail')
const passed = results.filter(r => r.status === 'pass')
if (failed.length === 0) {
return `All ${passed.length} tests passing.`
}
return [
`## Test Failures (${failed.length}/${results.length})`,
'',
...failed.map(f => [
`### ${f.suite} > ${f.test}`,
'```',
f.error,
'```',
'',
].join('\n')),
].join('\n')
}
This format gives Claude exactly what it needs: which tests failed, in which suite, and what the error message was. No noise, no decoration, just actionable data. For more on testing with Claude Code, see our testing skills guide.
Building a Lint Watcher
Lint watchers are useful for catching style violations and potential bugs before they accumulate. ESLint with the --format json flag produces structured output that is easy to parse.
# Run ESLint with JSON output
npx eslint src/ --format json --quiet
The --quiet flag is important. It suppresses warnings and only reports errors. Without it, you flood Claude's context with style warnings that are not actionable in the current task.
function parseLintOutput(jsonOutput: string): LintIssue[] {
const results = JSON.parse(jsonOutput)
return results
.filter((file: any) => file.errorCount > 0)
.flatMap((file: any) =>
file.messages
.filter((msg: any) => msg.severity === 2) // errors only
.map((msg: any) => ({
file: file.filePath,
line: msg.line,
rule: msg.ruleId,
message: msg.message,
fixable: msg.fix !== undefined,
}))
)
}
Combining Watchers Into a Skill
Individual watchers are useful, but combining them into a Claude Code skill makes them accessible with a single command. The skill watches for changes and surfaces all relevant feedback in one stream.
# Build Watcher Skill
## Trigger
Use when the user starts a development session or asks to
monitor builds, tests, or lint results.
## Behavior
1. Start file watchers for the project type (detect from package.json)
2. On file change, run: TypeScript check, lint, affected tests
3. Surface only errors and failures
4. Format output as structured markdown
5. Continue watching until the user stops the session
## Output Format
When errors are detected, present them as:
- File path and line number
- Error type and code
- Brief description
- Suggested fix if obvious
The skill itself does not contain the watcher code. It instructs Claude on how to set up and interpret watcher output within the current project. Claude then uses its built-in tools (Bash, file reading) to implement the monitoring.
Advanced Patterns
Differential Reporting
Instead of reporting all errors every time, report only changes since the last check. If a file had 3 errors and now has 2, report that one error was fixed and list the remaining two. This reduces noise and helps Claude track progress.
Priority Ranking
Not all errors are equal. A runtime error that crashes the app is more urgent than a missing return type. Rank errors by severity and present the most critical ones first.
const PRIORITY_ORDER = {
'runtime-error': 1,
'type-error': 2,
'import-error': 3,
'lint-error': 4,
'style-warning': 5,
}
Cross-File Dependency Tracking
When Claude modifies a file, the watcher should check not just that file but also files that depend on it. A change to a shared utility might break ten consumers. The watcher should trace the dependency graph and run checks on affected files.
Performance Considerations
Build watchers run in the background and consume resources. Keep them lightweight.
Debounce aggressively. When Claude modifies multiple files in quick succession (which it often does), you do not want to trigger a build after each file. Wait for changes to settle -- 500ms is usually enough.
Run incremental checks when possible. TypeScript's --incremental flag and ESLint's cache dramatically reduce check times on large projects.
Limit watched directories. Watch src/ and lib/, not the entire project. Exclude node_modules/, dist/, and other generated directories.
FAQ
Do build watchers work with all frameworks?
The pattern is universal, but the implementation varies by framework. The examples here are for TypeScript/Node.js projects. The same concepts apply to Python (mypy, pytest), Go (go build, go test), Rust (cargo check), and any other language with CLI-based build tools.
Will build watchers slow down my development?
Not if implemented correctly. Debouncing prevents excessive runs, and incremental checks keep individual runs fast. On a typical Next.js project, a debounced TypeScript check completes in under 2 seconds.
Can I use build watchers with multiple Claude Code sessions?
Yes. Each session can have its own watcher, or you can use a single watcher that writes to a shared output file that multiple sessions read. The shared approach is simpler. See our guide on managing multiple sessions for details.
How do build watchers interact with hot module replacement?
They complement HMR. HMR handles runtime updates in the browser. Build watchers handle compile-time errors that HMR cannot catch. Together, they provide complete coverage of the development feedback loop.
Should I run watchers in CI too?
Watchers are for local development. In CI, run the same checks as full commands (not in watch mode) and fail the build on errors. The watcher pattern is about fast feedback during development, not about replacing CI.
Explore production-ready AI skills at aiskill.market/browse or submit your own skill to the marketplace.
Sources
- Chokidar File Watcher - Efficient cross-platform file watching library
- TypeScript Compiler API - TypeScript CLI options reference
- ESLint Formatters - ESLint output format documentation