Post-mortem 0003: Web agent validated a replacement server instead of its current GUI

Status: resolved

Executive summary

A Web agent changed the GUI source but did not know which URL and process hosted its session. It delegated acceptance to the user, then treated a bare Vite HTTP 200 as success despite a missing window.__DSH_BOOT__ white screen, and finally validated a replacement dsh web server on another port while the original page had already picked up rebuilt artifacts. The fix makes the current URL and runtime mode model-visible and shell-queryable, rejects standalone Vite before listen, and verifies production refresh and development HMR against external state.

Summary

The session ran inside the DeepSeek Harness Web GUI at port 3081 while its selected Workspace was an empty test/ directory. The model request named neither the GUI nor its source checkout, URL, process, or update mode. Repository affordances exposed apps/web with a Vite development script, while the full browser composition lived behind dsh web.

The resulting actions were individually plausible but did not share one acceptance target. A source edit, a successful build, an HTTP 200, an injected boot manifest, and the user's existing page were treated as interchangeable facts.

The evidence source is the persisted event log for session-3eb796c2-5159-4686-affe-df8719f6f987, whose header records cwd /Users/tn.shen/Documents/deepseek-harness-gui-master/test. Its initial request header is sequence 6; the user-facing handoff, bare-Vite launch, replacement-host launch, boot-manifest probe, and first 3081 process probe are sequences 30939, 31865, 34309, 34441, and 34681 respectively. The timeline below follows those events rather than reconstructing intent from the later report.

Impact

The user had to identify three consecutive mistakes: acceptance was delegated back to them; the proposed preview was a blank page; and the reported successful URL was not the page they were using. An unmanaged replacement server also outlived the turn until the user challenged it.

No change in this investigation restarted or modified the read-only 3081 and 3082 trial services.

Timeline

  • In turn 2, after editing the theme, the agent's sequence-30939 message told the user to run pnpm run demo:tui or open an unspecified Web application. It ran no assembled Web acceptance.
  • In turn 3, the agent read apps/web/package.json, launched bare Vite on port 5173 at sequence 31865, observed HTTP 200, and declared success. The browser instead threw client-modules: window.__DSH_BOOT__ is missing or not an object and rendered a white page.
  • In turn 4, the agent found the full dsh web path, rebuilt the shell, launched an unmanaged process on port 3334 at sequence 34309, and checked only that this replacement returned 200 with a boot manifest at sequence 34441. It never probed port 3081.
  • In turn 5, the user reported at sequence 34556 that 3081 already showed the new theme. Only then, at sequence 34681, did the agent inspect the existing process and remove the redundant server.

Root cause

The Web assembly had no model-visible identity for the current GUI, canonical URL, or runtime mode. The session cwd correctly identified the user's selected Workspace, but the model treated that project directory as the application directory. No durable record related the GUI source checkout, built artifacts, serving process, target origin, and browser acceptance.

The wrong startup path looked legitimate because bare Vite returned HTTP 200. window.__DSH_BOOT__ is injected only by the full host, so transport readiness did not imply application readiness. The first regression test repeated this mistake in another form: a timeout killed Vite and satisfied a nonzero-exit assertion. Live reproduction exposed that false positive.

Background process semantics were also bypassed with shell &, so job identity, completion notices, collection, and cleanup did not apply. Verifying port 3334 therefore proved only that a second service worked.

Guardrails added

  • The Web launcher publishes the canonical loopback URL and actual production/development mode in the logged app:web-surface prompt section and managed $DSH_WEB_URL/$DSH_WEB_MODE environment.
  • Production guidance requires rebuilding artifacts and verifying the existing URL after refresh. Development guidance explains that dsh web --dev mounts only the HMR receiver; pnpm run dev:web in the same checkout must also rebuild client-plugin bundles, while shell and plain-package changes still require refresh.
  • apps/web standalone Vite serve mode rejects during configuration. Its subprocess test proves natural exit and instruments Server.listen() so a transient bind cannot pass unnoticed.
  • Layered real-path tests cover the CLI request, exact production/development prompts, shell runtime facts, same-port static replacement, source watcher rebuild, host stat polling, and browser HMR under an unchanged page identity.
  • PR evidence preserves screenshots from the original 3081 session and a real-model before/after GUI run; external browser, HTTP, process, and session-log observations carry acceptance.

Lessons

  • The agent must know hidden runtime prerequisites before it can guide the user; startup mode is application context, not tribal knowledge.
  • HTTP readiness, build success, and a boot manifest are different facts. Acceptance names the exact origin and externally observes the requested change there.
  • A replacement service cannot prove that an existing page changed. Long-running processes use managed task lifecycles when they are actually requested.
  • A regression test must be able to fail for the reported mechanism. Process timeout is not equivalent to fail-fast, and post-exit port availability does not prove the port was never bound.

事故复盘(postmortem) 0003:Web agent(智能体)验收了替代服务器,而非其当前 GUI

状态:已解决

摘要

Web agent 修改了 GUI 源码,却不知道当前会话对应哪个 URL、由哪个进程承载。它把验收交还给用户,随后在 window.__DSH_BOOT__ 缺失导致白屏的情况下,仍把裸 Vite 返回的 HTTP 200 当作成功;最后,原页面其实已经加载了重建产物,它却去验收另一个端口上的替代 dsh web 服务器。修复让当前 URL 和运行模式对模型可见且可由 shell 查询,在独立 Vite 开始监听前拒绝启动,并依据外部状态验收生产模式刷新与开发模式 HMR(热模块替换)。

概述

该会话运行在端口 3081 的 DeepSeek Harness Web GUI 中,而用户选择的 Workspace 是空的 test/ 目录。模型请求既未指明该 GUI,也未提供它的源码检出目录、URL、进程或更新模式。仓库在 apps/web 中提供了 Vite 开发脚本,完整的浏览器组合则由 dsh web 提供。

由此产生的各个动作单看都合理,却没有指向同一个验收目标。源码修改、成功构建、HTTP 200、注入的启动 manifest(元数据清单)和用户原本打开的页面,被当成了可以相互替代的事实。

证据源是 session-3eb796c2-5159-4686-affe-df8719f6f987 的持久化事件日志,其头部记录的 cwd 为 /Users/tn.shen/Documents/deepseek-harness-gui-master/test。初始请求头位于序列 6;面向用户的交接、裸 Vite 启动、替代宿主启动、启动 manifest 探测,以及首次探测 3081 进程,分别位于序列 30939、31865、34309、34441 和 34681。下方时间线以这些事件为依据,而不是根据后续报告反推意图。

影响

用户不得不连续指出三个错误:agent 把验收交还给用户;建议预览的页面一片空白;报告成功的 URL 并不是用户正在使用的页面。一个不受管理的替代服务器还持续运行到下一个轮次,直到用户提出质疑。

本次调查没有重启或修改只读的 3081 和 3082 试验服务。

时间线

  • 在第 2 个轮次中,agent 修改主题后,在序列 30939 的消息中让用户运行 pnpm run demo:tui 或打开一个未明确指定的 Web 应用。它没有对组装后的 Web 应用执行任何验收。
  • 在第 3 个轮次中,agent 读取 apps/web/package.json,在序列 31865 于端口 5173 上启动裸 Vite,观察到 HTTP 200 后便宣布成功。浏览器却抛出 client-modules: window.__DSH_BOOT__ is missing or not an object,并显示白屏。
  • 在第 4 个轮次中,agent 找到了完整的 dsh web 启动路径,重新构建 shell,在序列 34309 于端口 3334 上启动一个不受管理的进程,并且只在序列 34441 检查了这个替代服务是否返回 200 和启动 manifest。它从未探测端口 3081。
  • 在第 5 个轮次中,用户在序列 34556 报告 3081 已经显示新主题。直到序列 34681,agent 才检查既有进程并移除冗余服务器。

根因

Web 组合没有向模型提供当前 GUI、规范 URL 或运行模式的身份信息。会话 cwd 正确标识了用户选择的 Workspace,但模型把这个项目目录当成了应用目录。系统也没有持久记录将 GUI 源码检出目录、构建产物、服务进程、目标 origin 和浏览器验收关联起来。

裸 Vite 返回 HTTP 200,使错误的启动路径看似合理。window.__DSH_BOOT__ 只由完整宿主注入,因此传输层就绪不代表应用已就绪。首个回归测试以另一种方式重复了同样的错误:超时机制终止 Vite 后,非零退出断言仍会通过。真实复现暴露了这一误报。

agent 还通过 shell & 绕过了后台进程语义,因此任务身份、完成通知、结果收集和清理机制均未生效。验证端口 3334 只能证明第二个服务可以工作。

已添加的防护措施

  • Web 启动器在记录到日志的 app:web-surface 提示词区段,以及受管的 $DSH_WEB_URL/$DSH_WEB_MODE 环境变量中,发布规范环回 URL 和实际的生产/开发模式。
  • 生产模式指南要求重新构建产物,并在刷新后验证既有 URL。开发模式指南说明,dsh web --dev 只挂载 HMR 接收端;同一源码检出目录中的 pnpm run dev:web 还必须重新构建客户端插件 bundle,而 Web shell 和普通包的改动仍然需要刷新页面。
  • apps/web 的独立 Vite 服务模式会在配置阶段拒绝启动。其子进程测试验证进程自然退出,并插桩 Server.listen(),确保短暂绑定端口也不会漏检。
  • 分层的真实路径测试覆盖 CLI(命令行界面)请求、精确的生产/开发模式提示词、shell 运行时事实、同端口静态产物替换、源码 watcher 重建、宿主 stat 轮询,以及页面 identity 不变的浏览器 HMR。
  • PR(Pull Request)证据保留了原始 3081 会话的截图,以及真实模型驱动的 GUI 修改前后对比;验收以外部浏览器、HTTP、进程和会话日志的观测结果为准。

教训

  • agent 必须先知道隐藏的运行时前置条件,才能指导用户;启动模式属于应用上下文,不应依赖团队口口相传。
  • HTTP 就绪、构建成功和启动 manifest 是不同的事实。验收必须明确指定确切的 origin,并从外部观察所请求的改动是否在该 origin 生效。
  • 替代服务无法证明既有页面已经改变。确实收到启动长时间运行进程的请求时,应使用受管任务生命周期。
  • 回归测试必须能够针对所报告的机制失败。进程超时不等同于快速失败,进程退出后端口可用也不能证明该端口从未被绑定。