Commit d28d0b4
fix(triage-dependabot): five bugs - SimpleCov, archived repos, branch-protection loop, stale-removal guard, prerelease close (#37)
* fix(triage-dependabot): detect SimpleCov coverage thresholds on Ruby repos
detect_repo_coverage() only inspected Python config files
(pyproject.toml, setup.cfg, Makefile, tox.ini, .coveragerc) so Ruby
repos using SimpleCov returned None. github/markup pins line and
branch coverage at 100 in test/test_helper.rb but every patch-bump
Dependabot PR there got flagged for review instead of auto-merging.
Adds a SimpleCov regex pass over test/test_helper.rb, spec/spec_helper.rb,
.simplecov, and Rakefile. Handles three forms:
SimpleCov.minimum_coverage 100
SimpleCov.minimum_coverage 80.5
SimpleCov.minimum_coverage line: 100, branch: 100
When line: and branch: are both present, takes the lower value
because the lowest gate is the one that fails the build first.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Zack Koppert <zkoppert@github.com>
* fix(triage-dependabot): skip archived repos before flagging to Q1
There was no archived-repo check in the main loop. PRs against
archived repos can never merge, but the tool was happily flagging
them to Q1 every hour. zkoppert/advanced-security-enforcer (archived)
PR #73 has been re-flagged repeatedly.
Adds is_archived_repo() with a process-lifetime cache, called right
after the dependabot-author check. When archived: log + mark thread
done + clean stale entries + record cooldown + continue. The
archived PR never enters the decision tree, so it never becomes a
flag-for-review candidate.
API failures fall back to False so a flaky GitHub does not suppress
real Dependabot work. New skipped_archived counter shows up in the
end-of-run summary.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Zack Koppert <zkoppert@github.com>
* fix(triage-dependabot): idempotent approve + branch-protection backoff
jmeridth/gh-health-files#59 logged 52 redundant approve events in
one day. The pathology:
1. Tool decides outcome=merge.
2. Auto-merge fails (repo has no repo-level auto-merge).
3. Fallback path approves the PR and runs a sync merge.
4. Sync merge fails with 'the base branch policy prohibits the merge'
because the repo requires a CODEOWNERS review.
5. Outer except records an error but does NOT extend the cooldown.
6. Next hour: cron picks the same PR up, re-approves, re-fails.
Three changes, all required to break the loop:
a. has_existing_approval() consults gh pr view headRefOid + reviews +
latestReviews and returns True when my login already has APPROVED
at the current head SHA. do_merge() now skips the approve fallback
in that case.
b. New _BRANCH_PROTECTION_MARKERS tuple (matched case-insensitively)
captures the rejection strings: base-branch-policy, required status
check, changes requested, review-required, at-least-1-approving-
review. Kept separate from _AUTO_MERGE_DISABLED_MARKERS so the
existing auto-merge -> approve fallback semantics are unchanged.
c. BranchProtectionBlocked is raised from do_merge when either the
auto-merge attempt or the sync merge attempt trips a branch-
protection marker. The run loop converts it into a flag-for-review
with a new 24h cooldown (BRANCH_PROTECTION_COOLDOWN_SECONDS),
marks the notification done, and never retries the same PR until
the cooldown lapses.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Zack Koppert <zkoppert@github.com>
* fix(triage-dependabot): defensive guard + diagnostic logging in stale-removal
A 2026-06-11 triage session reported the cron deleting a hand-curated
Q1 entry (core-ux-2746-author-me-copilot-coauthored-prs) that had no
notification field. Investigation of that cron log showed both
stale_removed=2 events were legitimate notification matches
(jmeridth/gh-health-files#58 and #59 - both PRs closed that hour with
matching thread_ids), and remove_stale_entries() already returns False
for items whose notification field is missing or not a dict. So the
dependabot cron is not the proximate cause - but the symptom is severe
enough that defense in depth plus diagnostic instrumentation is worth
shipping anyway.
Changes:
* remove_stale_entries() docs the invariant: never removes an item
without a dict-typed notification field, never removes anything when
both thread_id and pr_url are empty.
* Every removal now logs at INFO with the matched key (thread_id=... or
url=...), the bucket name (inbox / prioritized.q1_do_first / ...),
the item id, and the item title. Future stomps are now diagnosable
from the log file alone without reconstructing the input todo.yml.
* Regression test covers four entry shapes in the same bucket: no
notification field, notification: null, notification: {}, and one
legitimately-matched dependabot entry. Asserts only the matched
entry is removed and the diagnostic log line fires.
* Round-trip test verifies ruamel preserves an entry containing
IssueQuery#maybe_expand_author_for_agents (the Ruby method syntax in
the original lost item) plus backtick-quoted code, so YAML quoting
is ruled out as a future loss vector.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Zack Koppert <zkoppert@github.com>
* fix(triage-dependabot): read commit.oid from gh review json shape
has_existing_approval was reading review.get("commit_id") /
review.get("commitId"), but `gh pr view --json reviews,latestReviews`
emits the commit as a nested object: {"commit": {"oid": "<sha>"}}.
The flat keys are always absent in real gh output, so the head-SHA
guard always returned False and the function always returned False -
the consistent-approve skip never fired in production. The bug-3
recovery still worked because MergeBlockedByBranchProtectionError +
24h cooldown breaks the retry loop, but the first line of defense
was silently dead.
I now read commit.oid first, then fall back to commit.sha and the
flat commit_id/commitId fields so older or alternate gh shapes still
work. Updated the three existing tests to use the production-shape
nested commit object, added a new test that asserts the flat
commit_id fallback still works, and added a latestReviews coverage
test (the same nested shape applies there).
Verified against live gh output on PR #37:
gh pr view 37 --json reviews | jq '.reviews[0].commit'
-> {"oid": "d5eaf3a585..."}
Tests: 204 passed (was 202).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Zack Koppert <zkoppert@github.com>
---------
Signed-off-by: Zack Koppert <zkoppert@github.com>
Signed-off-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>1 parent 07ccbbc commit d28d0b4
3 files changed
Lines changed: 841 additions & 517 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
0 commit comments