
Forcing Claude Code to Reliably Pass Lint with Lefthook
Text by Hirotaka Miyagi
Published
Hello, I'm Miyagi, the product manager for Liam.
When having Claude Code perform implementation tasks, it sometimes considers a task complete even if linting fails. After struggling with this behavior, I found a setup that patiently ensures linting issues are resolved before completing a task. I'll share that method today.
Liam is an open-source DB design tool, and you can read the code for the setup described here directly. Please take a look.
GitHub - liam-hq/liam: Automatically generates beautiful and easy-to-read ER diagrams from your database.
Claude Code Tends to Deem Files It Didn't Change as "Unrelated"
When instructing Claude Code to implement features or write tests, the AI often tries to run linting and tests only on the files it has changed.
This is a reasonable decision for getting quick feedback, but it caused a problem where it would complete a task even if the project-wide linting was failing, judging it as "unrelated to the current changes."
Upon investigation, most of the errors were caused by changes within that session, which meant I had to spend extra time giving additional instructions like, "This error was caused by your changes, so please fix it."
I expect a coding agent to autonomously reach the ideal state with minimal human intervention. Therefore, I needed to enforce a strict rule: "The entire project's lint must always pass, and a task is not considered complete until it does."
Simply giving instructions through prompts like in CLAUDE.md
wasn't enough to ensure stable behavior, so a more robust mechanism was needed.
The Solution: A Two-Pronged Approach
To solve these issues, we adopted a two-pronged approach:
- Implementing a pre-commit hook with Lefthook
- Disabling
--no-verify
in Claude Code's settings
1. Introducing Lefthook
First, we introduced Lefthook.
Add lefthook pre-commit hooks for automatic linting by devin-ai-integration[bot] · Pull Request #2311 · liam-hq/liam
This approach uses a git pre-commit hook to automatically run linting before a commit is made.
To be honest, I wasn't a big fan of pre-commit hooks. I found the extra commit time hard to accept, and there are times, like during debugging, when you want to commit without passing lint.
However, in this modern era where AI handles most of the commits, I decided it was time to change my thinking.
Why We Chose Lefthook
A great feature of Lefthook is that you can override settings in a lefthook-local.yml
file, separate from the main lefthook.yml
configuration file.
This allows developers to disable or customize hooks as needed without affecting the team's shared settings. Of course, you can still skip it with git commit --no-verify
as usual.
Other attractive points were that it's built with Go and is fast, and that the prepare
script automatically sets up the git hooks during pnpm install
, having minimal impact on developers' existing workflows.
We didn't choose it after a strict comparison with other tools; the background is that we had Devin research and implement it, and since it met our requirements, we just went with it.

translate: @Devin please set up a configuration in liam-hq/liam to always run pnpm lint before a git commit. Husky comes to mind, but please research and introduce a modern tool
Implementation Details
# lefthook.yml
pre-commit:
parallel: true
commands:
lint:
glob: "*.{js,ts,json,md,yml,yaml}"
run: pnpm lint
fail_text: "Lint failed. For AI Agents: Please fix all linting errors before committing."
fail_text
is the message displayed when linting fails. We consider this message part of the context provided to the agent, and it clearly instructs it to fix all errors.
Running Lint on the Entire Project
We adopted a policy of running lint on the entire project, not just the staged files. This decision was also geared more towards the AI agent than for humans.
2. Additional Control with Claude Code Settings
However, the Lefthook setup alone wasn't enough. Claude Code knew the workaround: git commit --no-verify
. Grrr...
So, we decided to explicitly forbid it in the project settings.
chore: add Claude Code settings with git commit permissions by MH4GF · Pull Request #2397 · liam-hq/liam
# .claude/settings.json
{
"permissions": {
"allow": [],
"deny": [
"Bash(git commit --no-verify:*)"
]
}
}
When a human is working, there are situations where you might want to temporarily skip the hook with this command. In such cases, the developer can handle it by committing manually from a different terminal session.
This setting completely prevents Claude Code from running git commit --no-verify
via its Bash tool.
Future Prospects and Challenges
Limitations with Claude Code Action
Actually, there's one limitation to this setup. Currently, this pre-commit hook doesn't work with Claude Code Action.
This is because the mcp__github_file_ops__commit_files
tool used internally by Claude Code Action creates commits by calling the GitHub API directly, bypassing local Git hooks.
However, this problem can potentially be solved by leveraging Claude Code's Prehook feature. It should be possible to impose a similar constraint by inserting a linting process before the MCP operation. I plan to investigate and report on this in a future article.
Summary
In this article, I've introduced a method for ensuring that the AI agent, Claude Code, reliably passes linting before completing a task.
- Technical Control with Lefthook: Forcing lint execution with a pre-commit hook.
- Behavioral Control with Claude Code Settings: Prohibiting the execution of commands that would bypass the hook.
This two-pronged approach allows us to provide flexibility for developers while enforcing strict rules for the AI agent.
The Liam development team is not only actively using AI development tools but also working on developing new features that incorporate AI. We look forward to sharing more about these initiatives in the future.
Text byHirotaka Miyagi
Hirotaka Miyagi is Tech Lead at ROUTE06. A full-stack engineer specializing in web development, he has built extensive experience through multiple startups before joining ROUTE06 to lead its technical initiatives.
Last edited on
There are currently no glossary entries for this blog post.