Claude Duet 执行到一半退出——是任务拆得太粗还是页面变了?

文章导读
任务跑到一半停住,第一件该做的事不是重写提示词,而是看退出前最后一条记录落在哪里。如果最后一条停在「等待元素出现」或「点击目标」,多数是页面结构变了,选择器找不到东西;如果最后一条停在「等待下一步」「生成计划」或触发步数上限,且任务描述里含「整理一下、把异常的挑出来、处理剩下的」这类没有边界的动词,通常是任务粒度太粗,执行器拿不到可判定的下一步。这两类可以用同一份日志区分,再用单步任务复跑确认。
📋 目录
  1. A 从退出前的最后一条记录判断退出类型
  2. B 把粗粒度任务改写成可执行的细步骤
  3. C 验证页面元素是否仍然可定位
  4. D 用单步任务做回归验证
A A

任务跑到一半停住,第一件该做的事不是重写提示词,而是看退出前最后一条记录落在哪里。如果最后一条停在「等待元素出现」或「点击目标」,多数是页面结构变了,选择器找不到东西;如果最后一条停在「等待下一步」「生成计划」或触发步数上限,且任务描述里含「整理一下、把异常的挑出来、处理剩下的」这类没有边界的动词,通常是任务粒度太粗,执行器拿不到可判定的下一步。这两类可以用同一份日志区分,再用单步任务复跑确认。

先按退出前最后一条记录分三类:等待超时、元素定位失败、主动中止。定位失败优先查页面结构,超时和步数用尽优先查任务粒度。处理动作是先把任务改写成带完成条件的步骤,再单独复跑失败的那一步,然后对涉及的选择器做一次存在性检查,两者都过再跑完整流程。边界:页面改动和粒度问题可能同时存在,日志分不清时以单步复跑结果为准。

从退出前的最后一条记录判断退出类型

执行轨迹或日志的末尾一般能看出属于哪一类。建议先把下面三类分开,不要一上来就重写任务描述。

  • 等待超时:最后一条是等待响应、等待元素出现、等待导航完成,通常带等待时长或重试次数。排查方向是等待目标是否具体(有没有指定唯一选择器或文本)、页面是否还在加载、模型侧返回是否被截断。超时只是表象,要看它到底在等什么。
  • 元素定位失败:最后一条里出现选择器、XPath 或文本匹配,提示找不到元素、匹配到多个元素(strict mode 冲突)、元素存在但不可见或不可点击。排查方向是页面结构。
  • 主动中止:出现 cancel、abort、超出最大步数、检测到重复动作而终止。这类通常与任务范围过大有关,步骤预算在粗粒度描述下被快速耗尽。

如果日志只有一句「任务失败」,先把执行器的日志级别调到能打印每一步的输入和结果,再复现一次。没有单步记录,后面的判断只能靠猜,也分不清是哪一类。

把粗粒度任务改写成可执行的细步骤

笼统描述的问题不在于写得短,而在于每一步缺少「怎么算完成」的判定条件。同一个任务的两个版本对照如下。

笼统版:

帮我把后台的订单列表导出一份表格,顺便把异常的挑出来。

这段描述里没有页面地址、没有筛选条件、没说「异常」看哪个字段、也没说导出落到哪里。执行器进到列表页之后无法判断下一个动作该点哪里,容易在筛选和导出之间反复试探,最后等待超时,或者被判定为重复动作而中止。

细化版,每一步都带完成条件:

1. 打开订单列表页,等待表格首行出现。
2. 在状态筛选框选择“待处理”,点击查询,等表格重新渲染完成。
3. 读取表格的订单号、下单时间、金额三列,最多读取 50 行。
4. 金额为空或订单号重复的行,单独记为异常行。
5. 点击导出按钮,等下载事件出现后结束。
6. 若第 5 步等待超时,停止执行并报告当前页面上可见的按钮文案,不要自动重试。

差异集中在三处:每步都有明确的成功标志(首行出现、重新渲染完成、下载事件出现),失败时能定位到具体步号,最后一步给了兜底动作而不是无限重试。改写时优先保留这三样,长度本身不是重点。

Claude Duet 执行到一半退出——是任务拆得太粗还是页面变了?

验证页面元素是否仍然可定位

粒度改完之后还要排除页面变化。最省事的办法是在目标页面的开发者工具控制台里跑一段存在性检查,把任务里用到的关键元素逐个列出来。下面的骨架只做查询,不会点击任何东西,替换 selector 之后即可执行。

const targets = [
  { name: '导出按钮', selector: 'button[data-action="export"]' },
  { name: '状态筛选', selector: 'select[name="status"]' },
  { name: '表格首行', selector: 'table tbody tr' },
];

for (const t of targets) {
  const els = document.querySelectorAll(t.selector);
  const visible = Array.from(els).some(el => el.offsetParent !== null);
  console.log(`${t.name} | 匹配数=${els.length} | 可见=${visible}`);
}

结果判读:

  • 匹配数为 0:选择器失效,页面结构变了,需要按当前 DOM 重新取值,暂时不要动任务描述。
  • 匹配数大于 1:选择器不够唯一,容易点到错误的元素,改用更具体的属性或加限定条件。
  • 匹配数为 1 但可见为 false:元素在折叠面板、隐藏容器或尚未渲染的区块里,先确认前置的展开动作是否执行过。
  • 三项都正常:页面结构大概率没变,回到任务粒度和等待条件上排查。

这段脚本查不到 iframe 内部的节点,也看不到 shadow root 里面的元素。如果目标区域属于这两种结构,需要先切到对应的 frame 或取出 shadowRoot 再查,否则会把它误判成「页面变了」。

用单步任务做回归验证

改动之后不要直接跑全流程,先用单步任务确认每一步都能独立跑通。建议按这个顺序做:

  1. 把失败前的那一步单独写成一条任务,只做这一件事,执行一次,记录开始状态、结束状态和最后一条日志。
  2. 同一条单步任务连续再跑一次,对比两次的最后一条记录、耗时区间、是否触发重试。两次一致说明可复现;两次不一致说明存在加载时序或数据差异,不能只按粒度问题处理。
  3. 按任务顺序逐步加回后续步骤,每加一步跑一次,观察在哪一步首次出现中途退出。
  4. 全部步骤接回去之后再完整跑一次,确认能走到结束而不是提前停。

回归过程中仍然会有需要人工介入的位置,例如登录与验证码、需要审批才能触发的下载、页面弹出的二次确认框、首次访问才出现的引导层。这些位置不要硬塞进自动流程,把它写成任务里的一个暂停点,执行到那里停下来等人处理,比让执行器自己猜要稳。

如果单步都能跑通、合起来又中途退出,问题多半出在步骤之间的状态衔接上,比如上一步的渲染还没完成就执行下一步,这时该加的是等待条件,而不是继续拆细任务。