Skip to content

fix(selfhost): disambiguate red-CI hold reasons and enrich hold audit metadata - #3288

Merged
loopover-orb[bot] merged 1 commit into
mainfrom
fix-auto-action-hold-audit
Jul 5, 2026
Merged

fix(selfhost): disambiguate red-CI hold reasons and enrich hold audit metadata#3288
loopover-orb[bot] merged 1 commit into
mainfrom
fix-auto-action-hold-audit

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Summary

  • agentHoldAuditDetail's ciState === "failed" branch returned a bare "auto-action held because CI is failing but no close action was planned" for every red-CI hold, unconditionally — it never checked protected-author or close-autonomy the way the gateBlockerCodes.length > 0 branch a few lines below it already did. That made "protected owner/admin/automation author" and "close autonomy isn't set to auto yet" indistinguishable from each other and from a genuinely unexplained hold — the single most common real-world cause of this exact opaque message.
  • Extracted the shared disambiguation into closeWithheldReason() and applied it to both branches, so a red-CI hold now reports "close withheld for protected author" or "close withheld because close autonomy is <level>" exactly like a gate-blocker hold already did.
  • Persisted the previously-missing structured fields on the agent.action.hold audit event's metadata: headSha, gateBlockerTitles, ciFailingCheckNames, closeEligible, closeAutonomy, mergeAutonomy, protectedAuthor (owner/admin/automation flags), closeOwnerAuthors, precisionBreakerEngaged/precisionBreakerDirections — so a hold is fully debuggable from the audit table alone, without re-deriving eligibility from settings by hand.
  • Investigated whether "contributor + red CI + close=auto" genuinely fails to plan a close (reported as the biggest concern): it does not. planAgentMaintenanceActions's willClose condition already covers this correctly (see the pre-existing "blocked contributor PR ... close=auto is actually closed" regression test), and this PR's new tests confirm a hold only ever reaches the generic fallback when the author is protected and close autonomy isn't auto — i.e. every reachable hold now has a specific, correct explanation rather than the previous unconditional opaque one.

This is the first of several follow-ups from a live Grafana/Loki review of the self-host stack; the other four (Orb relay registration alerting, Codex provider timeout fallback/correlation, retryable PR-freshness reclassification, and the maintenance-admission log-level downgrade) are separate, independently-isolated PRs in progress.

Scope

  • The PR title follows type(scope): short summary Conventional Commit format.
  • This PR is focused — one file (src/queue/processors.ts) plus its two test files, one coherent fix.
  • This follows CONTRIBUTING.md and does not reintroduce GitHub Pages, VitePress, site/, or CNAME.
  • No linked issue — a live-VPS-observed audit-transparency gap with the rationale above; no pre-existing tracked issue.

Validation

  • git diff --check
  • npm run typecheck
  • npx vitest run test/unit/precision-breakers-chain.test.ts test/unit/queue.test.ts — 512/512 passing, including new regression tests for: red-CI + protected author (owner/admin/automation) → specific reason; red-CI + closeOwnerAuthors: true → falls through correctly; red-CI + closeAutonomy: observe → specific reason; protected-author precedence over close-autonomy; and an end-to-end webhook-driven test asserting the enriched metadata_json shape.
  • npm run test:changed — 56 affected test files, 1577 tests, all green
  • npm run test:ci — full local gate, green
  • Verified via v8 coverage that every new/changed line and branch in this diff is exercised, except one defensive ?? null fallback marked /* v8 ignore next */ (a real GitHub PR always carries a head sha by the time it reaches this path — mirrors 8 other existing /* v8 ignore next */ annotations already in this same file for the same class of "structurally unreachable on the real webhook path" fallback).

Safety

  • No secrets, wallet details, hotkeys, coldkeys, user PATs, private keys, raw trust scores, private rankings, or private maintainer evidence are exposed.
  • No auth/cookie/CORS/GitHub App/Cloudflare/session changes.
  • N/A — no UI/API/OpenAPI behavior change (backend audit/logging only).
  • N/A — no visible UI change.
  • Behavior change is additive/diagnostic only: the actual close/merge/hold decision logic (planAgentMaintenanceActions, applyPrecisionBreakers) is untouched — only the after-the-fact audit description of an already-decided hold changed, plus new metadata fields on an existing audit event. No repo's automation behavior changes as a result of this PR.

Notes

  • Confirmed this repo's own .gittensory.yml doesn't need any change — this is a pure engine fix, not a production config change.

…nrich hold audit metadata

agentHoldAuditDetail's ciState==="failed" branch returned a bare, unexplained
"auto-action held because CI is failing but no close action was planned" for
EVERY red-CI hold, unconditionally -- it never checked protected-author or
close-autonomy the way the gate-blocker-codes branch a few lines below it
already did. That made a protected owner/admin/automation author and a
"close autonomy isn't auto yet" repo indistinguishable from each other, and
from a genuinely unexplained hold, in the single most common real-world
cause of this message on a self-hosted instance.

Extract the shared protected-author/close-autonomy disambiguation into
closeWithheldReason() and apply it to BOTH branches, so a red-CI hold now
reports "close withheld for protected author" or "close withheld because
close autonomy is <level>" exactly like a gate-blocker hold already does.

Also persist the previously-missing structured fields on the agent.action.hold
audit event's metadata (headSha, gateBlockerTitles, ciFailingCheckNames,
closeEligible, closeAutonomy, mergeAutonomy, protectedAuthor flags,
closeOwnerAuthors, precisionBreakerEngaged/directions) so a hold is fully
debuggable from the audit table alone.

Investigated whether contributor + red CI + close=auto genuinely fails to
plan a close (the reported "biggest product issue"): it does not --
planAgentMaintenanceActions' willClose condition already covers this case
correctly (see the existing "blocked contributor PR ... close=auto" regression
test), and this fix's own new tests confirm a hold only ever reaches the
generic fallback when the author is protected AND close autonomy isn't
auto -- i.e. every reachable hold now has a specific, correct explanation.
@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@loopover-orb loopover-orb Bot added the gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier. label Jul 5, 2026
@loopover-orb

loopover-orb Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Tip

🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩

✅ Gittensory review result - approve/merge recommended

Review updated: 2026-07-05 01:02:23 UTC

3 files · 1 AI reviewer · no blockers · readiness 100/100 · CI green · clean

✅ Suggested Action - Approve/Merge

  • safe to merge

Review summary
The change correctly centralizes the red-CI and gate-blocker hold explanation path so protected authors and non-auto close autonomy no longer collapse into the generic CI-failed message. The added audit metadata is useful and the visible tests cover the main regression plus the structured metadata shape. I do not see a reachable correctness break in the diff.

Nits — 4 non-blocking
  • nit: src/queue/processors.ts:2043 adds a very large historical comment around a small helper; keep the rationale, but trim the narrative so future readers can see the current contract quickly.
  • nit: src/queue/processors.ts:2705 duplicates the planner's close-eligibility formula for audit metadata, which is easy to drift from `planAgentMaintenanceActions`; consider exporting a small shared helper or adding a focused parity test if you keep it duplicated.
  • src/queue/processors.ts:2043: shorten the helper comment to the invariant, e.g. that CI-failed and gate-blocker holds must use identical close-withheld disambiguation.
  • src/queue/processors.ts:2705: move the close-eligibility calculation into a shared helper used by both the planner and audit metadata path, or add a test that fails if contributor/owner/admin/automation eligibility diverges.
Signal Result Evidence
Code review ✅ No blockers 1 reviewer
Linked issue ✅ No-issue rationale PR body explains why no issue is linked.
Related work ✅ No active overlap found No same-issue or scoped active PR overlap found.
Change scope ✅ 20/20 Low review scope from cached public metadata (no linked issue context).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 56 registered-repo PR(s), 46 merged, 423 issue(s).
Contributor context ✅ Confirmed Gittensor contributor JSONbored; Gittensor profile; 56 PR(s), 423 issue(s).
Gate result ✅ Passing No configured blocker found.
Review context
  • Author: JSONbored
  • Role context: owner (maintainer lane)
  • Public audience mode: oss maintainer
  • Lane context: Repository registration is not available in the local Gittensory cache.
  • Public profile languages: not available
  • Official Gittensor activity: 56 PR(s), 423 issue(s).
  • PR-specific overlap: none found.
Contributor next steps
  • Treat this as maintainer-lane context rather than normal contributor-lane activity.
  • No action.
  • Link the issue being solved, or explicitly explain why this is a no-issue PR.
Signal definitions
  • Related work = same linked issue, overlapping active PRs, or title/path similarity.
  • Change scope = cached public metadata such as size labels, draft state, and review-burden hints.
  • Validation posture = whether the PR provides enough public validation/test evidence for maintainer review.
  • Contributor workload = public contributor activity and cleanup pressure, not a repo-wide quality failure.
  • Contributor context = public GitHub/Gittensor identity context; non-Gittensor status is not a blocker.

🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed


💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →.

Checked by Gittensory, a quiet PR intelligence layer for OSS maintainers.

  • Re-run Gittensory review

@codecov

codecov Bot commented Jul 5, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.14%. Comparing base (3875335) to head (891a113).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #3288   +/-   ##
=======================================
  Coverage   94.13%   94.14%           
=======================================
  Files         276      276           
  Lines       30236    30246   +10     
  Branches    11016    11021    +5     
=======================================
+ Hits        28464    28474   +10     
  Misses       1127     1127           
  Partials      645      645           
Files with missing lines Coverage Δ
src/queue/processors.ts 92.98% <100.00%> (+0.02%) ⬆️
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@loopover-orb loopover-orb Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Gittensory approves — the gate is satisfied and CI is green.

@loopover-orb
loopover-orb Bot merged commit c6813b5 into main Jul 5, 2026
10 checks passed
@loopover-orb
loopover-orb Bot deleted the fix-auto-action-hold-audit branch July 5, 2026 01:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant