gstack /canary 深度解析:基于 browse 守护进程与基线对比的部署后可视化金丝雀监控
/canary 是 gstack(Garry's Stack)技能库中的 Post-Deploy Visual Monitor(部署后可视化金丝雀监控) 技能。它以"发布可靠性工程师(Release Reliability Engineer)"的视角,在每次发布后的关键 10 分钟窗口内,驱动 browse 无头浏览器守护进程对线上应用反复进行页面加载、截图、控制台错误与性能采样,并与部署前捕获的基线对比,把"已经发布"与"已验证可用"之间的空档补上。阅读本文后,你将掌握 /canary 的参数语义、基线建立流程、告警分级与瞬态容错规则、健康报告的产物结构,以及它在源码与测试中的底层实现依据。
该技能的权威定义位于 canary/SKILL.md,由 canary/SKILL.md.tmpl 通过 bun run gen:skill-docs(见 package.json 的 gen:skill-docs 脚本)自动生成。技能核心围绕 browse 守护进程展开,browse 的完整命令语义记录在 browse/SKILL.md 与 browse/sections/command-list.md 中。
/canary 解决的问题:CI 通过不等于线上可用
技能开头给出了一个典型的失败叙事:一个部署在 CI 中全绿,却在生产环境崩坏,原因可能是缺失的环境变量、CDN 缓存了过期静态资源、或是数据库迁移在真实数据量下比预期慢得多。这类问题的共性在于:它们在 CI 环境里根本不会被观察到,只有真实流量、真实环境变量、真实 CDN 链路才会触发。
/canary 的定位就是"shipped 到 verified 之间的安全网",把发现问题的时间窗口压缩到 前 10 分钟,而不是 10 小时。它在 docs/skills.md 的技能目录中被描述为 SRE 角色:"Post-deploy monitoring loop. Watches for console errors, performance regressions, and page failures using the browse daemon.",并在技能正文中将其明确为"post-deploy monitoring mode"。
与技能调度相关的两个环境要素也值得说明:
- 技能以斜杠命令方式唤起,用户在输入
/canary <url>时即触发本技能,frontmatter 中声明了模型需要拥有 Bash、Read、Write、Glob、AskUserQuestion 工具,版本为 1.0.0,premable 层级为 2。 - frontmatter 里的
triggers(如monitor after deploy、canary check、watch for errors post-deploy)让技能在用户以自然语言而非斜杠命令提出"监控部署""金丝雀检查"等请求时也能被正确路由。
底层引擎:browse 守护进程与技能用到的命令
/canary 本身不实现浏览器能力,它全部委托给 browse 守护进程。browse 是 gstack 的常驻无头 Chromium,第一次调用自动启动(约 3 秒),此后单条命令约 100ms,且 cookie、标签页、登录会话等状态在多次调用间保持(详见 browse/SKILL.md)。
在每次调用任何 browse 命令之前,技能执行 SETUP 检查来解析可执行文件路径 $B:优先使用仓库内的 $_ROOT/.claude/skills/gstack/browse/dist/browse,否则回退到 $HOME/.claude/skills/gstack/browse/dist/browse;两者都不可执行则输出 NEEDS_SETUP,此时需要先向用户确认执行一次性构建(约 10 秒),再运行 cd <SKILL_DIR> && ./setup。若系统缺少 bun,脚本会用固定版本 1.3.10 并校验 SHA-256 校验和后安装。
/canary 监控循环中反复出现的 $B 命令及其在命令体系中的实现位置如下:
| 命令 | 用途 | 依据 |
|---|---|---|
goto <url> |
导航到页面,超时或报错意味着页面加载失败 | 导航类命令见 browse/sections/command-list.md |
snapshot -i -a -o <png> |
输出可访问性树(-i 仅交互元素)并同时生成带标注框的截图(-a -o) |
snapshot 标志语义见 browse/sections/command-list.md |
console --errors |
仅过滤输出 console 的错误与警告,是"新控制台错误"告警的数据源 | 在 browse/src/commands.ts 中注册 |
perf |
输出页面加载耗时,是性能回归告警的数据源 | 同上 |
links |
输出全部链接为 "text → href",用于页面自动发现 | 同上 |
text |
输出清洗后的页面文本,作为内容快照 | 同上 |
browse 的许多读取类输出(text、links、console 等)会被包裹在 BEGIN/END UNTRUSTED EXTERNAL CONTENT 标记中,以防御提示注入。snapshot 的 -D(diff)、-c(compact)、-s <sel>(作用域)、-C(cursor-interactive)等标志可以自由组合,-o 仅在同时使用 -a 时生效,例如 $B snapshot -i -a -C -o /tmp/annotated.png。
命令形态与参数
/canary 支持四种参数形态,覆盖"发布前建档、发布后持续监控、单次体检"三种场景:
/canary <url>:对某 URL 做发布后 10 分钟(默认时长)的持续监控;/canary <url> --duration 5m:自定义监控时长,允许范围为 1 分钟到 30 分钟;/canary <url> --baseline:捕获基线截图,必须在部署之前运行;/canary <url> --pages /,/dashboard,/settings:显式指定要监控的页面清单,覆盖默认的自动发现;/canary <url> --quick:单次通过式健康检查,不进入持续监控循环。
未指定 --pages 时,页面列表从应用导航自动发现(见 Phase 3)。默认监控时长为 10 分钟,每次采样间隔为 60 秒。整个技能遵守 Read-only 原则:只观察与报告,除非用户明确要求介入修复,否则不修改代码。
七阶段工作流
Phase 1:Setup,建立产物目录
技能启动后先在项目内建立报告目录结构:
eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null || echo "SLUG=unknown")"
mkdir -p .gstack/canary-reports
mkdir -p .gstack/canary-reports/baselines
mkdir -p .gstack/canary-reports/screenshots
gstack-slug 用于生成当前项目的稳定 slug,失败时回退为 unknown。之后解析用户参数:默认时长 10 分钟;默认页面由应用导航自动发现。
在动手监控之前,还需要完成 Step 0 的平台与基准分支探测:通过 git remote get-url origin 判断托管平台(含 github.com 为 GitHub、含 gitlab 为 GitLab),或通过 gh auth status / glab auth status 兜底,进而用 gh pr view 或 Git 原生命令(git symbolic-ref、git rev-parse --verify origin/main 等)确定 PR 目标分支或仓库默认分支,作为后续所有比较与日志记录的上下文。
Phase 2:基线捕获(--baseline 模式)
基线是金丝雀的灵魂。 若传入 --baseline,则在部署前对每个页面(来自 --pages 或首页)采集四类证据:
$B goto <page-url>
$B snapshot -i -a -o ".gstack/canary-reports/baselines/<page-name>.png"
$B console --errors
$B perf
$B text
每个页面需收集:截图路径、console 错误数、来自 perf 的页面加载时间、文本内容快照。随后写入基线清单 .gstack/canary-reports/baseline.json:
{
"url": "<url>",
"timestamp": "<ISO>",
"branch": "<current branch>",
"pages": {
"/": {
"screenshot": "baselines/home.png",
"console_errors": 0,
"load_time_ms": 450
}
}
}
完成基线后技能立即 STOP 并明确告知用户:"Baseline captured. Deploy your changes, then run /canary <url> to monitor.",把部署动作交还给用户,保证基线永远代表"发布前的已知良好状态"。
Phase 3:页面自动发现
未显式给出 --pages 时,通过以下命令发现导航入口:
$B goto <url>
$B links
$B snapshot -i
从 links 输出中提取前 5 个站内导航链接,首页总是被包含。随后通过 AskUserQuestion 呈现页面清单,推荐选择 A(主导航目标);用户也可以追加更多页面(B),或只监控首页做快速检查(C)。从 browse/sections/command-list.md 的实现看,links 输出为 "text → href" 形态,天然适合提取导航目标。
Phase 4:部署前快照(无基线时的回退参照)
若不存在 baseline.json,技能会在部署前先做一次快速参考快照,作为后续回归检测的参照系:
$B goto <page-url>
$B snapshot -i -a -o ".gstack/canary-reports/screenshots/pre-<page-name>.png"
$B console --errors
$B perf
需要说明的是,此回退只是"参照点",强度弱于真正的基线。技能因此特意鼓励在部署前使用 --baseline:没有基线时,金丝雀退化为健康检查(health check)。
Phase 5:持续监控循环
在指定时长内每 60 秒对每个页面执行一次检查:
$B goto <page-url>
$B snapshot -i -a -o ".gstack/canary-reports/screenshots/<page-name>-<check-number>.png"
$B console --errors
$B perf
每次检查后与基线(或部署前快照)对比,按四档告警分级:
- 页面加载失败:
goto返回错误或超时,触发 CRITICAL; - 新控制台错误:出现基线中不存在的错误,触发 HIGH;
- 性能回归:加载时间超过基线的 2 倍,触发 MEDIUM;
- 坏链:出现基线中不存在的新 404,触发 LOW。
循环内置两条经验法则,防止误报与报警疲劳:
- 针对变化告警,而非绝对值(Alert on changes, not absolutes):基线里就有 3 个 console 错误的页面,只要仍然只有 3 个就算正常;多出 1 个新错误才触发告警。性能阈值同样是相对的:2 倍于基线是回归,1.5 倍可能只是正常波动。
- 不要狼来了(Don't cry wolf):只有连续 2 次及以上检查都持续出现的模式才构成告警,单次网络抖动不告警。
一旦出现 CRITICAL 或 HIGH,立即通过 AskUserQuestion 通知用户,告警卡要求包含时间、页面、类型、具体发现、截图证据路径以及基线与当前值:
CANARY ALERT
════════════
Time: [timestamp, e.g., check #3 at 180s]
Page: [page URL]
Type: [CRITICAL / HIGH / MEDIUM]
Finding: [what changed — be specific]
Evidence: [screenshot path]
Baseline: [baseline value]
Current: [current value]
用户据此在四个动作中决策:立即调查并停止监控(A)、继续监控等待下一次采样以确认是否为瞬态(B)、立刻回滚部署(C)、判为误报继续监控(D)。
Phase 6:健康报告
监控结束(或用户提前终止)后产出总结报告:
CANARY REPORT — [url]
═════════════════════
Duration: [X minutes]
Pages: [N pages monitored]
Checks: [N total checks performed]
Status: [HEALTHY / DEGRADED / BROKEN]
Per-Page Results:
─────────────────────────────────────────────────────
Page Status Errors Avg Load
/ HEALTHY 0 450ms
/dashboard DEGRADED 2 new 1200ms (was 400ms)
/settings HEALTHY 0 380ms
Alerts Fired: [N] (X critical, Y high, Z medium)
Screenshots: .gstack/canary-reports/screenshots/
VERDICT: [DEPLOY IS HEALTHY / DEPLOY HAS ISSUES — details above]
报告落盘为 .gstack/canary-reports/{date}-canary.md 与同名前缀的 .json 两份。同时把结果写入 review dashboard 的 JSONL 日志:
eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)"
mkdir -p ~/.gstack/projects/$SLUG
日志条目为 {"skill":"canary","timestamp":"<ISO>","status":"<HEALTHY/DEGRADED/BROKEN>","url":"<url>","duration_min":<N>,"alerts":<N>},按项目 slug 分目录持久化在 ~/.gstack/projects/ 下,便于跨发布追踪趋势。
Phase 7:基线滚动更新
部署健康时,技能询问用户是否用本次截图更新基线:
- A) 用当前截图更新基线(推荐:部署健康,新基线反映当前生产状态);
- B) 保留旧基线。
选择 A 后,最新截图被复制进 baselines 目录并更新 baseline.json。这一机制让基线随健康的发布滚动前进,避免基线老化到与生产严重脱节。
关键规则速查
| 规则 | 含义 |
|---|---|
| 速度优先 | 调用后 30 秒内开始监控,不要过度分析 |
| 对变化告警 | 与基线对比,而非与行业标准对比 |
| 截图即证据 | 每条告警必须附带截图路径,无例外 |
| 瞬态容忍 | 仅对连续 2+ 次检查持续出现的模式告警 |
| 基线是王道 | 无基线的金丝雀只是健康检查 |
| 阈值为相对值 | 2 倍基线为回归;1.5 倍可能是正常波动 |
| 只读原则 | 只观察和报告,不修改代码 |
与发布链路的配合:/land-and-deploy 之后的最后一道闸
/canary 在技能体系中的上游是 docs/skills.md 描述的发布链路:/ship 创建 PR,/land-and-deploy 负责合并、等 CI、执行部署并验证生产健康,/canary 则在部署完成后立即接管。docs 中给出的典型会话演进是:运行 /setup-deploy 一次性检测部署平台(Fly.io、Render、Vercel、Netlify、Heroku、GitHub Actions 或自定义)并写入配置,之后 /land-and-deploy 一条命令从"已批准"走到"已在生产验证",部署完成后用 /canary 继续盯防:
You: /canary https://myapp.com
Claude: Monitoring 8 pages every 2 minutes...
Cycle 1: ✓ All pages healthy. p95: 340ms. 0 console errors.
Cycle 2: ✓ All pages healthy. p95: 380ms. 0 console errors.
Cycle 3: ⚠ /dashboard — new console error: "TypeError: Cannot read
property 'map' of undefined" at dashboard.js:142
Screenshot saved.
Alert: 1 new console error after 3 monitoring cycles.
该文档同时建议在风险较高的发布后周期性重复运行 /canary,而不仅是部署完成后的一次性检查。
技能工程化侧面:模板生成与可校验的产物契约
从仓库结构可以观察到两个工程化细节,它们解释了为什么 /canary 的流程可以被反复执行而不漂移:
其一,SKILL.md 由模板生成。 canary/SKILL.md 文件头注释明确标注" AUTO-GENERATED from SKILL.md.tmpl do not edit directly ",需要重新生成时执行 bun run gen:skill-docs(即 scripts/gen-skill-docs.ts)。对照 canary/SKILL.md.tmpl 可见,发布流程主体(Arguments、Phase 1-7、Important Rules)保存在模板中,而 preamble、browse 环境探测({{BROWSE_SETUP}})、基准分支探测({{BASE_BRANCH_DETECT}})等共享段落以占位符形式注入,保证所有技能共享同一套运行时规范且互不漂移。
其二,工作流的产物契约被端到端测试锁定。 test/skill-e2e-deploy.test.ts 中定义了 Canary skill E2E(canary-workflow 标签):测试在一个临时 git 仓库中复制 canary 技能目录,然后以模拟提示驱动模型,要求其在没有 browse 守护进程、没有真实 URL 的前提下演示对工作流的理解,即创建 .gstack/canary-reports/ 目录结构、按 Phase 2 的 schema(url、timestamp、branch、pages 下的 screenshot / console_errors / load_time_ms)写出模拟 baseline.json、按 Phase 6 的 Health Report 格式(CANARY REPORT 头、duration、pages、status、逐页结果表、verdict)写出模拟报告。测试断言目录存在且产物文件数大于 0。这意味着 baseline.json 与健康报告的字段结构是可被测试校验的契约,任何对 Phase 2 / Phase 6 输出格式的改动都需要同步更新测试,从而防止技能文档与真实行为脱节。
写在最后:金丝雀的设计哲学
回看整个 /canary 设计,核心并不在于"截图"或"看日志"这些单个动作,而在于三组取舍:
- 对比基线而非绝对标准:生产页面本来就可能有历史遗留的 console 错误或偏慢的加载,绝对阈值只会产生噪音;只有"相对基线的变化"才与"这次部署引入了什么"直接相关。
- 用截图锁定证据:告警消息不携带抽象描述,而是携带可复查的截图路径,任何告警都能被人工回溯确认。
- 瞬态与持续的区分:网络抖动在发布后 10 分钟窗口内几乎必然出现,只有跨越两次采样仍然存在的异常才值得打断用户。
如果你的部署流程目前只依赖 CI 绿灯,/canary 提供了一种低成本补齐生产验证的手段:部署前一条 --baseline 建档,发布后一条 /canary <url> 盯防,即可把"发布后前 10 分钟"从无人区变成可观测、可告警、可回滚决策的受控窗口。技能完整定义见 canary/SKILL.md,browse 命令全量参考见 browse/sections/command-list.md,端到端契约测试见 test/skill-e2e-deploy.test.ts。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00