1. 两个 Bug 同时来为什么单工作目录会翻车线上项目最常见的场景不是「一个 Bug 修三天」而是「两个 Bug 同时冒出来还都挺急」。比如后端分页接口对非法参数处理错误前端商品列表在手机屏幕上横向溢出。你打开 Copilot CLI一个会话让它修后端另一个会话让它修前端看起来很美好——直到你发现两个 Agent 在同一个工作目录里同时读写文件。问题不在于它们会不会「故意」覆盖而在于它们共享了同一套工作区状态同一个暂存区、同一份未提交修改、同一批依赖文件。后端 Agent 跑了一次格式化前端 Agent 刚写的 CSS 就被卷进 diff前端 Agent 改了package.json后端 Agent 的测试快照就对不上了。即使两个任务本身毫无关系依赖文件、配置文件、测试快照都可能成为干扰源。传统解法是串行修完第一个 → 提交 → 切分支 → 再修第二个。这很稳但完全没利用上 Agent 并行工作的能力。Git Worktree 解决的就是这个问题——一个仓库同时关联多个工作目录每个目录有独立的当前分支、独立的工作区文件、独立的暂存区状态、独立的未提交修改但它们共享同一份 Git 对象和历史记录。Copilot CLI 新增的/worktree命令把「创建工作树」和「启动 AI 会话」这两步衔接起来了。你不再需要手动git worktree add再切目录再开 CLI而是直接在会话里用一条命令把新任务丢进隔离工作区。这篇就按「后端 Agent → fix-api 分支」「前端 Agent → fix-mobile-css 分支」的路径把创建、切换、验证、合并、清理整条链路走一遍命令和配置都能直接复制。适合谁看已经在用 Copilot CLI 做日常编码、想尝试多 Agent 并行但被「互相覆盖」劝退的开发者以及想理解 Git Worktree 在 AI 编码流程里到底怎么落地的工程同学。下面所有操作都在本地 Git 仓库内完成不涉及任何网络代理配置。2. 前置准备Copilot CLI 与 Git Worktree 环境核对在动手之前先把环境对齐。/worktree目前属于实验功能并且必须在 Git 仓库中使用。这意味着两件事第一你的项目得先git init并至少有一次提交第二你得在 CLI 里显式开启实验开关否则/worktree可能不在命令列表里。基础环境建议Git 2.20、Node.js 22、Python 3.11本文示例项目用 FastAPI你也可以换成任意技术栈。安装 Copilot CLInpm install -g github/copilot检查版本确认装上了copilot --version git --version python --version登录 GitHub 账号copilot login也可以直接运行copilot然后按终端提示完成浏览器授权。这里有个安全提醒值得单独说不要从用户主目录、包含未知可执行文件的目录、或存放大量敏感资料的目录启动 Copilot CLI。官方明确说明可信目录的权限范围属于启发式控制不能当成完整安全沙箱。换句话说工作树隔离的是代码文件不是系统权限。进入项目根目录后先确认工作区干净git status --short如果没有输出说明可以开始创建 Worktree。如果还有未提交修改先提交或 stash 掉否则新工作树会从当前 HEAD 创建可能带上你不想带的状态。还可以执行一次copilot init它会分析项目并生成或更新.github/copilot-instructions.md。建议在这个文件里写清楚构建与测试要求比如## Project rules - Python 版本为 3.11 或更高。 - 后端使用 FastAPI。 - 修改后端后必须运行 pytest -q。 - 修改前端时禁止引入新的 UI 框架。 - 不得修改任务范围之外的文件。 - 不得删除现有测试来使测试通过。明确的仓库规则能减少两个并行会话产生风格不一致的修改。这一步不是必须的但在多 Agent 场景下收益很明显——你相当于给两个 Agent 发了同一份「施工规范」。关于/worktree的三种形式先有个印象后面会逐个用到命令作用/worktree fix-api从当前 HEAD 创建工作树并切换过去/worktree 修复分页参数异常根据任务描述创建分支并把描述作为新任务提示/worktree new 修复移动端布局保留当前会话在新工作树中启动新会话copilot --worktreefix-api启动 CLI 时直接创建或复用指定工作树/worktree、/worktree new和--worktree目前均属于实验功能。正式团队采用前应固定 CLI 版本并先在测试仓库中验证行为别直接在生产仓库上试。3. 可复制配置从零搭一个双 Bug 演示项目为了让你能完整复现我们先造一个同时存在两个 Bug 的小项目。结构如下parallel-bug-demo/ ├── app/ │ ├── __init__.py │ └── main.py ├── static/ │ ├── index.html │ └── style.css ├── tests/ │ └── test_api.py ├── requirements.txt └── .gitignore创建虚拟环境并激活python -m venv .venvWindows PowerShell.venv\Scripts\Activate.ps1Linux 或 macOSsource .venv/bin/activaterequirements.txt内容fastapi uvicorn pytest httpx安装依赖python -m pip install -r requirements.txt3.1 制造后端分页 Bugapp/main.pyfrom fastapi import FastAPI, Query app FastAPI() PRODUCTS [ {id: 1, name: Keyboard}, {id: 2, name: Mouse}, {id: 3, name: Monitor}, {id: 4, name: Dock}, ] app.get(/api/products) def list_products( limit: int | None Query(defaultNone), ) - dict: # Bug0 会被当成 False最终退回默认值 10。 actual_limit limit or 10 return { limit: actual_limit, items: PRODUCTS[:actual_limit], }这里有两个问题limit0被静默转换成 10负数或过大数值没有被接口层拒绝。正确需求是limit可省略默认 10传入时只允许 1—100非法参数返回 HTTP 422。3.2 编写后端测试tests/test_api.pyfrom fastapi.testclient import TestClient from app.main import app client TestClient(app) def test_default_limit() - None: response client.get(/api/products) assert response.status_code 200 assert response.json()[limit] 10 def test_custom_limit() - None: response client.get(/api/products, params{limit: 2}) assert response.status_code 200 assert len(response.json()[items]) 2 def test_zero_limit_is_rejected() - None: response client.get(/api/products, params{limit: 0}) assert response.status_code 422 def test_too_large_limit_is_rejected() - None: response client.get(/api/products, params{limit: 101}) assert response.status_code 422运行pytest -q预期两个用例失败FAILED tests/test_api.py::test_zero_limit_is_rejected FAILED tests/test_api.py::test_too_large_limit_is_rejected3.3 制造移动端布局 Bugstatic/index.html!doctype html html langzh-CN head meta charsetutf-8 meta nameviewport contentwidthdevice-width, initial-scale1 titleProduct Dashboard/title link relstylesheet href./style.css /head body main classpage header classhero p classeyebrowPRODUCT DASHBOARD/p h1商品管理/h1 button classprimary-button新建商品/button /header section classproduct-grid article classproduct-card h2Mechanical Keyboard/h2 p库存12/p /article article classproduct-card h2Wireless Mouse/h2 p库存30/p /article article classproduct-card h24K Monitor/h2 p库存8/p /article /section /main /body /htmlstatic/style.css* { box-sizing: border-box; } body { margin: 0; background: #0b1020; color: #f4f7ff; font-family: system-ui, sans-serif; } .page { width: 1180px; margin: 0 auto; padding: 48px; } .hero { display: flex; align-items: center; gap: 24px; } .hero h1 { margin-right: auto; } .primary-button { min-width: 180px; padding: 14px 24px; } .product-grid { display: grid; grid-template-columns: repeat(3, 340px); gap: 24px; margin-top: 40px; } .product-card { padding: 24px; border: 1px solid #34405e; border-radius: 20px; background: #151d33; }桌面端看起来基本正常但页面设置了固定 1180px 宽度卡片列也使用固定宽度。手机打开后会产生明显的横向滚动。3.4 初始化 Git 仓库git init git add . git commit -m chore: create parallel bug demo确认工作区干净git status --short没有输出就说明可以开始创建 Worktree 了。到这里两个 Bug 已经就位测试也处于「红」的状态接下来就是让两个 Agent 在隔离工作区里各自把它们修绿。4. 验证请求两个 Worktree 并行修复与结果确认进入 Copilot CLIcopilot开启实验功能/experimental on检查帮助确认/worktree在当前版本可用/help4.1 启动第一个 Worktree 修复后端创建第一个工作树并把任务描述直接作为提示/worktree new 修复FastAPI分页参数异常。仅修改app/main.py和tests/test_api.py。limit默认值为10显式传入时必须在1到100之间不得删除或放宽现有测试。完成后运行pytest -q并总结修改文件和测试结果。第一个 Agent 应该会阅读接口和测试 → 复现失败 → 修改参数约束 → 运行完整测试 → 汇报差异。一种合理修复是from fastapi import FastAPI, Query app FastAPI() PRODUCTS [ {id: 1, name: Keyboard}, {id: 2, name: Mouse}, {id: 3, name: Monitor}, {id: 4, name: Dock}, ] app.get(/api/products) def list_products( limit: int Query(default10, ge1, le100), ) - dict: return { limit: limit, items: PRODUCTS[:limit], }测试结果应为4 passed。关键点不是让 Agent「想办法修一下」而是提前限制可以修改哪些文件、期望行为是什么、必须运行什么测试、哪些捷径禁止使用。4.2 启动第二个 Worktree 修复前端保留后端会话继续运行再创建第二个会话/worktree new 修复static/index.html和static/style.css的移动端横向溢出。仅修改static目录页面在360px宽度下不得出现横向滚动桌面端保持三列布局移动端按钮改为全宽。不要引入前端框架。完成后检查CSS语法并总结改动。第二个 Agent 与后端 Agent 处于不同工作树因此它修改static目录时不会看到第一个会话尚未合并的后端变化。一种合理的 CSS 修复如下* { box-sizing: border-box; } html, body { max-width: 100%; overflow-x: hidden; } body { margin: 0; background: #0b1020; color: #f4f7ff; font-family: system-ui, sans-serif; } .page { width: min(100% - 32px, 1180px); margin: 0 auto; padding: 48px 0; } .hero { display: flex; align-items: center; gap: 24px; } .hero h1 { margin-right: auto; } .primary-button { min-width: 180px; padding: 14px 24px; } .product-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 24px; margin-top: 40px; } .product-card { min-width: 0; padding: 24px; border: 1px solid #34405e; border-radius: 20px; background: #151d33; } .product-card h2 { overflow-wrap: anywhere; } media (max-width: 720px) { .page { width: min(100% - 24px, 1180px); padding: 24px 0; } .hero { align-items: stretch; flex-direction: column; } .hero h1 { margin: 0; } .primary-button { width: 100%; min-width: 0; } .product-grid { grid-template-columns: 1fr; gap: 16px; margin-top: 24px; } }4.3 查看两个并行会话新版 Copilot CLI 加入了 Sessions 侧栏可以查看和切换多个并发会话。快捷操作包括打开 Sessions 侧栏、n创建新会话、x关闭当前会话、收起侧栏。也可以用/sessions info查看信息或通过/resume进入会话选择器。Git 层面确认工作树git worktree list预期看到类似结果/path/parallel-bug-demo abc1234 [main] /path/parallel-bug-demo.worktrees/fix-api def5678 [fix-api] /path/parallel-bug-demo.worktrees/fix-mobile-css ghi9012 [fix-mobile-css]工作树的真实目录名和分支名可能由 CLI 自动生成因此不要在自动化脚本中硬编码示例名称。4.4 不要直接相信「任务已完成」Agent 报告测试通过不等于你已经完成验收。至少检查四类证据git branch --all git worktree list git log --oneline --all --decorate --graph -10 git diff main...fix-api git diff main...fix-mobile-css重点确认是否修改了任务范围之外的文件是否删除或弱化测试是否加入不必要依赖是否写死本机路径是否将密钥写入代码是否用隐藏溢出来掩盖布局问题是否改变原有接口返回结构。如果分支名由 CLI 自动生成以git branch显示结果为准。4.5 依次合并两个修复回到主工作目录cd /path/to/parallel-bug-demo git switch main git status --short先合并后端修复git merge --no-ff fix-api再合并前端修复git merge --no-ff fix-mobile-css运行回归测试pytest -q启动接口uvicorn app.main:app --reload验证正常请求curl http://127.0.0.1:8000/api/products?limit2预期返回{ limit: 2, items: [ {id: 1, name: Keyboard}, {id: 2, name: Mouse} ] }验证非法参数curl -i http://127.0.0.1:8000/api/products?limit0预期 HTTP 状态为422 Unprocessable Entity。前端则应至少检查 360×800、390×844、768×1024、1440×900 这几个视口不要只在自己的桌面分辨率下看一遍就结束。4.6 如果两个 Agent 修改了同一个文件Worktree 可以隔离编辑过程但不会自动消除合并冲突。假设两个 Agent 都修改了README.md第二次合并可能出现CONFLICT (content): Merge conflict in README.md Automatic merge failed查看冲突git status git diff --name-only --diff-filterU文件中可能出现 HEAD 后端分页参数现已限制为 1—100。 移动端商品列表现已改为单列布局。 fix-mobile-css人工整理为后端分页参数现已限制为 1—100。 移动端商品列表在 720px 以下改为单列布局。然后继续git add README.md git commit这说明任务拆分时应该优先按照文件和模块边界分工拆分方式冲突风险一个改后端一个改前端低一个改业务代码一个补独立测试中两个同时重构相同服务高两个同时修改锁文件和公共配置很高Worktree 只是把冲突从「同时写文件」推迟到「合并分支」并不会判断哪一份业务逻辑正确。5. 本篇常见错排查401、local proxy failed、reading choices 与 OAuth并行流程跑起来之后报错往往集中在认证、网络和会话状态三类。下面按真实报错逐个拆。5.1 401 Unauthorized现象CLI 里执行任何需要模型响应的命令都返回 401或者提示 token 无效。排查顺序先确认copilot login是否真的完成浏览器授权有没有点到最后一步再检查当前终端环境里有没有残留的旧 token 环境变量覆盖了登录态。如果你在多个终端窗口之间切换注意每个窗口的环境变量可能不同。如果你是通过 API 方式接入模型服务401 通常意味着 Key 不对或 Base URL 不匹配。以 TaoToken 为例正确的三件套是Base URL: https://taotoken.net/api Key: 在控制台创建的 API Key Model ID: 按文档填写的模型标识三个字段必须同时正确缺一个都会 401 或 404。Key 建议放在环境变量里不要写进代码export TAOTOKEN_API_KEY你的Key5.2 local proxy failed现象CLI 启动时报local proxy failed或连接被拒绝。这类报错通常和本地网络配置有关。先确认没有残留的代理环境变量echo $HTTP_PROXY echo $HTTPS_PROXY如果输出非空而你并不需要代理清掉它们再重试。另一个常见原因是端口被占用——比如你之前启动的 uvicorn 还占着 8000新的服务起不来。用lsof -i :8000macOS/Linux或netstat -ano | findstr 8000Windows确认。5.3 reading choices 报错现象模型返回解析失败日志里出现reading choices相关字段缺失。这通常意味着返回体结构和客户端预期不一致。可能原因Base URL 指向了错误的端点比如把对话端点和 completions 端点搞混或者 Model ID 填了一个不存在的模型。检查你的配置里 Base URL 是否精确到/apiModel ID 是否和文档一致。5.4 OAuth 授权失败现象copilot login打开浏览器后回调失败或提示 OAuth error。先确认系统时间准确——OAuth 对时间偏差敏感差几分钟就可能失败。再检查浏览器是否拦截了回调地址。如果公司网络有额外的登录门户也可能干扰回调。换一个网络环境或换一个浏览器重试通常能定位问题。5.5 Worktree 相关报错fatal: not a git repository说明你不在 Git 仓库里执行/worktree。先git init并提交一次。worktree already exists同名工作树已存在。用git worktree list查看必要时git worktree remove清理后再建。branch already checked out同一个分支不能在两个工作树同时检出。给新任务换个分支名。5.6 权限与安全边界Copilot CLI 第一次准备使用能修改文件或执行程序的工具时通常会请求授权。常见选择包括仅允许本次操作、本会话持续允许该工具、拒绝并要求改变方案。如果对rm选择「本会话持续允许」并不只是允许删除某一个指定文件而可能允许该会话继续运行其他rm命令。因此不建议为了省一次确认就直接长期使用copilot --allow-all或copilot --allow-all-tools。优先采用明确任务范围 逐项批准危险命令 独立测试数据 合并前查看 Diff。Worktree 降低的是代码互相覆盖的概率不是命令执行风险。还有一个容易被忽略的点Worktree 不是安全沙箱。它能隔离当前分支、工作目录文件、未提交修改、暂存状态但通常不能隔离操作系统进程、用户环境变量、网络访问、SSH 配置、云服务凭证、本地数据库、Docker 守护进程。如果 Agent 在 Worktree 里运行docker compose down它可能仍然影响其他工作树共用的容器。如果两个工作树使用相同端口 8000后启动的服务也可能因为端口占用而失败。更稳妥的做法是为每个任务分配独立资源APP_PORT8101 TEST_DATABASE_URLsqlite:///api-agent.db另一个工作树使用APP_PORT8102 TEST_DATABASE_URLsqlite:///css-agent.db对于高风险项目还应进一步使用 Dev Container、Docker 隔离网络、临时测试数据库、最小权限令牌、出站网络限制、只读挂载、CI 环境复验。6. 语义一致 CTA把并行修复沉淀成可复用流程/worktree真正有价值的地方不是让你同时打开更多 AI 会话而是为并行任务建立清晰的工程边界。合理流程应该是拆分独立任务 → 创建隔离 Worktree → 两个 Agent 并行修改 → 各自运行测试 → 人工审查 Diff → 依次合并 → 主分支回归验证 → 清理临时工作树。它解决了「多个 Agent 同时碰同一工作目录」的混乱但不会自动解决业务判断、合并冲突、系统权限和测试数据隔离。当任务边界明确时Worktree 可以让两个 Agent 真正并行当任务高度耦合时盲目增加会话数量只会把编辑冲突变成更晚出现的合并冲突。如果你想把这条链路继续往下走几个入口可以按需取用需要创建和管理 API Key、把模型接入到自己的 CLI 或脚本里走 API Keys 控制台配合 接入文档 把 Base URL、Key、Model ID 三件套对齐。想先在网页里验证某个模型的行为再决定要不要接进工作流用 模型对话 快速试。长期做编码和 Agent 任务、需要稳定额度和并发能力看 Coding Plan。用 Claude Code 或 Anthropic 系工具链的参考 Claude Code 接入说明。最后留一个清理清单合并完成后按顺序执行git worktree list git worktree remove ../parallel-bug-demo.worktrees/fix-api git worktree remove ../parallel-bug-demo.worktrees/fix-mobile-css git worktree prune git branch --merged git branch -d fix-api git branch -d fix-mobile-css不要在没有确认提交和合并状态时强制删除工作树或分支。另一个现实问题是依赖占用每个 Worktree 都有独立文件目录多个node_modules、.venv和构建产物可能迅速消耗磁盘空间定期清理是必要的。最值得保留的原则是Agent 负责执行清晰任务Worktree 负责隔离代码变化开发者负责审查与最终决策。