只让改个前端样式,它动了后端逻辑
Asked for a UI tweak, it changed backend logic
一个小小的前端需求(“按钮改成‘保存’”“隐藏这个字段”)回来时,API 处理、校验逻辑或数据库查询也被改了。
适用于: Claude Code · Cursor · Codex · Copilot agent
症状
前端改动生效了,也没 bug,但 diff 碰了 `api/`、`server/` 或 `db/`。本该在视图里隐藏的字段被直接从 API 返回里删了,或者校验逻辑被“对齐”成新文案。
为什么会这样
智能体会选能最直接让可见结果成立的改法,不管它在哪一层。没有声明分层边界时,“隐藏字段”在 API 里做和在组件里做一样容易。这正是 @liushiyumath 三分法里第 3 类的例子:能用、没 bug,但不是你想要的做法。Lawrence Jones 也说过,智能体“有时会做一些不太理想的事”。
示例
示意。
textRequest: "Don't show the phone number on the profile card."
M src/components/ProfileCard.tsx - <p>{user.phone}</p>
M server/api/users.ts - phone: user.phone, // removed from the API for every client
复制即用的修复
按你用的智能体挑选文件。规则片段用英文写,放进任何仓库都能用。
规则
Claude Code:加入 CLAUDE.md
CLAUDE.md## Respect layer boundaries
- Frontend requests change frontend code only (components, styles, copy, client state).
Do not edit `server/`, `api/`, `db/`, migrations or shared validation for a UI request.
- If the UI change seems to need a backend change, stop and propose it separately: what, why,
and which other clients would be affected.
- The reverse holds too: backend tasks don't restyle the UI.
规则
Codex、Copilot 编码智能体等:加入 AGENTS.md
AGENTS.md## Respect layer boundaries
- Frontend requests change frontend code only (components, styles, copy, client state).
Do not edit `server/`, `api/`, `db/`, migrations or shared validation for a UI request.
- If the UI change seems to need a backend change, stop and propose it separately: what, why,
and which other clients would be affected.
- The reverse holds too: backend tasks don't restyle the UI.
Cursor 规则
Cursor:项目规则
.cursor/rules/frontend-tweak-changed-backend.mdc---
description: "AgentFails: Asked for a UI tweak, it changed backend logic"
alwaysApply: true
---
## Respect layer boundaries
- Frontend requests change frontend code only (components, styles, copy, client state).
Do not edit `server/`, `api/`, `db/`, migrations or shared validation for a UI request.
- If the UI change seems to need a backend change, stop and propose it separately: what, why,
and which other clients would be affected.
- The reverse holds too: backend tasks don't restyle the UI.
钩子
仅限前端任务的范围文件
供下面的范围守卫钩子使用。任务开始时写好,结束后删除。
.agent-scope# Frontend-only task: UI tweak on the profile card
src/components/**
src/styles/**
src/app/**/page.tsx
tests/components/**
钩子
Claude Code PreToolUse 钩子:拦截声明范围之外的编辑
改编自 dev.to(yureki_lab)的 diff budget 设计。在仓库根目录的 .agent-scope 中每行写一个允许的 glob。退出码 2 会阻止该工具调用,并把 stderr 反馈给智能体。它只覆盖文件编辑工具,shell 写入仍需靠 diff 检查兜底。
.claude/hooks/scope_guard.py#!/usr/bin/env python3
"""PreToolUse hook: block Edit/Write/MultiEdit outside the globs in .agent-scope."""
import fnmatch, json, os, sys
call = json.load(sys.stdin)
if call.get("tool_name") not in ("Edit", "Write", "MultiEdit"):
sys.exit(0)
root = os.environ.get("CLAUDE_PROJECT_DIR") or call.get("cwd") or os.getcwd()
scope_file = os.path.join(root, ".agent-scope")
if not os.path.exists(scope_file):
sys.exit(0) # no scope declared for this task -> allow
allowed = [l.strip() for l in open(scope_file) if l.strip() and not l.startswith("#")]
target = os.path.relpath(call["tool_input"]["file_path"], root)
if any(fnmatch.fnmatch(target, pat) for pat in allowed):
sys.exit(0)
print(
f"BLOCKED: {target} is outside this task's scope ({', '.join(allowed)}).\n"
"If this edit is required, stop and ask the user to widen .agent-scope, with a one-line reason.\n"
"If it is just an improvement you noticed, add it to FOLLOWUPS.md and keep going.",
file=sys.stderr,
)
sys.exit(2)
配置
在 .claude/settings.json 中注册范围守卫
.claude/settings.json{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write|MultiEdit",
"hooks": [
{ "type": "command", "command": "python3 \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/scope_guard.py" }
]
}
]
}
}
技能
团队共享的 /verify 技能(Claude Code)
把它提交进仓库,让整个团队(以及每个智能体会话)跑同一套检查。把命令换成你项目里真实的命令。
.claude/skills/verify/SKILL.md---
name: verify
description: Verify the current change before calling it done. Use before every commit, PR, or "done" message.
---
# Verify
Run every step and paste the real output. Never summarize a step you did not run.
1. **Scope**: run `git diff --stat` and `git status --short`. For every changed file, say which part
of the request needs it. Any file you cannot justify gets reverted, or listed under
"Unrequested changes" for the user to decide.
2. **Build and types**: `npm run build` (or the project's build command).
3. **Tests**: `npm test`. Report pass/fail counts. If a test was edited, skipped or deleted,
say so explicitly with the reason.
4. **Behavior**: exercise the changed behavior once for real (a curl, a CLI run, a screenshot)
and show what happened.
5. **Report** in this format:
- Requested: <one line>
- Changed files: <list, each with its reason>
- Checks run: <command -> result>
- Not done / unsure: <list, or "none">