跳至主要内容
五折优惠 全部方案,限时优惠。起价 $2.48/mo
14 min left
开发者工具与 DevOps

如何为 AI 生成的代码配置 Linter

S By Sherwin 14 min read
ESLint output showing AI-specific lint violations in a TypeScript file

AI agent 能生成可以通过编译的代码。

但这仍然不是你想要的标准。标准是:代码不会导入从未用到的东西,不会靠 any 强制转换来逃避类型系统,不会忽略错误返回值,也不会硬编码本应被 gosec 在评审前捕获的凭据。

AI 模型在陈旧的 Stack Overflow 答案上训练,并照搬它们在那里学到的模式,包括已废弃的 API、缺失的类型注解,以及那些技术上正确但太过庞大、难以安全评审的函数。你需要让 linter 进入流程当中。不是作为一条建议,而是作为一道关卡。

本指南涵盖三套生态系统的配置(用 Ruff 的 Python、用 ESLint v10 扁平配置的 TypeScript/JavaScript,以及用 golangci-lint 的 Go),规则专门针对 AI 引入的失败模式做了调优。随后讲解如何让这道关卡变得难以绕过,使 agent 无法仅凭 --no-verify 跳过本地钩子而不被另一层拦截。

这套配置是分层的:IDE 级的 linting 在 agent 编写代码时就内联捕获问题,pre-commit 钩子捕获任何到达提交尝试阶段的问题,CI 则捕获任何在本地通过的问题。每一层都相互独立,你可以挑选适用于自己技术栈的语言章节。无论你 lint 哪种语言,强制执行层的工作方式都一样。

TL;DR

  • Ruff(Python)、ESLint v10(TS/JS)和 golangci-lint(Go)各自都有专门的规则,能捕获 AI 最常见的失败
  • 下面的配置都有注释;每条规则的存在都有其理由
  • Lefthook 负责处理 pre-commit 关卡;Cursor 的 afterFileEdit 钩子内联运行 lint
  • 四个强制执行层让 AI agent 用 --no-verify 跳过关卡变得困难得多
  • CI 是最后的后盾:agent 无法向 GitHub Actions 传入 --no-verify

如何为 Python AI 代码配置 Ruff

Terminal output from Ruff flagging unused imports and a missing return type annotation in AI-generated Python code

对于 AI 辅助的代码库,Ruff 是合适的 Python linter。它快到可以在每次保存文件时运行而不阻塞任何东西,既覆盖代码风格也覆盖真正的逻辑错误,并且在同一个二进制文件里附带了格式化功能(替代 Black)。下面的规则针对 AI 模型在 Python 中最常引入的特定失败模式。

安装 Ruff

pip install ruff

# or via uv (faster for new projects):
uv add --dev ruff

就这样。没有插件生态,没有 peer 依赖的扯皮。

pyproject.toml 配置

[tool.ruff]
line-length = 88
target-version = "py311"

[tool.ruff.lint]
select = [
  "E",   # pycodestyle — style consistency
  "F",   # pyflakes — catches unused imports (the most common AI artifact)
  "I",   # isort — import ordering (AI frequently reorders imports incorrectly)
  "N",   # pep8-naming — naming conventions
  "UP",  # pyupgrade — flags deprecated APIs (AI trains on old Stack Overflow answers)
  "S",   # flake8-bandit — security rules: subprocess.shell=True, eval(), hardcoded creds
  "ANN", # type annotation enforcement (AI frequently omits return type annotations)
]
ignore = []  # intentional: do not add sweeping ignores in AI-assisted codebases

[tool.ruff.lint.per-file-ignores]
"tests/**" = ["S101"]  # allow assert in tests

F 系列规则立刻就能回本。AI 代码会为最终用不到的包生成 import 语句,而 F401(未使用的导入)会把每一处都揪出来。UP 系列规则捕获那些 AI 从 3.10 之前的 Python 答案中学来的、对已废弃 API 模式的调用;单是 UP006 和 UP007 就能标记出几十处不必要的类型检查模式。S(Bandit)系列规则捕获安全失败:看起来像凭据的硬编码字符串(S105/S106)、通过 subprocess(shell=True) 进行的 shell 注入(S603/S607)、弱加密选择(S324)。

ignore = [] 是刻意为之的。你往这个列表里添加的每一个例外,都是一类你决定放行的 AI 失败。

运行 Ruff

ruff check .           # lint only — see what's wrong
ruff check --fix .     # auto-fix safe violations (imports, formatting)
ruff format .          # format the codebase (replaces Black)

--fix 会默认应用 Ruff 的安全修复,例如移除未使用的导入,或应用直接明了的格式化与 lint 更正。Ruff 也有不安全的修复,但那些需要显式地选择启用,而且应当更谨慎地评审。Ruff 无法安全修复的任何内容,都要手动评审。

如何为 TypeScript 和 JavaScript AI 代码配置 ESLint v10

ESLint v10 flat config reporting no-explicit-any and max-lines-per-function violations in an AI-generated TypeScript file

ESLint v10 弃用了旧版的 .eslintrc.* 配置格式。现在一切都是 eslint.config.mjs 中的扁平配置。如果你看到某个教程在用 .eslintrc.json 或 .eslintrc.js,那它针对的是 ESLint v8 或 v9,其语法是不同的。请使用下面这套。

本配置中的 @typescript-eslint 规则针对 AI 生成的 TypeScript 中反复出现的特定失败模式:any 这个逃生通道、难以评审的庞大函数,以及本该是常量的硬编码值。

安装带 TypeScript 支持的 ESLint v10

npm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin

# or with pnpm:
pnpm add -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin

截至 2026 年 6 月,ESLint v10.5.0 是当前版本。@typescript-eslint 系列包应与你的 TypeScript 版本匹配;请查阅它们的 README 以了解兼容性矩阵。

eslint.config.mjs 配置

import tseslint from '@typescript-eslint/eslint-plugin';
import tsParser from '@typescript-eslint/parser';

export default [
  {
    files: ['**/*.ts', '**/*.tsx'],
    languageOptions: {
      parser: tsParser,
      parserOptions: { project: './tsconfig.json' },
    },
    plugins: { '@typescript-eslint': tseslint },
    rules: {
      // AI defaults to `any` to bypass the type system — this blocks it
      '@typescript-eslint/no-explicit-any': 'error',

      // AI generates variables it declares but never uses
      '@typescript-eslint/no-unused-vars': 'error',

      // AI writes functions that work but are too long to review safely
      'max-lines-per-function': ['error', { max: 50 }],

      // AI overloads function signatures; this forces decomposition
      'max-params': ['error', 2],

      // AI hardcodes values that should be named constants
      'no-magic-numbers': ['error', { ignore: [0, 1, -1] }],

      // AI writes files that are too large to reason about in a single review
      'max-lines': ['error', { max: 250 }],

      // AI leaves console.log debugging in production code
      'no-console': 'warn',
    },
  },
];

max-lines-per-function: 50 规则是本配置中最激进的设置。在 AI 辅助的代码库里,首次运行时你会不断撞上它。这正是目的所在。你就应该撞上它。当你在大批量评审 AI 输出时,超过 50 行的函数是第一类变得无法推理理解的东西。

max-params: 2 规则强制进行分解。AI 模型从那些把五参数函数视为正常的代码库中学习;这条规则通过要求 agent 使用一个选项对象来予以反制,而这是更好的设计,也更易于阅读。

运行 ESLint

npx eslint .                       # lint
npx eslint . --fix                 # auto-fix safe issues
npx eslint . --max-warnings 0      # CI mode — treats warnings as errors

在你的 CI 步骤中使用 --max-warnings 0。它把 no-console 警告从「技术上已记录」提升为「实际阻断」。

可选:针对 AI 生成文件的更严格规则

如果你的团队使用某种文件命名约定来标记 AI 生成的代码(*.ai.ts、*-generated.ts 或类似形式),你可以专门对这些文件应用更严格的规则:

// Add to eslint.config.mjs after the main config object
{
  files: ['**/*.ai.ts', '**/*.ai.tsx', '**/*-generated.ts'],
  rules: {
    'max-lines': ['error', { max: 100 }],  // tighter file ceiling
    'complexity': ['error', 5],             // McCabe complexity limit
  },
}

如何为 Go AI 代码配置 golangci-lint

golangci-lint 是 Go 的标准多 linter 运行器。它附带 gosec、errcheck、staticcheck 以及另外 40 多个 linter,可从单个 YAML 文件配置。对于 AI 生成的 Go 代码,关键规则是错误返回值检查与安全模式检测:这是 AI 模型在 Go 中最一贯漏掉的两类失败。

安装 golangci-lint

# Official binary installer:
curl -sSfL https://golangci-lint.run/install.sh | sh -s -- -b "$(go env GOPATH)/bin" v2.12.2

# or via Homebrew:
brew install golangci-lint

.golangci.yml 配置

version: "2"

linters:
  enable:
    - gosec        # security: flags hardcoded creds (G101), file path injection (G304), weak crypto (G401)
    - unused       # flags unused vars and functions — a common AI artifact in Go
    - errcheck     # AI frequently ignores error returns — this blocks it
    - govet        # catches subtle correctness bugs AI introduces
    - staticcheck  # comprehensive static analysis
    - revive       # style: catches non-idiomatic Go patterns AI writes
    - misspell     # AI occasionally misspells in comments and string literals

  settings:
    gosec:
      severity: medium
      confidence: medium
    errcheck:
      check-type-assertions: true  # check `val, ok := x.(Type)` patterns
      check-blank: true            # catch `_ = someErr` error suppression

run:
  timeout: 3m
  issues-exit-code: 1

Go 的错误处理模式在设计上是显式的:每个可能失败的函数都会返回一个 error 值。AI 模型理解这一点,但对它重视不足;它们会在非关键代码路径中省略错误检查。errcheck 让这种省略变成一次 lint 失败。

gosec 是安全 linter。对于 AI 代码,它能捕获 AI 从 2020 年之前的 Go 教程中学来的模式:不安全的随机数生成(G404)、不安全的哈希函数(G401)、文件权限问题(G306)。这些都是你在代码评审中抓不到的错误,因为它们在语法上看起来很正常。

运行 golangci-lint

golangci-lint run ./...            # lint all packages
golangci-lint run --fix ./...      # auto-fix where possible

如何把 Linter 接入 Pre-commit 钩子

pre-commit 钩子会在每次 git commit 之前运行 lint,并在 lint 失败时阻断提交。这意味着 AI agent 无法提交不符合你所配置规则的代码。它必须先修复违规之处。

Lefthook 是推荐的选项。它跨平台、速度快,并且有一种专门适配 AI agent 强制执行的配置模式(下一节会讲到)。

Lefthook

npm install --save-dev lefthook
npx lefthook install

lefthook.yml:

pre-commit:
  parallel: true
  commands:
    lint-python:
      glob: "*.py"
      run: ruff check {staged_files} --fix
    lint-js-ts:
      glob: "*.{js,ts,tsx}"
      run: npx eslint {staged_files} --fix
    lint-go:
      glob: "*.go"
      run: golangci-lint run {staged_files}
  fail_text: |
    Lint failed. For AI Agents: fix all lint violations before committing.
    Do not use --no-verify to bypass this gate.

fail_text 消息会被 AI agent 在提交失败时读取。该模式记录在 Liam Bigelow 关于面向 Claude Code 的 Lefthook lint 强制执行的文章中。仅凭这一点拦不住一个铁了心的 agent,但它给出了正确的下一步指令(「修复 lint 违规」),而不是让它自行推断出一种绕开的办法。

替代方案:pre-commit(仅 Python 的配置)

如果你处在仅 Python 的技术栈,并且更偏好 pre-commit 框架:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.15.17
    hooks:
      - id: ruff-check
        args: [--fix]
      - id: ruff-format

Cursor:通过 afterFileEdit 钩子进行内联 Linting

如果你使用 Cursor,可以在 AI 修改文件时立即触发 lint,甚至在它尝试提交之前。在你的项目根目录创建 .cursor/hooks.json:

{
  "hooks": {
    "afterFileEdit": [
      {
        "match": "*.py",
        "run": "ruff check {file} --fix"
      },
      {
        "match": "*.{ts,tsx,js}",
        "run": "npx eslint {file} --fix"
      },
      {
        "match": "*.go",
        "run": "golangci-lint run {file}"
      }
    ]
  }
}

每当 Cursor 的 AI 修改文件时,这都会触发。agent 会在内联中获得 lint 反馈,在它把任务当作已完成之前,因此大多数违规都会在到达 pre-commit 关卡之前被修复。

如何让这道关卡更难被 AI Agent 绕过

Diagram of four enforcement layers (CLAUDE.md policy, Claude Code deny rule, PreToolUse hook, and CI backstop) blocking an AI agent from skipping the lint gate with --no-verify

四个强制执行层让 AI agent 用 --no-verify 跳过 pre-commit 关卡变得困难得多。每一层针对不同的攻击面:CLAUDE.md 中的策略、一条 Claude Code 拒绝规则、一个 PreToolUse 钩子,以及一道 CI 后盾。把它们当作一套分层配置来对待,而不是四个相互独立的保证。

当 lint 失败、且 agent 判定这些失败「与它的改动无关」时,AI agent 有时会向 git commit 传入 --no-verify 以跳过 pre-commit 钩子。这个判断未必总是错的,但你不应让 agent 单方面去做。lint 关卡的全部意义就在于由人设定策略;agent 的职责是满足它,而不是绕过它。

下面是每一层及其覆盖的攻击面。

第 1 层:在 CLAUDE.md 中记录策略

## Linting Policy

NEVER use `git commit --no-verify`. All commits must pass pre-commit hooks.
Pre-commit hooks run lint. Fix lint violations before committing. Do not treat
lint failures as unrelated to your changes — they may not be, and you don't get
to decide that.

Claude Code 会在会话开始时读取 CLAUDE.md。这并非强制执行;agent 仍然可以尝试绕过。但它消除了「我不知道」这条路径,并设定了一条清晰的策略,使 agent 必须主动选择去违反它,而它这样做的可能性,要低于从沉默中推断出一种绕开办法的可能性。

第 2 层:Claude Code 拒绝规则

添加到 .claude/settings.json:

{
  "permissions": {
    "deny": [
      "Bash(git commit --no-verify*)"
    ]
  }
}

这会阻断显式的调用。一个局限:拒绝规则使用前缀匹配,所以它只能捕获紧跟在 commit 之后的 --no-verify。一个足够有创意的 agent 可以把调用结构改成别的样子。不要单靠这一条。

第 3 层:PreToolUse 钩子

安装 block-no-verify 包:

npm install --save-dev block-no-verify

然后在 Claude Code 的设置中把它配置为 PreToolUse 钩子。它会在每次工具调用之前触发,并跨六个 git 子命令检查参数中是否有 --no-verify,而不仅仅是 commit。它以非零退出来在调用执行之前阻断它。

其手册 pydevtools.com 对此直言不讳:「钩子层是唯一能可靠强制执行该规则的一层。」请把它与其他各层配合使用;不要把任何本地钩子当作完整的保证。

第 4 层:CI 后盾

CI 在服务器上运行,在那里 agent 没有可以传入标志的 shell:

# .github/workflows/lint.yml
name: Lint
on: [push, pull_request]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Lint Python
        run: |
          pip install ruff
          ruff check . --output-format github

      - name: Lint JS/TS
        run: npx eslint . --max-warnings 0

      - name: Lint Go
        uses: golangci/golangci-lint-action@v9
        with:
          version: v2.12.2

AI agent 无法向 CI 传入 --no-verify。GitHub Actions 独立于 agent 在本地所做的一切而运行。如果某个提交不知怎么带着失败的 lint 蒙混过关,CI 会在它合并之前将其捕获。

这是最后的后盾。

附赠:ESLint MCP 服务器

如果你使用 Claude Code,有一个主动式的层能减少你撞上关卡的频率。

ESLint MCP 服务器(@eslint/mcp)把 ESLint 直接集成进 agent 的工具循环中。agent 可以在任务期间、在它尝试提交之前查询 ESLint。全局安装它:

npm install -g @eslint/mcp@latest

添加到 .claude/settings.json:

{
  "mcpServers": {
    "eslint": {
      "command": "npx",
      "args": ["@eslint/mcp@latest"]
    }
  }
}

配置好之后,agent 就可以在任务期间查询 ESLint。agent 会在内联中获得 lint 反馈,一些违规可以在到达钩子之前被更正。这并不替代关卡,而是减少关卡上的噪音。

常见问题

如果我只用一种语言,需要把三个 linter 都配置好吗?

不需要。为你的主要语言以及强制执行层做好配置即可。如果你的技术栈只用 TypeScript,就配置 ESLint,跳过 Ruff 和 golangci-lint。无论你 lint 哪种语言,防止 agent 绕过的那一节都适用。

首次运行时,这些配置会不会弄坏我现有的代码库?

几乎肯定会,而且是故意的。先运行 ruff check --fix . 或 npx eslint . --fix 来自动更正安全的违规。自动修复之后剩下的就是手动评审清单:no-explicit-any 强制转换、超过 50 行的函数、缺失的错误处理。逐步处理这些。不要为了回避它们而添加 ignore 规则。

到了现在这个阶段,Biome 算是 ESLint 的替代品吗?

Biome v2.5.0(2026 年 6 月发布)在格式化和基础 lint 规则上有竞争力。它比 ESLint 快,而且如果你通过 bun x ultracite@latest init 安装它,配置开销为零。对于那些想要单一工具、又不需要 @typescript-eslint 全部规则深度的团队,Biome 是个合理的选择。但对于本指南中针对 AI 的规则(max-params、no-magic-numbers、带有面向 AI 阈值的 max-lines-per-function),带 @typescript-eslint 的 ESLint 仍然覆盖更广。你可以两者都用:Biome 负责格式化和基础 lint,ESLint 负责面向 AI 的规则。

如果我的 AI agent 不断重新生成同样的 lint 违规,该怎么办?

这个 agent 是在绕开规则,而不是在修复底层问题。对于 no-explicit-any,这意味着加一个类型断言,而不是定义真正的类型。对于 max-lines-per-function,这意味着抽取出一个毫无实际用处、只是为了把行数压到阈值以下的辅助函数。这两种做法都过不了代码评审。lint 规则捕获的是症状;根本原因在 agent 的提示词里。优化提示词,明确指定类型约束或预期的分解方式;相比隐式的规则,agent 会更可靠地遵循显式的结构指引。如果你在运行一套更复杂的 agent 配置,把工作限定给一个把这些约束写进其指令里的专用子 agent,往往比一个宽泛的单一提示词更稳。

ESLint MCP 服务器会替代 pre-commit 关卡吗?

不会。它减少的是 agent 生成无法通过关卡的代码的频率。关卡仍会在每次提交时运行。MCP 服务器的内联检查与 pre-commit 钩子的强制执行是互补的,所以两者都别去掉。

Share

博客更多内容

继续阅读。

准备好部署了吗? 起价 $2.48/月。

独立云厂商,自 2008 年起。AMD EPYC、NVMe、40 Gbps。14 天退款保证。