Zero dependencies lighting fast universal lint hook for your Ralph Wiggum agent loop.
See also format hook: ralph-hook-fmt
Runs the same Rust lint hook in both Claude Code and Codex.
- Claude Code: collects files touched by
Write/Editand lints them onStop - Codex: snapshots the workspace on
UserPromptSubmit, diffs it onStop, and lints files changed during that turn
If lint errors are found, the agent is prompted to fix them before wrapping up.
- JavaScript/TypeScript:
oxlint>biome>eslint>npm run lint(in order of preference) - Rust:
clippy - Python:
ruff>mypy>pylint>flake8(in order of preference) - Java: Maven (
pmd:check>spotbugs:check) or Gradle (pmdMain>spotbugsMain) - Go:
golangci-lint>staticcheck>go vet(in order of preference)
claude plugin marketplace add https://github.com/chenhunghan/ralph-hook-lint.git
claude plugin install ralph-hook-lintCodex local plugin publishing is still marketplace-file based. As of today, the docs describe marketplace-based installation and plugin caching, but do not document a codex plugin install CLI command.
The easiest path is the bootstrap script, which clones or updates the plugin under ~/.codex/plugins/ralph-hook-lint, adds a local marketplace entry, enables codex_hooks, and writes repo-local hooks:
curl -fsSL https://raw.githubusercontent.com/chenhunghan/ralph-hook-lint/main/scripts/install_codex.sh | bashTarget a different repo or turn on verbose hook diagnostics:
curl -fsSL https://raw.githubusercontent.com/chenhunghan/ralph-hook-lint/main/scripts/install_codex.sh | bash -s -- --repo /absolute/path/to/repo --debugIf you already cloned this repo locally, run the script directly instead:
bash scripts/install_codex.sh --repo /absolute/path/to/repoAfter the script finishes:
- Restart Codex if this is your first plugin or hook install.
- In the Codex app, open the Plugin Directory and enable/install
ralph-hook-lintfrom your local marketplace if you want it visible there. - Open the target repo in Codex. The repo-local hooks are already configured.
If you prefer to wire everything up by hand, the equivalent flow is:
- Clone this repo somewhere stable.
- Add a local Codex marketplace entry that points at that clone.
- Enable or install the plugin from that marketplace in the Codex app.
- Enable hooks:
codex features enable codex_hooksThis is equivalent to setting the following in ~/.codex/config.toml:
[features]
codex_hooks = true- In the target repo, create
<repo>/.codex/hooks.json:
{
"hooks": {
"SessionStart": [
{
"matcher": "startup|resume|clear",
"hooks": [
{
"type": "command",
"command": "/path/to/ralph-hook-lint/scripts/setup.sh ralph-hook-lint",
"statusMessage": "Updating ralph-hook-lint"
}
]
}
],
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "/path/to/ralph-hook-lint/bin/ralph-hook-lint --snapshot-turn"
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "/path/to/ralph-hook-lint/bin/ralph-hook-lint --lint-turn",
"timeout": 120
}
]
}
]
}
}Replace /path/to/ralph-hook-lint with the absolute path to your local clone.
Example personal marketplace entry:
{
"name": "local-plugins",
"interface": {
"displayName": "Local Plugins"
},
"plugins": [
{
"name": "ralph-hook-lint",
"source": {
"source": "local",
"path": "./code/ralph-hook-lint"
},
"policy": {
"installation": "AVAILABLE",
"authentication": "ON_INSTALL"
},
"category": "Productivity"
}
]
}claude plugin marketplace update ralph-hook-lint
claude plugin update ralph-hook-lint@ralph-hook-lintOnce Codex hooks are configured, SessionStart runs scripts/setup.sh, which keeps the downloaded binary under the installed plugin directory up to date automatically.
If you move the plugin clone or reinstall it from a different marketplace path, update <repo>/.codex/hooks.json so the commands point at the right plugin location again.
By default, Claude uses a two-phase deferred linting approach:
- Collect phase (
PostToolUse): After everyWrite/Edit, file paths are collected without running linters. - Lint phase (
Stop): When the agent finishes, all collected files are linted at once in strict mode.
This lets the agent work freely during editing and catches all lint errors before the turn ends.
Current Codex pre/post tool hooks only expose Bash, so this plugin uses a turn-scoped flow instead:
- Update phase (
SessionStart): refresh the downloadedralph-hook-lintbinary if a newer release exists - Snapshot phase (
UserPromptSubmit): record the current fingerprint of supported source files under the repo - Lint phase (
Stop): diff the snapshot against the current workspace and lint only files changed in that turn
If Codex already continued once from a failing Stop hook, ralph-hook-lint will not trigger a second automatic continuation, which avoids endless stop-hook loops.
Disabled by default. The --lenient flag suppresses unused variable/import rules, which is useful when running lint on every Edit event instead of deferring to Stop. Intermediate edit states often have unused variables/imports that will be resolved in later edits.
To run lint on every edit with lenient mode, change hooks.json to:
- Open
~/.claude/plugins/ralph-hook-lint/hooks/hooks.json - Replace the
PostToolUsecollect hook with a direct lint:"PostToolUse": [ { "matcher": "Write|Edit", "hooks": [ { "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/bin/ralph-hook-lint --lenient" } ] } ]
This gives more immediate feedback but may block parallel editing.
For Codex, you can get the same behavior by editing <repo>/.codex/hooks.json and appending --lenient to the --lint-turn command.
By default, the hook only outputs systemMessage when blocking (lint errors found). To see all diagnostic messages, add --debug to the command in hooks.json:
"command": "${CLAUDE_PLUGIN_ROOT}/bin/ralph-hook-lint --lint-collected --debug"For Codex, edit <repo>/.codex/hooks.json and append --debug to both the --snapshot-turn and --lint-turn commands.
