---
name: unidesk-gc
description: >-
  UniDesk 磁盘 GC 与主机压力处置流程。用于 `bun scripts/cli.ts gc ...`、
  根盘高水位、worktree/缓存/日志清理、Web observe/Chrome 内存压力和安全停止判定。
---

# UniDesk GC

- 处理 UniDesk 磁盘或内存压力时，优先使用受控 UniDesk CLI。
- 到达保护边界后停止，不得扩大为临时 `rm -rf`、Docker prune、数据库清理、原生 Kubernetes 删除或运行时状态删除。

- 长期规则以 `docs/reference/gc.md` 为准。
- 执行远端 GC、k3s/PVC 归因、Web observe/Chrome 压力处置、registry/CI 留存或安全停止判定前，必须先读该文档。

## 本机流程

先执行只读归因：

```bash
df -h /
df -BG /
bun scripts/cli.ts gc plan --target-use-percent 69 --limit 50
```

默认计划存在缺口时，显式启用候选类别：

```bash
bun scripts/cli.ts gc plan --target-use-percent 69 --limit 2000 \
  --include-tool-caches \
  --include-stale-tmp \
  --include-vscode-stale-servers \
  --include-vscode-stale-extensions \
  --include-vscode-cached-vsix \
  --include-baidu-staging \
  --include-state-artifacts \
  --include-state-stale-scratch \
  --include-codex-sessions \
  --include-merged-worktrees \
  --include-vpn-diagnostic-logs
```

复核计划后，只执行相同候选范围：

```bash
bun scripts/cli.ts gc run --confirm --target-use-percent 69 --limit 2000 \
  --include-tool-caches \
  --include-stale-tmp \
  --include-vscode-stale-servers \
  --include-vscode-stale-extensions \
  --include-vscode-cached-vsix \
  --include-baidu-staging \
  --include-state-artifacts \
  --include-state-stale-scratch \
  --include-codex-sessions \
  --include-merged-worktrees \
  --include-vpn-diagnostic-logs
```

worktree 因 merge/cherry 超时而受保护时，只提高 worktree 临时预算：

```bash
bun scripts/cli.ts gc plan --target-use-percent 69 --limit 2000 \
  --include-merged-worktrees \
  --worktree-scan-budget-ms 120000 \
  --worktree-cherry-check-timeout-ms 10000 \
  --no-file-logs --no-docker-logs --no-journal --no-build-cache --no-tmp --no-db-summary
```

- 只对计划仍显示的候选执行匹配的 `run --confirm`。
- dirty、近期、活跃、未合并或超时保护的 worktree 必须继续受保护。

## 缓存与日志

单独检查 Docker 镜像候选：

```bash
bun scripts/cli.ts server cleanup plan --min-age-hours 24 --limit 80
```

- 返回零个过期镜像候选时，不得使用 `docker image prune` 或 `docker system prune`。
- 受保护镜像可能仍是当前运行或回滚真相。

默认 BuildKit 清理可能因缓存均较新而实际释放 `0B`。只有明确止压时才使用 `--build-cache-all`：

```bash
bun scripts/cli.ts gc plan --target-use-percent 69 --build-cache-all --limit 50 \
  --no-file-logs --no-docker-logs --no-journal --no-tmp --no-db-summary
bun scripts/cli.ts gc run --confirm --target-use-percent 69 --build-cache-all --limit 50 \
  --no-file-logs --no-docker-logs --no-journal --no-tmp --no-db-summary
```

Journald 上限也通过同一 CLI 管理：

```bash
bun scripts/cli.ts gc plan --target-use-percent 69 --journal-target-size 128M --limit 50 \
  --no-file-logs --no-docker-logs --no-build-cache --no-tmp --no-db-summary
```

计划可接受时，使用参数完全匹配的 `run --confirm`。

## 临时转储

- `/tmp/unidesk-cli-output` 保存超大 JSON/stdout。
- 截断的 plan/run 会持续写入新转储，因此 GC 诊断时可能增长到 GiB 级。
- 提取必要证据并确认没有活跃写入者后，可以删除该目录：

```bash
fuser -v /tmp/unidesk-cli-output 2>&1 || true
rm -rf -- /tmp/unidesk-cli-output
```

重复出现时应补受控 CLI 留存策略，不得把手工删除变成常规入口。

- 其他 `/tmp` 目录先检查大小、mtime 和活跃 fd。
- 未明确归属和可重建性时，不得删除可能属于并行任务的当日 source/workspace scratch。

## 远端节点

- provider host 统一使用 `bun scripts/cli.ts gc remote <providerId> ...`。
- 远端长任务必须异步执行，并用 `status --job-id` 短查询，不得维持长 SSH 会话。

### 已合并 worktree

先读取 YAML 策略并只生成计划：

```bash
bun scripts/cli.ts gc remote <providerId> merged-worktrees plan
```

- 仅枚举 Git 登记且位于 `workspaceRoots` 的 worktree；
- dirty、活跃、近期、未被 `baseRef` 吸收、cherry 检查超时和主 worktree 必须显示为 protected；
- 手动入口与 policy timer 复用同一筛选器，批量上限分别读取 YAML 的手动和自动字段；
- 执行前逐项重新计划，只允许 `git worktree remove`，不得删除 branch 或使用 `rm -rf`。

复核候选后才允许执行：

```bash
bun scripts/cli.ts gc remote <providerId> merged-worktrees run --confirm
```

周期 stage 通过以下入口核对 YAML 字段、stage 和 `inSync`：

```bash
bun scripts/cli.ts gc remote <providerId> policy plan
bun scripts/cli.ts gc remote <providerId> policy status
```

### 内存分布与 Kubernetes 留存

先用紧凑 overview 归因真实内存压力：

```bash
bun scripts/cli.ts gc remote <providerId> memory-distribution
```

- 默认一次输出物理内存、PSS、进程 swap、cgroup、zombie 和 Kubernetes 对象分布；
- 同一结果必须直接披露 `processes.topSwap`，以及 zombie 父进程的 Kubernetes Pod 归属、
  泄露分类和处置类别，避免额外执行 `ps`、cgroup 映射和 `kubectl` 拼接；
- swap 单独披露，不能计入 WebProbe 启动资格；
- 需要完整明细时才使用 `--full`。

发现大量 zombie 时不得尝试终止 zombie。先按 overview 的 owner 定位长期父进程，
修复子进程回收路径，再通过正常自动交付滚动 owner。运行面重启只能清除既有 zombie，
不能替代源码修复。

Kubernetes 历史对象手动回收固定使用：

```bash
bun scripts/cli.ts gc remote <providerId> retention plan --limit 500
bun scripts/cli.ts gc remote <providerId> retention run --confirm --limit 500
bun scripts/cli.ts gc remote <providerId> status --job-id <id>
```

- 筛选、保留窗口、分组、批量和级联策略只来自目标节点的 `kubernetesObjectRetention` YAML；
- 多 namespace 必须由共享 `defaults` 与显式 `policies` 表达：
  - 一个 `retention plan` 聚合显示全部 policy；
  - 每个 policy 使用唯一 `configId` 和不会随 commit 变化的稳定 owner 分组标签；
  - CLI `--limit` 是每个 policy 的上限，并继续受对应 YAML 单批上限约束；
  - 根级 `aggregatePreviewLimit` 控制聚合样例数，默认 overview 不得依赖第二次 `--full` 才能判断。
- 手动入口与 `policyTimer.includeKubernetesObjectRetention` 自动阶段必须复用同一筛选器；
- 活跃、过新、未知分组、时间不可解析、扫描截断和 Table 形状漂移全部保护；
- 只删除 YAML 指定的根对象，通过 ownerRef 级联，不直接删除下游 TaskRun、Pod、Secret 或 Event；
- 自动回收由 `gc remote <providerId> policy plan|install --confirm|status` 管理，周期和单批上限由 YAML 控制。
- `policy status` 必须一次披露期望与安装态的配置/runner 指纹、policy 数量、各 namespace 关键留存字段和 `inSync`；
- owning YAML 变化后只通过 `policy install --confirm` 收敛安装态，禁止直接修改 host JSON、runner 或 systemd unit。

## WebProbe 低内存人工处置

- 唯一资格指标：
  - 只读取 `/proc/meminfo` 的 `MemAvailable`；
  - 输出必须披露 `metric=MemAvailable`、`source=/proc/meminfo` 和 `swapExcluded=true`；
  - swap 只作为独立压力信号，绝不计入启动可用量；
  - 启动门槛和比较语义只读取 `config/hwlab-node-lanes.yaml#templates.hwlabV03.webProbeWorkbench.resourcePolicy.memoryStartGuard`；
  - 文档、skill、源码和测试不得复制 `thresholdBytes` 的当前数值；
  - 除非用户明确授权修改 WebProbe 启动门槛，否则不得改变 owning YAML 的 `thresholdBytes`。
- 人工处置命令必须按顺序执行：

```bash
bun scripts/cli.ts gc remote <node> plan --memory-pressure-only
bun scripts/cli.ts gc remote <node> run --confirm --memory-pressure-only
bun scripts/cli.ts gc remote <node> status --job-id <id>
bun scripts/cli.ts gc remote <node> memory
```

- 顺序约束：
  - 默认 scoped plan 必须直接披露 YAML 限量候选摘要和 `runEligibility`，一次输出即可判断；
  - 只有 `runEligibility.allowed=true` 时才允许执行明确标记安全的内存压力候选；
  - `--full` 只用于下钻完整身份明细，不得作为 scoped run 的默认前置步骤；
  - `run` 返回 job id 后，重复执行 `status --job-id <id>` 直到终态；
  - job 未终态时不得提前执行 `memory` 或重试 WebProbe；
  - 终态后执行 `memory`，只有不再命中 owning YAML 的 `manualComparator` 时才可重试人工启动；
  - 内存处置不得改用未带 `--memory-pressure-only` 的通用 plan/run。
- sentinel cadence：
  - 命中 owning YAML 的 `sentinelComparator` 时返回 `skipped-wait-next-round`；
  - 不创建 observer、不启动 Chromium、不自动执行 GC；
  - 等待下一轮 cadence 重新读取 `MemAvailable`。

以下远端场景必须先读 `docs/reference/gc.md`：

- G14 registry/CI workspace 留存、k3s/PVC 归因和安全停止判定；
- PK01 pikanode 临时目录留存与 Docker provider 安全边界；
- JD01 k3s/PVC 归因，以及 NC01/JD01 Web observe artifact、Chrome/observer 内存增长与 YAML-first 真相源核对。

## Web observe 与 Chrome 逃逸进程

- 正常生命周期：
  - 远端节点的 Chrome 内存增长应先按 observer 生命周期问题处理；
  - sentinel/quick-verify 的全部终态必须停止对应 observer；
  - runner 的 TTL、maxSamples 和 artifact cap 必须来自 YAML；
  - 归属尚未判清时不得按名称终止 Chrome，也不得删除 web-observe 目录。
- 保护边界：
  - active observer、受控任务仍引用的浏览器会话及其完整进程树始终受保护。
- 只有独立且无归属的 Playwright `cliDaemon`/Chrome 进程树才可进入精确终止：
  - 存活时间超过 owning YAML 定义的异常存活窗口；
  - 受控 observe/sentinel 状态未显示该进程树归属于 active observer 或在途受控任务；
  - manifest、heartbeat、PID/session 记录均不能把它归属到有效 run；
  - `PID`、`PPID`、`SID`、`startTicks` 与后代进程能闭合为唯一身份；
  - 任一证据缺失或存在歧义时继续标记 protected。
- 精确终止：
  - TERM 前重新核对 `PID/PPID/SID/startTicks`，防止 PID 复用；
  - 只向已核实的唯一进程树发送 TERM；
  - 按 YAML 定义的有界等待时间重新枚举；
  - 仍未退出时，只以不可变的 `PID/startTicks` 复核残留身份；
  - 同时披露原始与当前 `PPID/SID`，允许 TERM 后重挂载但不放宽 PID 复用保护；
  - 只对复核后的同一残留进程树发送 KILL。
- 禁止批量终止：
  - 不得使用 `pkill -f chrome`、`killall chrome` 或其他按进程名批量终止方式；
  - 证据不完整或归属存在歧义时保持 protected，不得猜测。
- 终止后验证：
  - 确认目标 PID、后代 PID 和 session/process group 全空；
  - 确认 active observer 未受影响；
  - 重新采集 `/proc/meminfo` 的 `MemAvailable`、独立 swap 信号、observer 状态和 Chrome/runner 摘要；
  - 结构化记录判定证据、TERM/KILL 结果、残留进程与终止前后资源数据。

JD01 等远端节点只能显式启用可重建工具缓存：

```bash
bun scripts/cli.ts gc remote JD01 plan --target-use-percent 49 --include-tool-caches --limit 100
bun scripts/cli.ts gc remote JD01 run --confirm --target-use-percent 49 --include-tool-caches --limit 100
```

- 必须先复核 plan。
- 候选仅限固定 allowlist 的 npm/npx/Bun 缓存。
- 不得改用 `rm -rf ~/.npm ~/.bun`，也不得触碰 `node_modules`、auth/config、k3s/containerd、PVC 或 Docker volume。

## 保护边界

以下对象不得作为通用磁盘止压目标：

- `docker system prune`、`docker image prune`、Docker/Compose volume 删除；
- 未经 `gc db-trace`、备份和维护窗口的 PostgreSQL PGDATA/trace 清理；
- `/var/lib/containerd`、`/var/lib/rancher/k3s`、`/var/lib/kubelet`、PVC、registry blob、runtime snapshot；
- Codex auth/config/profile；session 仅能通过 `--include-codex-sessions` 清理，活跃 SQLite 日志还需 `fuser` 与专门留存决策；
- 以下 Web observe 与 Chrome 对象：
  - active run 和 live observer runner；
  - 归属或 stale 分类未闭合的 Chrome process tree；
  - 缺少 manifest/heartbeat/pid/open-fd 证据的 web-observe state root；
  - 只有按“Web observe 与 Chrome 逃逸进程”完成无归属判定的唯一进程树才可精确终止。
- dirty、active、unmerged、recent 或 timeout-protected worktree；
- 用户未明确要求时的 `backend-core` rebuild/restart/replacement。

- 低风险候选执行后仍为 `summary.target.safeStop=true` 时，停止并报告剩余压力源与决策选项。
- 不得绕过 CLI 追求目标百分比。

## 验证

使用以下有界证据收口：

```bash
df -h /
df -BG /
docker system df
du -sh /root/unidesk/.worktree /root/unidesk/.state /tmp /var/log 2>/dev/null || true
```

- 远端内存/Chrome 压力还需记录 `free -h`、Chrome/observer 摘要与 web-observe state 大小。
- 总结起止 `df` 百分比、主要清理类别、受保护失败和剩余高风险压力源。
