Skip to content

feat(butler): ADR-0074 落地——孤儿标签移除 + deadman 双层触发 + hc.io 实配(ADR-0074) - #230

Merged
randypanding merged 1 commit into
mainfrom
w1-followup
Aug 21, 2026
Merged

feat(butler): ADR-0074 落地——孤儿标签移除 + deadman 双层触发 + hc.io 实配(ADR-0074)#230
randypanding merged 1 commit into
mainfrom
w1-followup

Conversation

@randypanding

Copy link
Copy Markdown
Contributor

动机(owner 授权的三项收尾)

  1. 孤儿标签检查移除:closed issue 的 state:* 是历史事实非漂移(closed+state:done 是正常终态),原检查只产噪音(run 32484413154 报 6 条全噪音)——owner 裁决"不重要",ADR-0074 决策 1。
  2. deadman 双层触发:hc.io Webhook 无自定义 header,无法直连回调 repository_dispatch(实测边界)。仓内层 butler-heartbeat-watch(6h)检测 heartbeat 成功 run 陈旧度 > deadman_stale_hours(3h) → 自动 trip;外部层(hc.io→owner 告警)承担"Actions 整体静默"形态——ADR-0074 决策 2。
  3. hc.io 实配记录:owner 已注册 check,DEADMAN_PING_URL org secret 已配置,butler-heartbeat 实测 ping 成功(run 32508780521 attempt 1/2)。

变更

  • governance/butler-reconcile.sh:检查 (b) 移除(closed 清单不再拉取)
  • governance/deadman-trip.sh(新):trip 执法核心单一实现(trip workflow 与 watch 共用)
  • .github/workflows/butler-deadman-trip.yml:改为调脚本(语义不变:脚本 0→run 变红=可见信号)
  • .github/workflows/butler-heartbeat-watch.yml(新):陈旧度兜底;API 失败=infra 红不盲 trip(ADR-0040 决策 5 同款)
  • governance/policy/butler.yaml:+deadman_stale_hours、+deadman-watch 服务行、行 1 描述更新
  • docs/deadman-setup.md:双层触发说明 + 当前状态实记(剩余 owner 侧仅 grace 核对与告警通道勾选)
  • gate.yml:bash -n 登记 deadman-trip.sh

验证

  • bash -n / yaml 解析全过;ADR-0074 已三向落位(archive#3 + agent-registry#80 INDEX)
  • 合并后:dispatch butler-reconcile 验证无孤儿输出;dispatch butler-heartbeat-watch(stale_hours_override 注入超小值)验证陈旧→trip 路径 + 复位

回滚:revert 本 PR + 删 watch workflow。
Card: Cloudbird-Software/.github#168

…artbeat-watch 仓内兜底)+ DEADMAN_PING_URL 已配实记(W1 运行反馈,owner 授权)
Copilot AI lite review requested due to automatic review settings August 21, 2026 18:07
@coderabbitai

coderabbitai Bot commented Aug 21, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@randypanding, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 4 minutes

Limit details: You’ve used all 10 included reviews currently available.

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

Wait for the limit to reset, then comment @coderabbitai review or push new commits to the PR.

An organization admin can change what happens after included review limits in Billing.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 81fe8c4b-13b4-4aaf-9569-d92dd61d37cd

📥 Commits

Reviewing files that changed from the base of the PR and between b0e87f3 and 9d0ac93.

⛔ Files ignored due to path filters (1)
  • scripts/__pycache__/adr_index_map.cpython-311.pyc is excluded by !**/*.pyc
📒 Files selected for processing (7)
  • .github/workflows/butler-deadman-trip.yml
  • .github/workflows/butler-heartbeat-watch.yml
  • .github/workflows/gate.yml
  • docs/deadman-setup.md
  • governance/butler-reconcile.sh
  • governance/deadman-trip.sh
  • governance/policy/butler.yaml

Comment @coderabbitai help to get the list of available commands.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

butler: ADR-0074 落地(移除孤儿标签检查 + deadman 双层触发)

✨ Enhancement 🐞 Bug fix 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• 移除 closed issue 的 state:*「孤儿标签」检查,降低 reconcile 噪音
• 抽取 deadman-trip 执法为单一脚本实现,并由 trip/watch 两入口复用
• 新增 heartbeat-watch 仓内兜底:心跳成功 run 陈旧即自动 trip;API 失败不盲 trip
Diagram

graph TD
  HC{{"hc.io"}} --> TRIPWF["butler-deadman-trip"] --> TRIPSCRIPT["deadman-trip.sh"]
  HBWF["butler-heartbeat"] --> HC
  WATCHWF["heartbeat-watch"] --> GHAPI["GitHub Actions API"]
  GHAPI --> WATCHWF
  WATCHWF --> TRIPSCRIPT
  TRIPSCRIPT --> CB[("AUTO_MERGE_DISABLED")]
  TRIPSCRIPT --> AM["Strip PR auto-merge"]
  TRIPSCRIPT --> P0["P0 issue (.github)"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. 外部回调全自动(Cloudflare Worker/转发服务)
  • ➕ 可让 hc.io 在 grace 超时后自动触发 repository_dispatch,无需人工一键 trip
  • ➕ 仓内无需依赖 Actions API 查询 runs
  • ➖ 新增外部组件与密钥托管面(PAT/worker secret),运维复杂度更高
  • ➖ 与“尽量少外部依赖”的仓内治理目标有张力
2. 保留 trip 内联逻辑(不抽脚本)
  • ➕ 变更文件更少,读者可在 workflow 内一次看完
  • ➖ trip 与 watch 会产生重复实现,易漂移(ADR-0074 明确要避免)
  • ➖ 难以在 gate.yml 里统一 bash -n / 本地自测
3. 改为只做外部告警 + 手动 trip(无仓内 watch)
  • ➕ 机制更“纯外部”,避免 Actions 反身监控的边界问题
  • ➖ “Actions 活着但 heartbeat 工作流被禁用/损坏”的形态会漏检
  • ➖ 增加人为响应依赖,降低缺席即停的自动性

Recommendation: 当前“双层触发 + 单一执法脚本”的组合是最均衡的:用仓内 watch 覆盖“心跳工作流失效但 Actions 仍运行”的常见故障形态,同时把“Actions 整体静默”交给外部告警闭环。后续若确需外部自动回调,可再增 Worker 转发,但不建议在本阶段引入额外外部依赖面。

Files changed (8) +247 / -164

Enhancement (2) +125 / -98
butler-deadman-trip.yml改为调用 deadman-trip.sh 的 trip 入口 workflow +16/-98

改为调用 deadman-trip.sh 的 trip 入口 workflow

• 将缺席即停的执法逻辑从 workflow 内联脚本抽离为 governance/deadman-trip.sh,并统一以脚本返回码驱动 workflow 变红信号。保留 repository_dispatch / workflow_dispatch 入口语义不变,但降低双实现漂移风险。

.github/workflows/butler-deadman-trip.yml

deadman-trip.sh新增缺席即停执法核心脚本(trip/watch 共用) +109/-0

新增缺席即停执法核心脚本(trip/watch 共用)

• 新增单一实现脚本:置 org Actions variable AUTO_MERGE_DISABLED=true,遍历 active 仓撤销 open PR 的 auto-merge,并幂等创建/评论 label=deadman-tripped 的 P0 issue。定义退出码语义为 0=已熔断完成、2=infra 失败,供调用方决定是否变红。

governance/deadman-trip.sh

Bug fix (2) +14 / -29
butler-reconcile.sh移除 closed issue 的 state:* 孤儿标签检查 +14/-29

移除 closed issue 的 state:* 孤儿标签检查

• 删除 closed issue 列表拉取与 orphan_state_labels 统计/报告逻辑,明确 closed+state:* 属历史事实而非漂移对象。相应更新输出文案与 AUDIT JSON 字段。

governance/butler-reconcile.sh

adr_index_map.cpython-311.pyc提交了 Python 字节码缓存文件(__pycache__) +0/-0

提交了 Python 字节码缓存文件(pycache

• 该变更引入了 .pyc 二进制缓存文件,通常属于构建产物而非源码,应确认是否为误提交并考虑通过 .gitignore 排除。

scripts/pycache/adr_index_map.cpython-311.pyc

Documentation (1) +19 / -32
deadman-setup.md更新 runbook:说明 ADR-0074 双层触发与当前实配状态 +19/-32

更新 runbook:说明 ADR-0074 双层触发与当前实配状态

• 将失败回调章节调整为“外部告警 + owner 一键 trip”的推荐路径,并补充仓内 heartbeat-watch 兜底的职责边界。记录 2026-08-21 的 hc.io 注册与 DEADMAN_PING_URL 配置现状。

docs/deadman-setup.md

Other (3) +89 / -5
butler-heartbeat-watch.yml新增 heartbeat 陈旧度兜底 watch(超阈值自动 trip) +75/-0

新增 heartbeat 陈旧度兜底 watch(超阈值自动 trip)

• 新增 6h 定时与手动触发的仓内兜底层:查询 butler-heartbeat 最近成功 run 的更新时间,超过 deadman_stale_hours 则触发 deadman-trip.sh。API 查询失败按 infra 退出(变红但不盲 trip),避免误熔断导致人工复位停摆。

.github/workflows/butler-heartbeat-watch.yml

gate.yml将 deadman-trip.sh 纳入 bash -n 语法门 +2/-1

将 deadman-trip.sh 纳入 bash -n 语法门

• 在现有治理脚本语法检查链路中新增 governance/deadman-trip.sh 的 bash -n,避免新增脚本未登记导致的语法盲区。

.github/workflows/gate.yml

butler.yaml策略更新:移除 orphan_state_labels,新增 deadman-watch 与阈值 +12/-4

策略更新:移除 orphan_state_labels,新增 deadman-watch 与阈值

• 从 wake_matrix 行 1 移除 orphan_state_labels 检查说明,并新增 deadman-watch 服务行记录仓内兜底职责。新增 deadman_stale_hours 阈值(默认 3h)以驱动 heartbeat-watch 的陈旧度判定。

governance/policy/butler.yaml

@randypanding
randypanding merged commit 4f04b91 into main Aug 21, 2026
12 checks passed
@randypanding
randypanding deleted the w1-followup branch August 21, 2026 18:11

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements ADR-0074’s butler “dead-man” refinements by removing the noisy closed-issue “orphan state:* label” check, introducing an internal heartbeat staleness watchdog as a second-layer trigger, and consolidating trip enforcement logic into a single shared script used by both entry workflows.

Changes:

  • Remove closed-issue orphan state:* label scanning from butler-reconcile and update policy/docs accordingly.
  • Add governance/deadman-trip.sh as the single enforcement implementation and switch the trip workflow to invoke it.
  • Add butler-heartbeat-watch workflow and new policy threshold deadman_stale_hours to trigger trips when the heartbeat workflow’s last successful run is stale.

Reviewed changes

Copilot reviewed 7 out of 8 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
governance/policy/butler.yaml Removes orphan-label check; adds deadman-watch service and deadman_stale_hours threshold.
governance/deadman-trip.sh New shared enforcement core for dead-man trips (breaker variable, strip automerge, open P0).
governance/butler-reconcile.sh Removes orphan-label check and adjusts reporting/audit payloads.
docs/deadman-setup.md Updates runbook to describe dual-layer triggering and current setup status.
.github/workflows/gate.yml Registers deadman-trip.sh for bash syntax gating.
.github/workflows/butler-heartbeat-watch.yml New cron watchdog that checks heartbeat staleness and triggers trip via shared script.
.github/workflows/butler-deadman-trip.yml Refactors trip workflow to call governance/deadman-trip.sh.
Suppressed comments (1)

governance/butler-reconcile.sh:274

  • 本脚本已移除 orphan_state_labels 检查并在 findings/infra-fail 审计 JSON 里删掉了该字段,但 ok 分支的审计 JSON 仍保留 orphan_state_labels":0,会导致同一 butler 的 AUDIT actions 字段 schema 不一致,增加后续机器解析成本。建议从 ok 分支也移除该字段。
if [[ $FINDINGS -gt 0 ]]; then
  audit findings "{\"repos\":$REPO_COUNT,\"stale_in_progress\":$STALE_COUNT,\"stale_quarantine\":$QUAR_COUNT,\"report\":\"$REPORT_ACTION\"}"
  exit 1
fi
audit ok "{\"repos\":$REPO_COUNT,\"stale_in_progress\":0,\"orphan_state_labels\":0,\"stale_quarantine\":0,\"report\":\"$REPORT_ACTION\"}"

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +48 to +66
STALE_H="${STALE_HOURS_OVERRIDE:-$(python3 -c 'import yaml; print(yaml.safe_load(open("governance/policy/butler.yaml", encoding="utf-8"))["deadman_stale_hours"])' | tr -d '\r')}"
[[ "$STALE_H" =~ ^[0-9]+([.][0-9]+)?$ ]] || { echo "::error::deadman_stale_hours 非数值: $STALE_H" >&2; exit 2; }
# 最近一次成功 heartbeat run 的完成时间(workflow 文件名路由;无成功 run = 视为最陈旧)
LAST=$(gh api "repos/Cloudbird-Software/.github/actions/workflows/butler-heartbeat.yml/runs?status=success&per_page=1" \
--jq '.workflow_runs[0].updated_at // empty' 2>/dev/null)
if [[ -z "$LAST" ]]; then
if gh api "repos/Cloudbird-Software/.github/actions/workflows/butler-heartbeat.yml/runs?per_page=1" --jq '.total_count' 2>/dev/null | grep -qE '^[0-9]+$'; then
AGE_S=999999999 # 有 run 但零成功 → 视为远古(会 trip)
AGE_H="∞(存在 run 但零成功)"
else
echo "::error::heartbeat runs 查询失败——fail-closed 变红(不盲 trip)" >&2
exit 2
fi
else
AGE_S=$(( $(date -u +%s) - $(date -u -d "$LAST" +%s) ))
AGE_H=$(( AGE_S / 3600 ))
fi
THRESH_S=$(( STALE_H * 3600 ))
echo "AUDIT | butler=heartbeat-watch | trigger=${BUTLER_TRIGGER:-schedule} | outcome=checked | actions={\"last_success\":\"${LAST:-none}\",\"age_hours\":\"$AGE_H\",\"threshold_hours\":$STALE_H}"
Comment on lines +43 to +48
if ! gh api -X PATCH "orgs/$ORG/actions/variables/$CB" -f name="$CB" -f value=true >/dev/null 2>&1; then
if ! gh api -X POST "orgs/$ORG/actions/variables" -f name="$CB" -f value=true -f visibility=all >/dev/null 2>&1; then
infra "org 变量 $CB 置位失败(PATCH/POST 均败)"
fi
fi
act "熔断变量 $CB=true 已置位(与 cost-check 共用——宪法 §6 缺席即停;$SRC)"
Comment thread docs/deadman-setup.md
-H "Authorization: Bearer $PAT" \
https://api.github.com/repos/Cloudbird-Software/.github/dispatches \
-d '{"event_type":"deadman-tripped"}'
gh api -X POST repos/Cloudbird-Software/.github/dispatches -H "Authorization: Bearer $PAT" -f event_type=deadman-tripped
Comment on lines 227 to +233
REPORT_BODY="管家 reconcile 报告(唤醒矩阵行 1,ADR-0057,运行 $(date -u +%FT%TZ),trigger=$TRIGGER):

- 僵尸卡(state:in-progress 停滞 > ${STALE_DAYS}d): $STALE_COUNT
- 孤儿标签(closed 仍挂 state:*;v1 只报告不纠正——INV-02 状态标签写须 App 令牌经仲裁): $ORPHAN_COUNT
- 隔离超时(state:quarantine 停滞 > ${STALE_Q_DAYS}d): $QUAR_COUNT

孤儿标签清单:
${ORPHAN_ROWS:-(无)}

(僵尸卡/隔离超时的明细见 label butler:needs-human 的 issue;本报告聚合计数与孤儿清单。)"
(closed issue 的历史 state:* 标签不检——ADR-0074:退出状态机即为历史事实。
僵尸卡/隔离超时的明细见 label butler:needs-human 的 issue;本报告聚合计数。)"
@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (4) 📘 Rule violations (1) 📜 Skill insights (0)

Grey Divider


Action required

1. Stale阈值读取路径错误 🐞 Bug ≡ Correctness
Description
butler-heartbeat-watch 用 Python 从 governance/policy/butler.yaml 读取
deadman_stale_hours,但该字段实际位于 thresholds.deadman_stale_hours,导致命令替换失败并使该 workflow 永远 exit 2(infra
红、不 trip)。结果是仓内兜底层不可用,无法按 ADR-0074 预期自动触发缺席即停。
Code

.github/workflows/butler-heartbeat-watch.yml[48]

+          STALE_H="${STALE_HOURS_OVERRIDE:-$(python3 -c 'import yaml; print(yaml.safe_load(open("governance/policy/butler.yaml", encoding="utf-8"))["deadman_stale_hours"])' | tr -d '\r')}"
Relevance

●●● Strong

直接导致 workflow 运行时 KeyError;仓库近期接受了同类自动化配置解析错误修复。

PR-#207

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
watch workflow 读取的是顶层 key deadman_stale_hours;但 butler policy 中该值定义在 thresholds 子节点下,因此运行时会
KeyError → STALE_H 为空 → 数值校验失败并 exit 2。

.github/workflows/butler-heartbeat-watch.yml[42-50]
governance/policy/butler.yaml[58-68]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`butler-heartbeat-watch.yml` reads `deadman_stale_hours` from `governance/policy/butler.yaml` using `yaml.safe_load(...)["deadman_stale_hours"]`, but the policy file defines it under `thresholds.deadman_stale_hours`. This makes the Python snippet raise a KeyError, resulting in an empty `STALE_H` and the workflow exiting with infra (2), effectively disabling the stale watchdog.

### Issue Context
This watchdog is the ADR-0074 “repo-internal layer”; if it always fails infra, deadman will not be automatically tripped when the heartbeat workflow is broken/disabled.

### Fix Focus Areas
- .github/workflows/butler-heartbeat-watch.yml[48-49]
- governance/policy/butler.yaml[58-68]

### Suggested change
Update the Python expression to read the nested key:
- `...["thresholds"]["deadman_stale_hours"]`

Also consider adding a defensive default/error message if the key is missing, so it fails with a clear infra error rather than a confusing empty value.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. 误提交pyc构建产物 🐞 Bug ⚙ Maintainability
Description
PR新增了 scripts/__pycache__/adr_index_map.cpython-311.pyc(Python 字节码缓存),这类构建产物不应入库,会制造无意义
diff、增加冲突与供应链审计噪音。并且仓内已有对应源码 scripts/adr_index_map.py,无需依赖 .pyc
Code

scripts/pycache/adr_index_map.cpython-311.pyc[R1-4]

+
+
+\u0000\u0000\u0000\u0000�c�j
+	\u0000\u0000�\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000�\u0000\u0000\u0000\u0000\u0000\u0000��\u0000\u0000\u0000�\u0000d\u0000Z\u0000d�d�l�m�Z��\u0000d�d�l�Z�d�d�l�m�Z��\u0000d�d�l�Z�g\u0000d���Z�d
Relevance

●●● Strong

提交 .pyc 构建产物是明确的仓库卫生问题,且已有对应源码。

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR 里新增了 __pycache__/*.pyc,而同名逻辑已有可读源码脚本;.pyc 不是运行所需输入。

scripts/adr_index_map.py[1-18]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
A compiled Python cache file (`*.pyc` under `__pycache__/`) was added to the repo. This is an environment-specific build artifact and should not be tracked.

### Issue Context
The source (`scripts/adr_index_map.py`) is already in the repo; committing `.pyc` adds churn and can break repo hygiene/security scans.

### Fix Focus Areas
- scripts/__pycache__/adr_index_map.cpython-311.pyc[1-4]
- scripts/adr_index_map.py[1-20]

### Suggested change
1) Remove `scripts/__pycache__/adr_index_map.cpython-311.pyc` from git.
2) Add a gitignore rule (create one if needed) to prevent recurrence, e.g.:
  - `__pycache__/`
  - `*.pyc`
3) (Optional) Add CI check to block common build artifacts if this repo frequently gets such files committed.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. 小数阈值导致算术错误 🐞 Bug ☼ Reliability
Description
butler-heartbeat-watch 允许 deadman_stale_hours 为小数(正则接受 3.5),但随后用 Bash 算术扩展 `THRESH_S=$((
STALE_H * 3600 ))` 计算秒数,Bash 不支持浮点会直接报错并使 job 失败。该失败会把“阈值合法但为小数”的配置误判成 infra 红。
Code

.github/workflows/butler-heartbeat-watch.yml[R65-66]

+          THRESH_S=$(( STALE_H * 3600 ))
+          echo "AUDIT | butler=heartbeat-watch | trigger=${BUTLER_TRIGGER:-schedule} | outcome=checked | actions={\"last_success\":\"${LAST:-none}\",\"age_hours\":\"$AGE_H\",\"threshold_hours\":$STALE_H}"
Relevance

●●● Strong

正则明确允许小数而 Bash 算术不支持浮点,属于确定性的运行时失败。

PR-#173

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
代码显式允许 STALE_H 为小数,但随后用 $(( ... )) 做乘法;该语法仅支持整数运算。

.github/workflows/butler-heartbeat-watch.yml[48-50]
.github/workflows/butler-heartbeat-watch.yml[62-67]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`butler-heartbeat-watch.yml` validates `STALE_H` with a regex that allows decimals, but then uses Bash arithmetic expansion to compute seconds. Bash arithmetic is integer-only, so values like `3.5` will cause a runtime arithmetic error.

### Issue Context
Even if today’s default is `3`, the workflow explicitly documents/accepts decimals; future overrides (e.g., drills) can break the watchdog.

### Fix Focus Areas
- .github/workflows/butler-heartbeat-watch.yml[48-66]

### Suggested change (pick one)
1) If decimals are not needed: tighten validation to integers only (`^[0-9]+$`).
2) If decimals are desired: compute `THRESH_S` via Python:
  - `THRESH_S=$(python3 -c 'import sys; print(int(float(sys.argv[1])*3600))' "$STALE_H")`
  and then keep the `(( AGE_S <= THRESH_S ))` comparison integer-based.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

4. deadman-trip.sh uses GOVERNANCE_TOKEN 📘 Rule violation ⛨ Security
Description
The new deadman automation authenticates gh api calls using secrets.GOVERNANCE_TOKEN (via
GH_TOKEN) instead of a short-lived, repo-scoped cloudbrid-agent GitHub App token. This increases
blast radius and violates the required token mechanism for agent/automation GitHub operations.
Code

governance/deadman-trip.sh[R43-45]

+if ! gh api -X PATCH "orgs/$ORG/actions/variables/$CB" -f name="$CB" -f value=true >/dev/null 2>&1; then
+  if ! gh api -X POST "orgs/$ORG/actions/variables" -f name="$CB" -f value=true -f visibility=all >/dev/null 2>&1; then
+    infra "org 变量 $CB 置位失败(PATCH/POST 均败)"
Relevance

● Weak

同一 butler token 机制建议已被近期 PR 明确拒绝。

PR-#173

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 2778539 requires GitHub API operations in agent/automation code to authenticate
using cloudbrid-agent GitHub App tokens obtained via approved helper scripts, and to avoid using
long-lived/broad credentials directly. The new workflow exports GH_TOKEN from
secrets.GOVERNANCE_TOKEN, and the new governance/deadman-trip.sh uses gh api with that token
for org/repo mutations.

Rule 2778539: Agent GitHub operations must use cloudbrid-agent app tokens with constrained scope
.github/workflows/butler-heartbeat-watch.yml[36-40]
governance/deadman-trip.sh[33-45]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`governance/deadman-trip.sh` performs GitHub API operations authenticated with `GH_TOKEN` that is set from `secrets.GOVERNANCE_TOKEN` in workflows. Compliance requires agent/automation GitHub operations to use `cloudbrid-agent` GitHub App tokens with constrained scope (single-repo) and short TTL (<= 1 hour), obtained via `scripts/ghcb` or `scripts/gh-app-token.sh`, rather than using long-lived/broad tokens directly.

## Issue Context
This script performs org/repo mutations (`gh api` PATCH/POST org variables, DELETE PR auto-merge, create/comment issues). The workflow(s) invoking it currently provide `GH_TOKEN: ${{ secrets.GOVERNANCE_TOKEN }}`.

## Fix Focus Areas
- governance/deadman-trip.sh[33-45]
- .github/workflows/butler-deadman-trip.yml[41-52]
- .github/workflows/butler-heartbeat-watch.yml[36-41]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. 撤auto-merge失败被吞掉 🐞 Bug ☼ Reliability
Description
governance/deadman-trip.sh 在遍历仓库撤销 auto-merge 时,把 gh pr list 的 stderr 重定向到 /dev/null
且不检查退出码,导致权限/限流/网络问题时会静默跳过撤销并仍然以 exit 0 表示“已熔断完毕”。这会产生“P0 已触发但部分 PR 仍保持 auto-merge”的错误执法结果。
Code

governance/deadman-trip.sh[R63-66]

+  done < <(gh pr list --repo "$ORG/$r" --state open --limit 200 \
+    --json number,autoMergeRequest \
+    --jq '.[] | [.number, (if .autoMergeRequest != null then "1" else "0" end)] | @tsv' 2>/dev/null)
+done
Relevance

● Weak

同一 deadman 自动合并撤销失败处理建议在近期 PR 中明确被拒绝。

PR-#173

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
脚本撤 auto-merge 的输入来自 gh pr list,但该调用被 2>/dev/null 静默,且脚本最终仅依赖 INFRA 决定是否 exit 2;当前路径下 `gh pr
list 失败不会增加 INFRA`,因此会错误 exit 0。

governance/deadman-trip.sh[50-67]
governance/deadman-trip.sh[104-109]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`deadman-trip.sh` uses process substitution to read from `gh pr list ... 2>/dev/null` and never checks whether `gh pr list` succeeded. If the command fails (rate limiting, auth, transient network), the loop sees no rows and the script continues, potentially exiting 0 (tripped) without actually stripping any auto-merge.

### Issue Context
Deadman trip is a hard-stop safety mechanism. Partial enforcement should be treated as infra (exit 2) so the operator knows the system is not in a guaranteed safe state.

### Fix Focus Areas
- governance/deadman-trip.sh[50-67]
- governance/deadman-trip.sh[104-109]

### Suggested change
Refactor the per-repo listing so failures increment `INFRA`, e.g.
- Capture output and exit code:
 - `PR_ROWS=$(gh pr list ... --jq ... 2>&1)`
 - if rc != 0: `infra "gh pr list failed for $r: $PR_ROWS"; continue`
- Then iterate over `PR_ROWS` via a here-string.

Optionally, also treat repeated DELETE failures as infra (or at least count them) to avoid reporting a fully-successful trip when revocation didn’t actually happen.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
✅ Compliance rules (platform): 16 rules
✅ Web pages:
  +2 more
Review mode: ⚖️ Balanced: Downgraded extended -> standard: change is below the extended eligibility bar (hunks 16/18, lines 411/200; both must reach the floor). Router rationale: This changes security-sensitive governance automation and introduces substantial independent logic across two workflows, a shared enforcement script, policy thresholds, and reconciliation behavior, creating multiple easy-to-miss failure modes with broad operational blast radius.
ⓘ  3 issues published inline · 5 in summary

Grey Divider

Tip of the day
💡 Did you know, you can tweak Display preferences with a live preview to see your comment before it ships

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

echo "::error::缺 GOVERNANCE_TOKEN——陈旧度不可查,fail-closed 变红(不盲 trip)" >&2
exit 2
fi
STALE_H="${STALE_HOURS_OVERRIDE:-$(python3 -c 'import yaml; print(yaml.safe_load(open("governance/policy/butler.yaml", encoding="utf-8"))["deadman_stale_hours"])' | tr -d '\r')}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Stale阈值读取路径错误 🐞 Bug ≡ Correctness

butler-heartbeat-watch 用 Python 从 governance/policy/butler.yaml 读取
deadman_stale_hours,但该字段实际位于 thresholds.deadman_stale_hours,导致命令替换失败并使该 workflow 永远 exit 2(infra
红、不 trip)。结果是仓内兜底层不可用,无法按 ADR-0074 预期自动触发缺席即停。
Agent Prompt
### Issue description
`butler-heartbeat-watch.yml` reads `deadman_stale_hours` from `governance/policy/butler.yaml` using `yaml.safe_load(...)["deadman_stale_hours"]`, but the policy file defines it under `thresholds.deadman_stale_hours`. This makes the Python snippet raise a KeyError, resulting in an empty `STALE_H` and the workflow exiting with infra (2), effectively disabling the stale watchdog.

### Issue Context
This watchdog is the ADR-0074 “repo-internal layer”; if it always fails infra, deadman will not be automatically tripped when the heartbeat workflow is broken/disabled.

### Fix Focus Areas
- .github/workflows/butler-heartbeat-watch.yml[48-49]
- governance/policy/butler.yaml[58-68]

### Suggested change
Update the Python expression to read the nested key:
- `...["thresholds"]["deadman_stale_hours"]`

Also consider adding a defensive default/error message if the key is missing, so it fails with a clear infra error rather than a confusing empty value.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +65 to +66
THRESH_S=$(( STALE_H * 3600 ))
echo "AUDIT | butler=heartbeat-watch | trigger=${BUTLER_TRIGGER:-schedule} | outcome=checked | actions={\"last_success\":\"${LAST:-none}\",\"age_hours\":\"$AGE_H\",\"threshold_hours\":$STALE_H}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

3. 小数阈值导致算术错误 🐞 Bug ☼ Reliability

butler-heartbeat-watch 允许 deadman_stale_hours 为小数(正则接受 3.5),但随后用 Bash 算术扩展 `THRESH_S=$((
STALE_H * 3600 ))` 计算秒数,Bash 不支持浮点会直接报错并使 job 失败。该失败会把“阈值合法但为小数”的配置误判成 infra 红。
Agent Prompt
### Issue description
`butler-heartbeat-watch.yml` validates `STALE_H` with a regex that allows decimals, but then uses Bash arithmetic expansion to compute seconds. Bash arithmetic is integer-only, so values like `3.5` will cause a runtime arithmetic error.

### Issue Context
Even if today’s default is `3`, the workflow explicitly documents/accepts decimals; future overrides (e.g., drills) can break the watchdog.

### Fix Focus Areas
- .github/workflows/butler-heartbeat-watch.yml[48-66]

### Suggested change (pick one)
1) If decimals are not needed: tighten validation to integers only (`^[0-9]+$`).
2) If decimals are desired: compute `THRESH_S` via Python:
   - `THRESH_S=$(python3 -c 'import sys; print(int(float(sys.argv[1])*3600))' "$STALE_H")`
   and then keep the `(( AGE_S <= THRESH_S ))` comparison integer-based.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

randypanding added a commit that referenced this pull request Aug 26, 2026
来源 PR 复查发现物(#176..#390 全量复查):
- 误提交 .pyc 出库:scripts/__pycache__(#230 引入)、
  specs/IR-0004/suite/__pycache__(#338)、specs/ISSUE-263/suite/__pycache__(#336);
  附 .gitignore(__pycache__/、*.pyc)防复发
- governance/metrics.py:移除恒未读局部 val(#253 引入,ADR-0073 计算库)
- governance/dashboard-update.py:TemporaryDirectory with 块内 isdir 恒真检查删除
- scripts/sli-report.sh:X if False else 死分支简化 + die 后不可达 OR 链残串清理
  (#184/#197-203/#357 落位文件,ADR-0059 口径零改动)

不改动:gate/verdict/drift 语义、workflow YAML、REPOS.yaml/治理数据、pinned SHA。

验证:python -m compileall(除既有 escalate-test-issue.py 语法错,
见 issue 392 另报);bash -n 全绿;governance/tests/test-*.sh 10 套全过;
sli-report --self-test PASS=8 FAIL=0;yaml 解析 OK。

Card: #392
ADR: ADR-0059(SLI 口径保持)、ADR-0073(metrics/dashboard 纯函数契约保持)
randypanding added a commit that referenced this pull request Aug 26, 2026
adversary-gate(ADR-0067/0082)对 specs/** 任意变更(含缓存删除)
fail-closed 要求 survived 审计。本卡为技术债清扫而非 spec 套件变更,
按保守原则不触发该审计面:specs/*/suite/__pycache__/*.pyc 暂留库,
作为已申报债务记录于 issue #392 与 PR 描述。

Card: #392

refactor: 构建产物出库 + 死代码移除(行为保持;近一周 PR 技债清扫)

来源 PR 复查发现物(#176..#390 全量复查):
- 误提交 .pyc 出库:scripts/__pycache__(#230 引入)、
  specs/IR-0004/suite/__pycache__(#338)、specs/ISSUE-263/suite/__pycache__(#336);
  附 .gitignore(__pycache__/、*.pyc)防复发
- governance/metrics.py:移除恒未读局部 val(#253 引入,ADR-0073 计算库)
- governance/dashboard-update.py:TemporaryDirectory with 块内 isdir 恒真检查删除
- scripts/sli-report.sh:X if False else 死分支简化 + die 后不可达 OR 链残串清理
  (#184/#197-203/#357 落位文件,ADR-0059 口径零改动)

不改动:gate/verdict/drift 语义、workflow YAML、REPOS.yaml/治理数据、pinned SHA。

验证:python -m compileall(除既有 escalate-test-issue.py 语法错,
见 issue 392 另报);bash -n 全绿;governance/tests/test-*.sh 10 套全过;
sli-report --self-test PASS=8 FAIL=0;yaml 解析 OK。

Card: #392
ADR: ADR-0059(SLI 口径保持)、ADR-0073(metrics/dashboard 纯函数契约保持)


(注:specs/*/suite/__pycache__/*.pyc 经重估移出本 PR——adversary-gate 对 specs/** 变更类 fail-closed 要求 survived 审计;作为已申报债务留档 issue #392 与 PR 描述。)
randypanding added a commit that referenced this pull request Aug 26, 2026
adversary-gate(ADR-0067/0082)对 specs/** 任意变更(含缓存删除)
fail-closed 要求 survived 审计。本卡为技术债清扫而非 spec 套件变更,
按保守原则不触发该审计面:specs/*/suite/__pycache__/*.pyc 暂留库,
作为已申报债务记录于 issue #392 与 PR 描述。

Card: #392

refactor: 构建产物出库 + 死代码移除(行为保持;近一周 PR 技债清扫)

来源 PR 复查发现物(#176..#390 全量复查):
- 误提交 .pyc 出库:scripts/__pycache__(#230 引入)、
  specs/IR-0004/suite/__pycache__(#338)、specs/ISSUE-263/suite/__pycache__(#336);
  附 .gitignore(__pycache__/、*.pyc)防复发
- governance/metrics.py:移除恒未读局部 val(#253 引入,ADR-0073 计算库)
- governance/dashboard-update.py:TemporaryDirectory with 块内 isdir 恒真检查删除
- scripts/sli-report.sh:X if False else 死分支简化 + die 后不可达 OR 链残串清理
  (#184/#197-203/#357 落位文件,ADR-0059 口径零改动)

不改动:gate/verdict/drift 语义、workflow YAML、REPOS.yaml/治理数据、pinned SHA。

验证:python -m compileall(除既有 escalate-test-issue.py 语法错,
见 issue 392 另报);bash -n 全绿;governance/tests/test-*.sh 10 套全过;
sli-report --self-test PASS=8 FAIL=0;yaml 解析 OK。

Card: #392
ADR: ADR-0059(SLI 口径保持)、ADR-0073(metrics/dashboard 纯函数契约保持)


(注:specs/*/suite/__pycache__/*.pyc 经重估移出本 PR——adversary-gate 对 specs/** 变更类 fail-closed 要求 survived 审计;作为已申报债务留档 issue #392 与 PR 描述。)

Co-authored-by: randypanding <randypanding@users.noreply.github.com>
randypanding added a commit that referenced this pull request Aug 31, 2026
…70) (#478)

* chore: 技术债清扫收尾——.pyc 编译产物出库 + escalate-test-issue.py 语法修复(ADR-0061/ADR-0070)

- git rm --cached 3 个误提交的 .pyc(specs/IR-0004/suite/__pycache__ ×2、specs/ISSUE-263/suite/__pycache__ ×1,源自 #230/#336/#338;.gitignore 防复发已在位)
- scripts/escalate-test-issue.py L97 f-string 内层 ASCII 引号改「」(#392 卡另报的语法错误——修复前 py_compile 失败,脚本不可运行)

卡内其余三项(metrics.py 死变量 / dashboard-update.py isdir 防御 / sli-report.sh 死分支)已在先前 PR 完成,本 PR 仅收尾。

* feat: 处理仓库其他Issues

Co-authored-by: traeagent <traeagent@users.noreply.github.com>

---------

Co-authored-by: randypanding <randypanding@users.noreply.github.com>
Co-authored-by: traeagent <traeagent@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants