AgentMeshOS CI/CD Runner 整改后续计划
📌 结论摘要
本计划承接早期 CI/CD Runner 整改结果,当前技术目标已基本完成,但 2026-09-28 又重新暴露了 101 Runner 资源隔离不足 的系统性问题。依据是内核全局 OOM、systemd oom-kill 记录和 Docs Run 36353513155 的 build 失败;当时的直接触发进程是异常膨胀到约 7.2 GiB 的 grep,旧 Runner ID 6 的禁用则是为清理孤儿监听器而采取的临时隔离动作,不是永久取消双 Runner 能力。当前已把双 Runner 恢复做成“资源阈值准入”——Runner 各 4 GiB 硬上限、双实例共享 10 GiB 总上限,只有 101 内存、swap、PSI、负载和磁盘同时达到安全线时才允许第二实例接单;下一步必须完成真实双 Job 并发验收。
🧭 目录
🎯 计划目的
本计划承接 agentmeshos-ci-cd-runner-integration-plan.md,只处理截至 2026-09-01 仍未完成、未获得放行或被外部权限阻塞的事项。已完成的 AgentMeshOS Organization Runner 真实调度、四个无状态服务发布、节点职责/API/Console/Status、Release bundle、发布锁/告警、RR 恢复演练与容量边界不再重复实施;后续仅按固定验收规则持续复核。
📊 当前状态审计
| 项目 |
状态 |
证据与判定 |
| AgentMeshOS Organization Runner |
已完成,持续复核 |
pcdell-101-agentmeshos 已真实执行构建/测试/Release/部署,在线且保留 GitHub-hosted fallback。 |
| AgentMeshOS 无状态 CD |
已完成,持续复核 |
Docs、Console、Status、API 已有同 SHA 镜像、健康检查、Release 资产、回滚和告警证据。 |
| API/Console/Status 节点职责 |
已完成 |
101/RR/103/jj 角色字段和职责筛选已有实现与远端验收证据。 |
| RR 单节点恢复与容量 |
基本完成,需持续记录 |
DETACH-09 已验证恢复顺序;/backup 是 RR 系统盘,101 独立备份盘需每次写入前继续 findmnt 核验;容量归档策略已固化但尚未形成长期执行记录。 |
| Auth Site 业务门禁 |
已完成,本轮不接入自动升级 |
专用测试账户已完成本地密码登录/会话/账户页/登出、双会话失效、旧密码拒绝、新密码恢复、管理员重置及原密码恢复;API Key 登录链路亦已验证。 |
| AI Runtime Stateful 门禁 |
已完成门禁与恢复演练,未自动放行 |
生产库/恢复副本读取、边界和一致性证据已具备;按既定决策继续人工发布,不接入生产自动升级。 |
| CLI Agent Gateway 门禁 |
已完成门禁与独立回滚,未自动放行 |
安全边界、真实短对话、独立 rollback 已验证;按既定决策继续人工发布,不接入生产自动升级。 |
| 其他项目共享 Runner 接入 |
已完成首轮迁移与并发构建验收 |
五个仓库已由 GitHub 服务端保真转移至 agentmeshos/*;通用 Docker/J5/Encoding/CI Workflow 已采用 AGENTMESHOS_RUNNER_LABEL 并显式回退 ubuntu-latest,首轮完整 SHA 构建全部成功。 |
| 101 Runner 资源隔离 |
已完成修复,等待真实并发复核 |
2026-09-28 的 Docs 失败并非文档门禁失败,而是主机全局 OOM 打断了 Runner;临时收敛单实例后,Runner 2 已由新 Docs Run 正向验证。现已增加双 Runner cgroup 总上限、单实例上限和资源阈值准入,恢复 Runner ID 6;真实双 Job 并发结果以本节“Runner 双实例资源门禁与并发恢复”验收记录为准。 |
| Runner 丢失 Job 边界恢复 |
已实现并完成发布复核 |
上游 actions/runner 2.337.0 存在 #4728:Busy long-poll 在 Job 完成切换时被取消,可能丢下一个 Job;新增只匹配 runner_id=0、零步骤和明确注解的托管 Runner 重跑控制器。提交 7b46e77 的 Repository Guardrails Run 36355662477 与 Docs Run 36355662676 已全部成功。 |
| Runner 悬挂防复发 |
已实装并完成本地与现场负向验证 |
2026-09-30 的现场日志证明 Runner 进程可在 online/idle 状态下不再领取已排队 Job;systemd Restart=on-failure 无法识别这种“进程活着但轮询失效”的状态。新增只读判定加定向重启的 watchdog,2026-10-01 安装为 agentmeshos-runner-stall-watchdog.timer。 |
🧩 执行前置与禁止事项
- Auth 只接受用户明确授权的专用生产测试账户窗口;不得从生产文件猜密码、创建未授权账户或把 API Key 结果冒充本地密码门禁。
- 多项目迁移必须先取得原个人仓库的读取/转移权限,或由用户书面批准以 101 独立盘备份建立“无历史恢复基线”。在前置条件满足前,不修改原项目远端、不覆盖未提交工作树、不向空组织仓库推送。
- 所有跨节点交换遵守小文件控制链路例外;镜像、安装包、构建产物和业务数据不得经 Tailnet/SSH 中转。
- 新增证据、脚本和临时输出分别写入版本化目录或项目
temp/;凭据不进入仓库、日志和计划正文。
🧪 2026-09-01 执行记录
bash scripts/ci/stateful-release-gate.sh auth-site 已重新执行并返回 stateful_release_gate=passed service=auth-site。该结果只证明静态快照、SQLite 完整性、权限和镜像门禁通过。
bash scripts/ci/auth-site-smoke.sh 在获得专用 test 账户窗口后已通过本地密码登录、会话、账户页、登出和登出后会话拒绝。后续可逆测试还验证双会话失效、旧密码拒绝、新密码恢复、管理员重置后会话失效;结束时已确认原密码恢复可登录,未保存任何密码或 Cookie。
- 多项目只读复核:
OwlMonitor_app、pose-radar-ti、owlFront_mock 仍有未提交工作树且远端为 hhtbing/*;ota 的 HEAD 指向不可解析对象;AWRL6844EVM_J5_link_WT99P4C5-S1_J4 当前目录没有 .git。这些项目均未被改写或推送。
- GitHub 迁移复核:先确认
/srv/codex-hub-backup/project-runner-migration-20260831T175216Z 位于 /dev/sda1;随后将五个空的 agentmeshos/* 目标仓库改名为日期占位仓,并使用 GitHub 服务端转移保留原个人仓库历史、分支和标签。此前写入 401 由命令中 token 字符误录引起,已用正确凭据复核并更正,不作为权限结论。
- 本地映射:
OwlMonitor_app、ota、pose-radar-ti、owlFront_mock 的 origin 已更新到对应 agentmeshos/*,未修改其工作树或索引;当前 AWRL 工作目录及其备份副本均无法识别为 Git 工作树,但已转移的远端 main 提交可读,未强行重建本地仓库。
- 并发 Runner:101 现有
pcdell-101-agentmeshos 外新增独立 pcdell-101-agentmeshos-2(Runner 2.337.0、独立凭据和 _work、同一 github-runner 最小权限用户);官方 svc.sh 生成并启用 systemd 服务。两实例均已在 GitHub 显示 online,并在首轮队列中同时执行 Job。
- 首轮多项目构建验收:
OwlMonitor_app 1d4b61acb627cdf4a66a948875797fb570ac3a41(Run 33533458076)、ota 9acba57dbeaa1c6e33bb952141069dda1004a380(Run 33533466198)、pose-radar-ti fa1dc895139780af2f05ce2ceab456f56dc67654(Run 33533473516)、owlFront_mock ba54d960cbe90da1e9b275ca6ede13c0d905269b(Run 33533559886)和 AWRL6844EVM_J5_link_WT99P4C5-S1_J4 4e72912a1f616ab413c21b15f4d8a10ea1c6955e(Run 33533490006)均为 success;每个 Run 的目标构建 Job 均成功。
- Auth 本地密码业务门禁已完成:专用
test 账户的登录、账户页、登出、双会话失效、旧密码拒绝、新密码恢复、管理员重置后会话失效和原密码恢复均通过;本轮不接入生产自动升级。
- 服务器端迁移已完成:五个仓库现在均归属
agentmeshos/*,默认分支为 main,远端提交与原仓库对应;四个可识别本地工作区的 origin 已更新到组织远端,AWRL 本地目录继续保留为非 Git 工作树事实。
- 并发验收已完成:101 资源复核为 8 vCPU、约 12 GiB 可用内存、约 389 GiB 可用磁盘;新增
pcdell-101-agentmeshos-2 后,两个 Runner 均显示 online,首轮项目构建出现两个同时 busy 的实例,随后全部恢复 online/idle。第二实例使用独立 /opt/agentmeshos-actions-runner-2、官方 svc.sh systemd 服务、独立 _work 和同一 github-runner 最小权限用户。
- 并发构建结果:Owl、OTA、pose-radar、owlFront、AWRL 的目标 Workflow 均成功;Owl/OTA/pose-radar/AWRL 的首轮构建与 owlFront 手动构建均被完整 SHA 监控,owlFront 与 pose-radar 的通用 Encoding Check 也成功。平台专用 Android/Google Play/Windows/GUI 等 Workflow 仍保留托管 Runner。
- Swap 复核:101 的
/swap.img 为 4 GiB,swappiness=60,MemAvailable 约 12 GiB;复核期间 si/so=0、memory PSI 为 0、无 OOM 记录。4 GiB 作为 OOM 缓冲暂保持不变;第二 Runner 上线后仍有约 10 GiB 可用内存,无需在构建窗口调整 swap。
- 并发策略复核:单个 Runner 实例一次执行一个 Job;新增第二实例后两个
agentmeshos-ci Job 可同时在 101 执行。GitHub Job 仍以 runs-on 标签、Runner Group 和可用实例共同决定调度,平台专用 Workflow 继续显式使用托管 Runner。
- 迁移与并发最终验收:五个组织仓库均有非空
main(默认分支和仓库规模已通过 API 复核);目标构建 Run 33533458076、33533466198、33533473516、33533559886、33533490006 均为 success;通用检查 Run 33539326228、33539333051、33539346683、33539348376 均为 success。两个 Runner 当前均为 online/idle。
- AgentMeshOS 计划更新提交
2e796972bd704844d9d712787c5330a2b78c3bc2 的 Docs Run 33540121351 已完成 build、Release bundle、RR deploy 和健康验证,结论为 success;当时 GitHub 报告的第三方 Action Node 20 弃用警告已关闭:官方 Actions 已统一升级到 Node 24 运行时的当前主版本,提交 4acfa1e1596f2d8d45256f5fbb144307ad185290 的 9 个相关 Workflow 已全部成功,首次 CLI Gateway BuildKit 瞬时连接中断已在同一 SHA 的失败 Job 重跑后成功覆盖,完成日志未再出现 Node 20 弃用警告。
🔧 2026-09-28 Runner OOM 处置
| 项目 |
证据 |
判定与动作 |
| 直接触发 |
06:07:34 CST 内核全局 OOM;session-2135.scope 内的 grep 进程匿名 RSS 约 7.2 GiB |
主机内存耗尽由失控制的诊断进程触发,不是文档构建本身的静态门禁失败。异常进程已退出,未保留业务数据风险。 |
| 受影响对象 |
actions.runner.agentmeshos.pcdell-101-agentmeshos-2.service 结果 oom-kill;Docs Run 36353513155 的 build 失败,后续 deploy 被跳过 |
Runner 属于受害者而非根因;必须恢复构建并重跑同一提交的 Docs 工作流。 |
| 遗留进程 |
单元失败后 RunnerService.js 与 Runner.Listener 仍存活,服务 cgroup 已不能可靠回收这些进程 |
先终止遗留进程并临时禁用旧单元,避免它继续接单或占据同一资源边界;该动作属于隔离,不是对 Runner ID 6 的永久删除。 |
| 防复发 |
活动单元已设置 MemoryHigh=3G、MemoryMax=4G、MemorySwapMax=1G、TasksMax=4096、KillMode=control-group、Restart=on-failure、RestartSec=15s |
重量级任务不会再无边界挤占整台 101;OOM 或异常退出后由 systemd 回收整个单元并自动重启,避免再次遗留孤儿监听器。 |
| 恢复验证 |
systemd 显示 active/running;组织 Runner API 显示 pcdell-101-agentmeshos-2 为 online、busy=false;Docs Run 36354537606 的 build、Release bundle 和 RR deploy 全成功,Repository Guardrails Run 36354537307 也成功 |
Runner OOM 处置已完成闭环;失败 Run 36353513155 只保留为历史证据,最新提交 04645791f55f4b751d5e974325b0ea1097d6d6a2 已由成功工作流覆盖。 |
🔁 上游 Runner 丢失 Job 边界恢复
| 项目 |
证据 |
判定与动作 |
| 上游缺陷 |
actions/runner 问题 #4728 在 v2.337.0 上仍为 open;日志表现为 Job 完成后 Get messages has been cancelled、随后 acknowledge 404、acquirejob 409 MissingKey |
这是上游持久化 Runner 的所有权交接缺陷,不是 101 内存或 RR 部署问题;不能通过修改 Runner 二进制做私有补丁。 |
| 现场命中 |
本轮 Docs Run 36355038100 第一次执行的 deploy-production / deploy 为 runner_id=0、runner_name=""、steps=[],注解为 The job was not acquired by Runner of type self-hosted even after multiple attempts |
与上游问题描述完全一致;手动重启 Runner 后重跑同一 Run 成功,证明生产部署链本身可用。 |
| 恢复边界 |
新增 .github/workflows/recover-runner-unacquired-jobs.yml 和 scripts/ci/recover-runner-unacquired-jobs.sh,使用 ubuntu-latest 独立于 101 Runner 执行 |
只重跑同时满足 runner_id=0、cancelled、零步骤和明确丢失注解的失败 Job;普通测试失败、构建失败、部署失败及达到尝试上限的运行绝不自动重跑。 |
| 负向回归 |
recover-runner-unacquired-jobs-test.sh 验证命中重跑、普通失败不动作、尝试上限不动作 |
控制器不会吞掉真实业务失败,也不会形成无限自动重跑循环;新工作流自身不在触发列表中。 |
🛡️ Runner Busy long-poll 悬挂看门狗
📌 结论
受控重启 Runner 服务不是 GitHub 自动动作,也不是一个独立服务名,只是对一次人工 systemctl restart 的描述。这里的管理对象是 101 上官方 GitHub Actions Runner 的 systemd 单元:actions.runner.agentmeshos.pcdell-101-agentmeshos-2.service。GitHub 负责投递 Job,systemd 负责拉起 Runner 进程,Runner 自己负责长轮询和领取 Job;三者不是同一层机制。
🔍 已确认故障
| 项目 |
现场证据 |
含义 |
| 故障窗口 |
Runner_20260927-222810-utc.log 第 6175–6255 行:16:01:44Z Job 成功完成后立即出现 status=Busy ... has been cancelled,直到 16:07:34Z 人工重启前没有新 Job 领取 |
Runner 进程没有退出,但 Busy 长轮询已失效 |
| 人工恢复 |
Runner_20260930-160736-utc.log 第 156–172 行:重启后 16:07:44Z Listening for Jobs,16:07:50Z 立即领取 queued 的 deploy-production / deploy |
部署链本身可用,故障发生在 Runner 的 Job 交接边界 |
| systemd 盲区 |
活动单元为 active/running、Restart=on-failure;主进程没有失败 |
普通故障重启不会覆盖“活着但不再接单”的状态 |
| 上游范围 |
actions/runner 2.337.0 仍存在 Busy long-poll 取消后失去下一次领取机会的 #4728 |
不能在本地修改 Runner 二进制;只能在 101 边界增加看门狗 |
🧩 看门狗动作条件
只有以下条件同时成立才执行一次 systemctl restart:
RUNNER_ID=7 在 GitHub API 中为 online 且 busy=false。
- systemd 服务为
active。
- 服务 cgroup 内没有
Runner.Worker,即当前没有正在执行的生产 Job。
- 最新 Runner 日志存在“Job 完成后 Busy 长轮询被本地取消”,且取消后超过 180 秒没有
Acknowledging runner request 或 Running job。
- GitHub 确有等待
self-hosted/agentmeshos-ci 的 queued Job,且该 Run 在 Busy 取消之后创建、排队也超过 180 秒。
- 距离上一次自动重启已超过 1800 秒冷却期。
任何条件不满足都只写一条 no_action,不停止服务、不重启服务、不改现有 3 GiB/4 GiB cgroup 限额。Runner ID 6 的启停由独立的容量门禁控制,不由悬挂看门狗直接修改。
🧪 验证证据
| 项目 |
证据 |
结果 |
| 负向回归 |
bash scripts/ci/tests/runner-stall-watchdog-test.sh |
无 queued Job、Runner busy、Worker 活跃、正常空闲、宽限期、已恢复、取消过旧和冷却期均不动作;只有真实悬挂夹具执行重启 |
| 现场安全实跑 |
systemctl start agentmeshos-runner-stall-watchdog.service |
Result=success、ExecMainStatus=0;当前返回 runner_status=online runner_busy=false 和 no_action reason=no_matching_queued_job |
| 运行边界 |
systemctl show actions.runner...-2.service |
Runner 仍为 active/running、NRestarts=0,MemoryHigh=3G、MemoryMax=4G、MemorySwapMax=1G、TasksMax=4096 未变化 |
| 周期调度 |
agentmeshos-runner-stall-watchdog.timer |
enabled/active,每 5 分钟检查一次,首次开机后 3 分钟启动 |
| 文件一致性 |
sha256sum |
仓库脚本与 /usr/local/lib/agentmeshos/runner-stall-watchdog.sh 一致;service 与 timer 的生产副本与仓库一致 |
⚠️ 已验证版本与回退
- 本机制只针对
actions/runner 2.337.0 的已验证日志形态。升级 Runner 后必须重新采集正常完成与 Busy 取消日志,再确认解析规则。
- 回退命令:
systemctl disable --now agentmeshos-runner-stall-watchdog.timer,并删除 /etc/systemd/system/agentmeshos-runner-stall-watchdog.service、/etc/systemd/system/agentmeshos-runner-stall-watchdog.timer 和 /usr/local/lib/agentmeshos/runner-stall-watchdog.sh 后执行 systemctl daemon-reload。
- 配置加载曾在首次安装回归中发现
EnvironmentFile 不接受现有凭据文件的 export 行;已改为脚本内部安全 source root-only 文件,之后 systemd 不再记录无效环境赋值。首次回归产生的 root-only journal 历史记录仍需轮换对应 Token 后才算安全闭环。
⚖️ Runner 双实例资源门禁与并发恢复
📌 结论
单个 GitHub Actions Runner 一次只能执行一个 Job,这是 actions/runner 的进程模型,不是 101 的配置缺陷。恢复双 Runner 的正确做法不是长期只留一个实例,也不是无条件启动两个实例,而是让 Runner ID 6 作为受控第二执行器:资源安全时上线,资源不安全且空闲时下线;正在执行 Job 时不强杀,依赖每个 Runner 4 GiB 硬上限和双 Runner 合计 10 GiB 的 cgroup 上限控制最坏情况。
🔍 被禁用原因与恢复判断
| 项目 |
现场事实 |
恢复结论 |
| 临时禁用对象 |
旧 Runner ID 6 对应的 actions.runner.agentmeshos.pcdell-101-agentmeshos.service 当前为 disabled/inactive;配置仍保留 .runner、.credentials 和独立 _work |
注册信息完整,可在门禁控制下重新启动,不需要重新注册或创建私有版本 |
| 直接 OOM 根因 |
2026-09-28 全局 OOM 的直接触发进程是失控 grep,不是 Runner 二进制;Runner 2 单元作为受害者被内核 oom-kill |
直接触发路径已终止;Runner 本身不需要通过永久禁用规避 |
| 为什么当时要禁用 |
OOM 后旧单元留下 RunnerService.js 与 Runner.Listener 孤儿进程,继续运行会污染进程和资源边界 |
禁用是清理窗口内的临时隔离措施,不能替代资源门禁和后续恢复 |
| 当前是否具备恢复条件 |
101 为 14 GiB 内存、11 GiB swap;本次复核时 MemAvailable 约 11 GiB、swap 使用约 75 MiB、memory PSI 短窗口为 0;Runner 2 已在相同 3 GiB/4 GiB cgroup 下持续运行 |
具备受控恢复条件,但必须先部署门禁和总 cgroup 上限,再启动第二实例 |
🧩 门禁阈值
| 指标 |
允许第二 Runner 启动 |
允许已启动且空闲的第二 Runner 继续在线 |
来源 |
MemAvailable |
不少于 6144 MiB |
不少于 4096 MiB |
/proc/meminfo |
| swap 剩余空间 |
不少于 4096 MiB |
不少于 2048 MiB |
/proc/swaps |
memory PSI some avg60 |
不超过 3.00 |
不超过 7.00 |
/proc/pressure/memory |
| 5 分钟负载 |
不超过每核 1.50,8 vCPU 合计 12.00 |
不超过每核 2.00,8 vCPU 合计 16.00 |
/proc/loadavg 与 getconf _NPROCESSORS_ONLN |
| 根盘可用空间 |
不少于 51200 MiB |
不少于 30720 MiB |
df -Pm / |
启动线和继续线有意分开,避免资源在阈值附近抖动时反复启停。门禁仅在第二 Runner 本地没有 Runner.Worker 子进程,且 GitHub Organization API 报告 status=online、busy=false 时才执行停止;任一来源显示正在执行 Job,或 GitHub API 无法确认空闲时,都会延迟停止并等待下一轮,不以中断 CI 换取瞬时资源下降。
🔧 实现与验证入口
| 对象 |
仓库路径 |
生产路径 |
| 容量门禁脚本 |
scripts/ci/runner-capacity-gate.sh |
/usr/local/lib/agentmeshos/runner-capacity-gate.sh |
| 安装与备份脚本 |
scripts/ci/install-runner-capacity-gate.sh |
安装前在 /root/.codex/backups/agentmeshos-runner-capacity-gate-<UTC>/ 保存原件 |
| 双 Runner 总 cgroup |
deploy/systemd/agentmeshos-ci.slice |
/etc/systemd/system/agentmeshos-ci.slice |
| 单 Runner cgroup drop-in |
deploy/systemd/runner-capacity.conf |
/etc/systemd/system/<runner>.service.d/10-capacity.conf |
| 门禁 service/timer |
deploy/systemd/agentmeshos-runner-capacity-gate.service、.timer |
/etc/systemd/system/,每 60 秒检查一次 |
| 负向回归 |
scripts/ci/tests/runner-capacity-gate-test.sh |
验证安全启动、资源不足不启动、资源恢复自动启动、空闲时停止、执行中不停止、swap 余量/PSI/磁盘压力和主 Runner 离线等分支 |
🧪 验收要求
bash scripts/ci/tests/runner-capacity-gate-test.sh 必须通过,证明门禁的启动、停止和拒绝路径都执行了预期动作。
- 安装后 systemd 必须显示两个 Runner 都以
agentmeshos-ci.slice 为 cgroup,单实例为 MemoryHigh=3G、MemoryMax=4G、MemorySwapMax=1G,slice 为 MemoryHigh=8G、MemoryMax=10G、MemorySwapMax=2G。
- GitHub Organization API 必须重新显示 Runner ID 6 为
online,且不能以重新注册、改名或私有补丁替代原注册对象。
- 必须触发至少两个
agentmeshos-ci Job,并在 GitHub API 观察 Runner ID 6 与 ID 7 同时 busy=true;完成后两者恢复 online,101 无 OOM、无 Runner 单元失败、无 Job 丢失。
- 验证期间必须记录
free、/proc/pressure/memory、systemctl show 和 gh api orgs/agentmeshos/actions/runners 的前后快照;如果资源跨过继续线,门禁必须停止空闲的第二实例,不能以“已经启动”为由忽略阈值。
📊 2026-10-02 实际验收
| 项目 |
现场证据 |
结论 |
| Runner 版本 |
/opt/agentmeshos-actions-runner/bin/Runner.Listener --version 与 Runner 2 同路径命令均返回 2.337.0 |
两个实例使用相同官方 Runner 版本,没有创建私有补丁 |
| 双实例在线 |
gh api orgs/agentmeshos/actions/runners --paginate 返回 ID 6 pcdell-101-agentmeshos 与 ID 7 pcdell-101-agentmeshos-2 均为 online |
原注册对象已恢复,ID 6 不再长期离线 |
| cgroup 隔离 |
systemctl show 显示两个服务 Slice=agentmeshos-ci.slice、MemoryHigh=3221225472、MemoryMax=4294967296、MemorySwapMax=1073741824;slice 为 MemoryHigh=8589934592、MemoryMax=10737418240 |
单实例和双实例总内存边界均已生效 |
| 探针并发 |
Run 36910459954 与 36910465317 在 02:55:40 CST 同时为 in_progress,GitHub API 同刻显示 Runner 6、7 同时 busy=true;日志分别出现 runner_name=pcdell-101-agentmeshos 与 runner_name=pcdell-101-agentmeshos-2 |
两个 Runner 确实由 GitHub 同时调度,不是单实例排队 |
| Guardrails 并发 |
Run 36910901862 与 36910907487 在 02:59:15–02:59:36 CST 同时保持两个 Runner busy=true,两个 Run 均为 completed/success |
更接近日常 CI 的两条 Job 链已并发成功 |
| 门禁执行 |
journalctl -u agentmeshos-runner-capacity-gate.service 在 02:55:39 CST 输出 no_action reason=secondary_runner_busy,指标为 mem_available_mb=10813、swap_used_mb=73、memory_psi_avg60_centi=0、load5_centi=81、disk_available_mb=272542 |
Job 执行期间门禁没有停止 Runner,也没有误杀 Job |
| 资源结果 |
并发验证后 slice MemoryCurrent=301592576、MemoryPeak=456265728;MemAvailable 约 10 GiB、swap 使用约 73 MiB、memory PSI 为 0;当前 boot 的 dmesg 未检出新的 OOM |
本轮并发未触发 OOM,但两次探针和 Guardrails 不等同于两个重量级 Docker BuildKit 同时满负载 |
| 回退点 |
安装脚本输出 backup=/root/.codex/backups/agentmeshos-runner-capacity-gate-20261001T185852Z;仓库脚本与生产副本 SHA-256 一致 |
可按备份目录恢复 systemd 单元和 drop-in,不删除 Runner 注册信息 |
🛠️ 2026-10-03 资源恢复失效修复
| 项目 |
现场证据 |
结论 |
| 症状 |
Organization Runner ID 6 为 offline、ID 7 为 online/busy=true,自托管部署 Job 因此在队列中等待 |
这不是 GitHub 把两个 Runner 串行化;是第二个执行器没有恢复到在线状态 |
| 直接原因 |
agentmeshos-runner-capacity-gate.service 在 04:06:55 因 memory_psi_avg60_centi=1439>700 停止 Runner ID 6,符合当时真实内存压力保护 |
压力停止本身正确,问题在恢复条件无法再次打开 |
| 系统根因 |
后续门禁持续输出 swap_used_mb=1408>512 或 swap_used_mb=3343>512;同期 MemAvailable 约 9 GiB、memory PSI 多数为 0、load5 约 3.0、swap 仍有约 10 GiB 空闲 |
累计 swap 使用量不是当前换页活跃度;它会把静止的换页缓存永久当作资源不足,Runner ID 6 因此无法自动恢复 |
| 防复发修复 |
启动线与继续线的 swap 条件改为剩余空间底线:启动不少于 4096 MiB、已启动空闲时继续在线不少于 2048 MiB;MemAvailable、memory PSI、负载和磁盘门禁保持不变 |
恢复判断同时依赖真实可用内存和压力指标,不再因低活跃度的累计 swap 使用量持续锁死第二 Runner |
| 现场恢复 |
新门禁记录 swap_free_mb=10844 后输出 action=start、action=complete ... status=online;GitHub API 随即显示 Runner ID 6 与 ID 7 同时为 online/busy=true,Runner ID 6 领取了排队 Job |
自动恢复和双实例并行已经现场验证;服务保持 disabled 仅表示不参与开机全量启动,实际生命周期由门禁 timer 控制 |
| 负向回归 |
bash scripts/ci/tests/runner-capacity-gate-test.sh |
覆盖资源不足停止、资源恢复自动启动、累计 swap 为 2 GiB 且剩余 10 GiB 时恢复、剩余 swap 仅 1 GiB 时阻断、PSI/磁盘/主 Runner 异常等路径 |
⚠️ 并发验收中发现的 /tmp 权限漂移
| 项目 |
现场证据 |
处置 |
| 症状 |
首次两条 Guardrails Run 36910625129、36910631522 均在同一 Test MM edge address guard 步骤失败:mktemp: Permission denied ... /tmp/agentmeshos-edge-hardcode-check... |
失败不是测试逻辑失败,也不是两个 Runner 互相抢同一临时目录 |
| 根因 |
stat -c '%a %A %U:%G %n' /tmp 返回 755 drwxr-xr-x root:root /tmp;findmnt /tmp 显示 tmpfs 运行参数中没有 mode=1777;sudo -u github-runner touch /tmp/github-runner-write-test 返回 Permission denied |
101 的 /tmp 从启动时就是错误权限,Runner 用户无写权限;此前单实例未连续命中该前置步骤,不代表权限正确 |
| 修复 |
新增 deploy/tmpfiles.d/00-agentmeshos-tmp-mode.conf,安装到 /etc/tmpfiles.d/ 并执行 systemd-tmpfiles --create |
修复后 /tmp 为 1777 drwxrwxrwt root:root,github-runner 创建的 mktemp -d 成功 |
| 防复发 |
scripts/ci/tests/tmp-permissions-contract-test.sh 校验 tmpfiles 契约和现场 /tmp=1777 |
后续重启或部署偏移会直接让 Guardrails 失败,不再等到某个 Job 卡在 mktemp 才发现 |
| 正向复核 |
修复后重新触发 Run 36910901862、36910907487 |
两个 Run 均并发执行并成功,证明 /tmp 修复和双 Runner 调度同时成立 |
🔁 回退
回退时先执行 systemctl disable --now agentmeshos-runner-capacity-gate.timer,再停止 agentmeshos-runner-capacity-gate.service 和第二 Runner 服务,最后从安装脚本输出的 backup 目录恢复 slice、drop-in、tmpfiles 配置和 systemd 单元并执行 systemctl daemon-reload。回退不会删除 Runner ID 6 的注册信息,也不会修改 Runner 2 的工作目录或凭据;恢复 /tmp 错误权限只用于验证回退,正常回退仍应保持 /tmp=1777。
🔐 阶段一:完成 Auth 真实业务门禁
1.1 授权与准备
- 用户提供一次性专用测试账户及有效窗口,确认允许登录、登出、密码修改/重置和会话恢复测试。
- 预先运行
scripts/ci/stateful-release-gate.sh auth-site,核对数据库快照 SHA-256、PRAGMA quick_check、Schema/计数、镜像和 auth-site.env 权限;记录基线但不输出秘密。
1.2 非破坏性业务验证
- 使用
scripts/ci/auth-site-smoke.sh 验证本地密码登录、/session、账户页、登出及登出后 401。
- 在同一专用账户窗口验证密码修改或重置、旧会话立即失效、新密码会话恢复;确认数据库未被回滚,失败时只回滚镜像。
- 保存脱敏请求状态、时间、运行提交 SHA、失败阶段和重试关系;不得保存密码或响应正文中的敏感数据。
1.3 放行判断
只有上述业务链路全部通过,才把 Auth 标记为“门禁完成、可考虑自动发布”;本轮仍不直接新增生产自动升级 Workflow。失败则保留证据、定位根因并在授权窗口内重跑。
🔁 阶段二:恢复并迁移多项目 Runner 接入
2.1 恢复 GitHub 权限
- 在 Home 真实可见终端由用户重新完成
gh auth login,或提供可访问 hhtbing/* 的等价凭据;分别验证身份、仓库可见性、目标组织仓库写权限,区分认证失败、仓库不存在和权限不足。
- 在 101 重新执行只读仓库/API/HTTPS 双路径验证;不因单次 401/404 推断仓库删除。
2.2 保真迁移优先路径
- 对每个项目先在
/srv/codex-hub-backup(现场确认来源 /dev/sda1)保存完整工作树、远端信息、分支/标签和 Git 对象完整性报告。
- 取得原仓库读取/转移权限后,按项目逐一转入
agentmeshos,保留历史、分支、标签和未提交状态记录;迁移后更新本地 origin,不自动提交用户未提交变更。
- 为每个仓库设置
AGENTMESHOS_RUNNER_LABEL=agentmeshos-ci,Workflow 默认选择 101 Runner;特殊硬件/平台 Job 保留明确的 ubuntu-latest/windows-latest fallback。
2.3 无历史恢复备选路径
仅在用户明确批准后,对无法恢复历史的项目从已备份工作树建立新基线:在组织空仓创建首个受控提交,单独标注“无历史恢复”,不得伪称为保真迁移;逐仓库完成构建、测试、Runner 调度和 SHA 监控。
2.4 多项目验收
每个项目独立验证:组织仓库可见且默认分支存在、Runner online、至少一个构建/测试 Job 在 101 执行、例外 Job 使用显式托管 Runner、完整 40 位 SHA 的所有 Workflow/Job/Step 均成功。任何失败、取消、超时或 Runner 离线都必须完成二次验证、根因记录、修复和重跑后再进入下一项目。
📈 阶段三:持续复核与收口
- 每次推送后运行
scripts/ci/monitor-github-actions.sh,按 owner/repository + full_commit_sha 保存失败 URL、Run ID、失败日志、修复提交和成功覆盖 URL。
- 每次 101 备份写入前执行
findmnt /srv/codex-hub-backup,确认设备为 /dev/sda1;禁止把 RR /backup 当作独立备份盘。
- 每月只读复核 Runner 在线状态、组织仓库授权范围、RR 容量阈值和无状态服务镜像/健康状态;发现漂移先记录证据再修复。
✅ 完成标准
- Auth 真实本地密码业务门禁在授权窗口内全量通过,且没有秘密进入证据。
- 已授权的其他项目完成保真迁移,或经用户批准完成明确标注的无历史恢复;每个项目均能在 101 Runner 执行并通过完整 SHA 监控。
- AgentMeshOS 无状态 CD、Stateful 人工发布边界、101/RR/103/jj 角色和备份盘边界保持不变;远端 GitHub 提交可见,相关 Actions 全部成功,工作区状态可解释。
⚠️ 未决项 / 需要决策
| 未决项 |
当前事实 |
下一步 |
| Docs 工作流正向复核 |
已完成;Runner 为 active/running 和 online/idle,Docs Run 36354537606 的 build、Release bundle、RR deploy 全成功 |
保留失败 Run 36353513155 作为历史证据,不再把最新状态描述为“等待复核”。 |
| 第二 Runner 是否恢复 |
已完成;Runner ID 6 与 ID 7 均为 online,探针和两条 Guardrails 已同时 busy=true 并成功完成 |
继续观察后续重量级 Docker 构建在双 Runner 下的 slice 峰值、PSI 和 swap;若跨越停止线,门禁应在空闲窗口安全暂停第二实例。 |
| 重量级诊断命令约束 |
本次 OOM 的直接触发进程是本机用户会话内异常膨胀的 grep |
后续大范围搜索必须先限定路径、文件类型和输出上限;禁止再把不受控的全盘文本扫描与构建窗口并行。 |