Skip to content

✨ dependencies: hold a PR on blocked until the PRs it depends on merge, across repos - #17689

Merged
hivecommons-hive[bot] merged 2 commits into
hivecommons:v6from
Danathar:feat/blocked-pr-depends-on
Nov 11, 2026
Merged

hivecommons-hive[bot] merged 2 commits into
hivecommons:v6from
Danathar:feat/blocked-pr-depends-on

Conversation

@Danathar

Copy link
Copy Markdown
Contributor

Summary

A PR whose body says Depends-on: #N or Depends-on: owner/repo#N now waits for those pull requests. The blocked sweep puts the existing blocked label on it, with the existing <!-- hive:blocked-on pr=owner/repo#N --> marker (one per open target). Every merge lane refuses a blocked PR. The sweep lifts the label with the existing <!-- hive:unblocked --> note once the targets have merged or closed, or are dropped from the body. People win: the hive never lifts a blocked a person set, and never puts its own back after a person removed it unless a new dependency appears. This is the design agreed per maintainer discussion: reuse blocked and the blocked-on marker, no new label or marker family.

Why

Some PRs must land after another PR, often in another repository. A common case is tests that need an upstream change. Agents already write Depends-on: lines (the default policies ask for them), but only the dashboard's critical path read them. A merge lane could land the PR first, so a person had to hold it and remember to release it.

How

Place. For each open, non-draft PR in a configured repo, the sweep reads the Depends-on: targets (ParseDependsOn). When at least one is an open pull request:

  • it adds blocked;
  • it posts one marker comment per open target.

The marker comment is written by postBlockedOnPRMarker, the PR branch of RecordVerdictBlocker moved into a shared helper. So the marker is byte-identical to the one a relay's blocked verdict writes. These targets place nothing:

  • an issue, which stays display-only (I did not use the issue= form for PRs);
  • a closed PR;
  • a target the App cannot read (404, no access). That one is logged once per PR and ref.

Respect people.

  • A blocked is the hive's only when its newest label event is a labeled by the App bot. The hard-suppress reconcile uses the same rule. Any other blocked is a person's: the sweep never places over it and never lifts it.
  • "Person released" is derived from the label timeline: the newest event is an unlabeled by a non-App actor, and the App labeled it earlier. In that case the sweep does not re-apply blocked for targets already marked on the PR. A newly listed open target re-arms it.

Lift. The blocked sweep now covers PRs too. It lifts the hive's own blocked once every marked target has merged or closed, or is no longer listed under Depends-on:, so a stale marker never holds the PR forever. The note:

  • starts Unblocked: owner/repo#N …, so the existing duplicate-note check still applies;
  • adds a clause when a target closed without merging.

Cross-repo targets are read by the owner/repo in the marker.

Lanes. The shared label gate labelBlockReason now refuses blocked as label:blocked. It is checked after the hold family, so a PR with both still reads as held. Every lane's prefilter goes through that gate, so a blocked PR is refused by:

  • the self-authored and trusted-bot lane;
  • the trusted-author lane;
  • the contributor lane, whatever exclude_labels says;
  • the queued merge-request lane, at listing, plus a re-check after the refetch;
  • the merge train, because it seats only on prefilter verdicts;
  • the native queue: label:blocked withdraws the App's own entry.

Outside the lanes:

  • the dashboard merge verdict (automergeBlockLabelReason) refuses it;
  • the dashboard's delegated human-merge shortcuts (delegatedHumanMergeGated) refuse it.

I chose the shared gate over adding blocked to DefaultTrustedAuthorExcludeLabels. That default only covers the trusted-author lane: the self-authored, trusted-bot and queued lanes have no exclude list. It would also leave out hives that set an explicit list. One side effect: a contributor PR wearing blocked now reports label:blocked instead of excluded-label:blocked. Two contributor-lane tests were updated for that.

Stage and parity. label:blocked joins label:hold and sentinel as a gate, not a stage (#17327). The PR's stage is its head's real progress. The route carries the gate reason blocked on owner/repo#N, taken from the marker the sweep last read, or "the blocked label is set" when no marker has been read. So the 090 entry comment reads "Held: blocked on …". On the activity monitor, the PR row now gets blocked_on_pr and the caption clause Blocked on PR #N — lifts when it merges, the same fields a blocked issue already has. The MCP stage_tracker_read tool reads the same /api/stage-tracker payload, so the API test covers both. In prs_by_band a blocked PR was already in the waiting band (the default waiting labels include blocked).

hive-open-issue. comment --blocked-on-pr now takes owner/repo#n as well as <n>. The request carries blocked_on_pr_repo, and the watcher reads that repository.

Defaults and write-back gate

The behaviour is on for every hive, with no new setting. The writes, the label with its marker comments and the lift with its note, go through the write-back gate as gate_comment. That is the kind RecordVerdictBlocker and the marker-less blocked ask already use for the same marker and label, so no new kind or kind-policy parity was needed. The sweep needs the App bot login to tell its own label from a person's, and does nothing without one.

Interplay with the human-merge-path holds (#17609, #17622)

blocked is not a hold source. OtherHoldSourcesForPR does not count it (a test pins this), so the human-merge-path hold still lifts when its own reason goes, while the lanes keep refusing the PR on blocked. Each label is lifted by its own sweep. applyHumanMergePathFenceRoute reads only hold-class labels, so it is unchanged; the blocked gate rides the route as a separate gate.

Docs

  • labels-and-control-signals/reference.md: blocked applies to Issues and PRs, with the PR placement and lift rules.
  • stage-labels.md: the gate row now lists label:blocked.
  • operator-reference/README.md: the exclude-list rows say blocked is always refused.
  • hive-open-issue.md: the cross-repo form.
  • dashboard.md: the Depends-on critical path.
  • notifications.md: postBlockedOnPRMarker replaces RecordVerdictBlocker as the protocol_command site.
  • changelog.d/added-blocked-pr-depends-on.md.

Tests

None were run locally (AGENTS.md); CI is the verdict. gofmt -l and bash -n are clean. No JS or static/ change, so index.html is untouched.

  • pr_blocked_depends_on_test.go:
    • same-repo plus cross-repo targets: one label and one marker per target, and a second pass is a no-op;
    • nothing is placed for an unreadable target (logged once), a draft, an issue-only or closed target, or a PR a person already blocked;
    • a person's release is respected, and a new target re-arms the hold;
    • lift cases: all merged; one still open (kept); target dropped from the body; closed unmerged (with the clause); person-applied (never lifted);
    • write-back gate off writes nothing;
    • no App login does nothing;
    • OtherHoldSourcesForPR ignores blocked;
    • the watcher reads blocked_on_pr_repo.
  • automerge_blocked_gate_test.go: every lane prefilter refuses blocked, the merge train does not seat it, the native queue withdraws it, and the gate clause names the target.
  • derive_pr_blocked_gate_test.go: label:blocked classifies exactly like label:hold on every lane.
  • stage_tracker_blocked_pr_test.go: the PR row's blocked_on_pr and caption, and the delegated human-merge gate.
  • merge_eligibility_blocked_test.go: the dashboard merge verdict.
  • bin/test_hive_open_issue.sh: --blocked-on-pr owner/repo#n.

Ratchets: no raw comment emitter was added; the marker comment goes through the shared helper. New write calls carry // writeback:gated behind a gate check, so the direct-writes baseline is unchanged.

Out of scope and follow-ups

🤖 Generated with Claude Code

https://claude.ai/code/session_01BQYnPGFJx81c8JXR3o1sg1

… on merge, across repos

A PR body's `Depends-on: #N` / `owner/repo#N` lines were read only by the
dashboard's critical path; nothing stopped a merge lane from landing the PR
before its prerequisite, so a person had to hold it by hand.

- The blocked sweep now puts the existing `blocked` label on an open,
  non-draft PR whose Depends-on names an open pull request (same repo or
  another), with one `<!-- hive:blocked-on pr=owner/repo#N -->` comment per
  open target, written by the same helper a relay's blocked verdict uses
  (postBlockedOnPRMarker, refactored out of RecordVerdictBlocker).
- It lifts its own `blocked` with the existing `<!-- hive:unblocked -->`
  note once every marked target merged or closed (the note names one that
  closed without merging) or is no longer listed.
- A person's `blocked` is never placed over or lifted; a person removing
  the hive's `blocked` is not overridden for the same refs (newest
  unlabeled event by a non-App actor after an App labeled event); a new
  open target re-arms it. Unreadable targets place nothing, logged once.
- Writes go through the write-back gate as gate_comment.
- The shared lane label gate (labelBlockReason) refuses `blocked` as
  label:blocked, so the self-authored/trusted-bot, trusted-author,
  contributor and queued lanes, the merge train and the native queue
  (withdraw) all refuse it; the queued lane re-checks after refetch; the
  dashboard merge verdict and human-merge shortcuts refuse it too.
- Stage: label:blocked is a held gate like label:hold; the route's gate
  reason reads "blocked on owner/repo#N", and the activity monitor PR row
  carries blocked_on_pr and the "Blocked on PR #N" caption.
- hive-open-issue comment --blocked-on-pr accepts owner/repo#n.

Docs: blocked reference (Issues and PRs), stage labels, operator
reference, hive-open-issue, dashboard critical path, notifications.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BQYnPGFJx81c8JXR3o1sg1
Signed-off-by: Danathar <Danathar@users.noreply.github.com>
@kubestellar-prow kubestellar-prow Bot added the dco-signoff: yes Indicates the PR's author has signed the DCO. label Nov 11, 2026
@kubestellar-prow

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign kellyaa for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@kubestellar-prow kubestellar-prow Bot added the size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. label Nov 11, 2026
@hivecommons-hive

hivecommons-hive Bot commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

@Danathar this PR is back in your court (stage/072-returned-to-contributor). The hive will not merge it until the head changes.

Review asks:

  • the review swarm requested changes

Reply here or push a new head to this branch (same sign-off); CI and the review swarm re-run automatically and the PR returns to the normal path.

Queued in your relay fix queue (1 waiting): fixes are handed out before new work as soon as you have capacity.

@hivecommons-hive

Copy link
Copy Markdown
Contributor

Triage of the red checks on f221525:

  • contract-tests (hive-open-issue helper tests, exit 2 with no FAIL: line) — PR-local. The three new --blocked-on-pr cases in bin/test_hive_open_issue.sh use --repo "acme/widget", but acme is in the script's own placeholder-owner refusal list (case "$REPO_OWNER_LC" in … acme …) so hive-open-issue.sh exits 2 before writing a request; the test file runs under set -e and the first run_script "blockedonprbot" comment --repo "acme/widget" … line aborts the step. Fix: use the owner the sibling cases already use (hiveorg/widgets, e.g. --blocked-on-pr "hiveorg/gadgets#42"), or export HIVE_REPOS for those cases so the repo is in scope. The malformed case (acme/gadget#) will still exit 2 for the right reason once the repo is accepted.
  • build-and-test — downstream of contract-tests (contracts=failure); clears with the fix above.
  • NOTICE matches the module graph — runner infrastructure, not this diff: go-licenses could not resolve a module source (go-import lookup timed out) and emitted Source: Unknown. Do not commit the generated NOTICE; it needs a rerun.

Fork PR, so the hive is not pushing to this branch.


🐝 Hive Agent: scanner | Instance: hosted-available-oke-11-placeholder-r05x | SHA: unknown

— hive: agent=scanner backend=copilot model=claude-fable-5.1 copilot=1.0.88

@hivecommons-hive hivecommons-hive 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.

The red contract-tests lane is a test-fixture problem, not a logic problem, and it is a one-line fix.

Why CI is red. bin/hive-open-issue.sh:352 lists acme as a documentation-placeholder owner and exits 2 for any --repo acme/... that is not in $HIVE_REPOS (hive-open-issue.sh:369). The three new cases in bin/test_hive_open_issue.sh:250, :264 and :278 all pass --repo "acme/widget", so the first one exits 2 before writing a request and the test script's set -e aborts the run — which is why the job log stops after the --parent section's "Second child" with Process completed with exit code 2 and no FAIL: line. I reproduced it by running the head's script directly: --repo acme/widget 7 --body x --blocked-on-pr acme/gadget#42 --dry-run → "--repo 'acme/widget' is a documentation placeholder", rc=2. Switching those three to hiveorg/widgets (the owner every other case in the file uses) should fix it. The --blocked-on-pr acme/gadget#42 target is fine — it is only matched by the regex at :287, not by the repo gate. build-and-test is red only because it requires the contract lane; NOTICE matches the module graph failed on a DNS timeout to gopkg.in from the runner, which is infrastructure.

Correctness read of the sweep (src/pkg/github/pr_blocked_depends_on.go, head f221525) — I read it against the existing latestLabelEventByApp / postBlockedOnPRMarker / unblockItem paths and did not find a defect:

  • Ownership test at :206-209 (latestKind == "labeled" && appIsLatest) matches what latestLabelEventByApp (pr_level_hold.go:290-294) now derives from the same labelEventHistory, so the sweep and the hard-suppress reconcile agree on whose blocked it is.
  • Lift path keeps the block on a target read error (:286-289) and lifts only when every marked target is merged/closed/unlisted; a hive blocked with no markers is left alone (:270). That is the conservative direction.
  • Placement on a person-released PR (:244-249) re-arms only when fresh (open, unmarked) is non-empty; a body re-listing an already-marked target re-arms too, as the comment at :235-237 says. Consistent with the body's "respect people" rules.
  • Per-pass cache keyed by lowercased owner/repo#n means one Issues.Get per target per sweep across all repos. blockedPRUnreadable is process-lifetime and unbounded, but grows one entry per (PR, bad ref) pair, so it is bounded by the open-PR population.

Body and diff agree on everything I checked. The change is large (32 files) and touches every merge lane's prefilter, so a lane owner should still read labelBlockReason and the native-queue withdrawal; the only thing I can say with certainty is that the one visible failure is the fixture owner above.

— hive: agent=reviewer backend=copilot model=claude-fable-5.1 copilot=1.0.88

@hivecommons-hive

hivecommons-hive Bot commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

Hive status — updated in place; edits do not notify. Full history is in the Activity Monitor.

Review findings from reviewer-2 (folded here because this head already carries its hive review):

CI triage for the red checks at f221525 — one is this PR's, two are not.

contract-tests (and therefore build-and-test) fail because of the new test cases, not the script change.

  • bin/test_hive_open_issue.sh:250 runs run_script "blockedonprbot" comment --repo "acme/widget" 7 … --blocked-on-pr "acme/gadget#42".
  • bin/hive-open-issue.sh:352 lists acme among the documentation-placeholder owners, so that call refuses with exit 2 ("--repo 'acme/widget' is a documentation placeholder") before anything is parsed.
  • The test file runs under set -euo pipefail (line 8) and run_script (line 62–65) does not tolerate a non-zero exit, so the suite aborts right there — which is why the job log ends at PASS: comment --blocked-by refusal writes no request with exit code 2 and no FAIL: line.
  • The same applies to blockedonprsamebot (line 263) and badblockedonprbot (line 278); the last one would also "pass" for the wrong reason, since the placeholder refusal fires before the owner/repo#n regex at hive-open-issue.sh:287.

I verified the script change itself does what the test intends: comment --dry-run --repo hiveorg/widgets 7 --blocked-on-pr "hiveorg/gadget#42" emits "blocked_on_pr": 42, "blocked_on_pr_repo": "hiveorg/gadget", and a bare 41 emits no blocked_on_pr_repo. Switching the three new cases to a non-placeholder owner (the rest of the file uses hiveorg/widgets) should turn contract-tests green.

Not this PR's fault:

  • NOTICE matches the module graph — the job log says so itself: go-licenses returned Source: Unknown for gopkg.in/natefinch/lumberjack.v2 and gopkg.in/yaml.v3 after a go-import lookup timeout. The diff touches no go.mod/go.sum. A re-run should clear it.
  • Sign-off survives the squash / verify PR contents show CANCELLED from a superseded run; the latest runs of both are green.

Scope note: this comment is CI triage only. I have not reviewed the ~1,000 lines of Go in src/pkg/github/pr_blocked_depends_on.go and the automerge/stage-tracker changes; that needs a proper read once CI is green.

Review findings from adjudicator-2 (folded here because this head already carries its hive review):

Security perspective, read at head f221525.

1. The sweep lifts a hive blocked that another path placed, while its target is still open — src/pkg/github/pr_blocked_depends_on.go:265-269

blockedPRMarkedTargets (:386-404) treats every <!-- hive:blocked-on pr=… --> comment as this sweep's own placement. But two other App-authored paths write the byte-identical marker plus blocked on a pull request without any Depends-on: line in the body:

  • hive-open-issue comment --repo o/r <PR> --blocked-on-pr N — the watcher only refuses PRs for --needs-decision (issue_request_watcher.go:767-775); the --blocked-on-pr branch (:741-757, :861-865) posts blockedNoteBody (= BlockedOnMarker, issue_blocked.go:1208-1213) and adds blocked on whatever <number> is.
  • RecordVerdictBlocker for a PR item (verdict_blocker.go:128-136), now routed through the shared postBlockedOnPRMarker.

Before this PR, SweepBlockedIssues skipped pull requests (issue_blocked.go:1179), so such a hold stayed until a person acted. After it, reconcileBlockedPR sees blocked with the App as latest labeler, marked = {N}, deps = ∅, and liftOrKeepBlockedPR emits N is no longer listed under Depends-on: for the marker (:265-269) → unblockItem + note (:293-300), on the first pass, with N still open. Every lane then takes the PR again. The file comment ("A PR the hive blocked without any marker … is left alone") does not cover a marker from a different writer. Tests cover "target dropped from the body" but not "marker never came from a Depends-on: line".

Suggested fix: only treat a marker as droppable-by-body when it is this sweep's own comment; otherwise fall through to the target read so the block holds until N settles.

--- a/src/pkg/github/pr_blocked_depends_on.go
+++ b/src/pkg/github/pr_blocked_depends_on.go
@@ func (c *Client) reconcileBlockedPR(
-	marked := blockedPRMarkedTargets(comments)
+	marked, viaDependsOn := blockedPRMarkedTargets(comments)
@@
-		c.liftOrKeepBlockedPR(ctx, repo, pr, labels, deps, marked, fresh, cache, res)
+		c.liftOrKeepBlockedPR(ctx, repo, pr, labels, deps, marked, viaDependsOn, fresh, cache, res)
@@ func (c *Client) liftOrKeepBlockedPR(
-	for _, t := range marked {
-		if !listed[t.key()] {
+	for _, t := range marked {
+		// Only a marker this sweep wrote from a Depends-on line may be
+		// dropped by editing the body; a --blocked-on-pr or verdict
+		// marker holds until its target settles.
+		if !listed[t.key()] && viaDependsOn[t.key()] {
 			clauses = append(clauses, t.ref()+" is no longer listed under `Depends-on:`")
 			continue
 		}
@@ func blockedPRMarkedTargets(
-func blockedPRMarkedTargets(comments []*gh.IssueComment) []blockedPRTarget {
+func blockedPRMarkedTargets(comments []*gh.IssueComment) ([]blockedPRTarget, map[string]bool) {
 	seen := map[string]bool{}
+	viaDependsOn := map[string]bool{}
 	var out []blockedPRTarget
 	for _, cm := range comments {
 		for _, m := range blockedOnMarkerRE.FindAllStringSubmatch(cm.GetBody(), -1) {
 			...
 			t := blockedPRTarget{repo: m[1], number: n}
+			if strings.Contains(cm.GetBody(), "says `Depends-on: ") {
+				viaDependsOn[t.key()] = true
+			}
 			if !seen[t.key()] {

(or carry an explicit <!-- hive:blocked-on-source depends-on --> line in blockedPRDependsOnMarkerComment and key on that — sturdier than prose matching.) Please add a test: App blocked + --blocked-on-pr marker, body without Depends-on:, target open → kept.

2. Low — untrusted body drives unbounded cross-repo reads and a growing process map — pr_blocked_depends_on.go:339-371

ParseDependsOn (depends_on.go:34-53) accepts any owner/repo#N; each distinct ref costs one Issues.Get per pass with the App token (:358), and an unreadable ref adds a key to the package-level blockedPRUnreadable map (:343-347) that is never pruned. A contributor body with hundreds of refs to private or non-existent repos is both a per-pass rate-limit drain and a slow memory leak; the marker/lift notes also confirm open/merged/closed state of any PR the App can read. Capping refs per PR (e.g. first 10) and bounding or pruning the map would close it.

Review findings from reviewer (folded here because this head already carries its hive review):

Style / maintainability (read at head f221525): the sweep mirrors SweepBlockedIssues well and reuses the existing marker, note and gate helpers; nothing blocking. Two low notes with fixes:

  1. blockedPRUnreadable is a process-lifetime set that only grows — src/pkg/github/pr_blocked_depends_on.go:87-91, written at :361-367. A key is set on the first failed read and never cleared, so (a) the map grows with every PR×ref that ever failed, and (b) if a target later becomes readable and then unreadable again, the warning is never re-logged. The test reaches into the map directly (pr_blocked_depends_on_test.go:285) rather than through a ForTest hook like NoteBlockedOnSeenForTest. Suggest clearing the key on a successful read so the set tracks currently unreadable pairs:

     func (c *Client) openBlockedPRTargets(...) []blockedPRTarget {
     	var out []blockedPRTarget
     	for _, t := range targets {
     		st := c.blockedPRTargetRead(ctx, t, cache)
    +		key := strings.ToLower(repo) + "#" + strconv.Itoa(number) + "|" + t.key()
     		if st.err != nil {
    -			key := strings.ToLower(repo) + "#" + strconv.Itoa(number) + "|" + t.key()
     			blockedPRUnreadable.Lock()
     			...
     			continue
     		}
    +		blockedPRUnreadable.Lock()
    +		delete(blockedPRUnreadable.m, key)
    +		blockedPRUnreadable.Unlock()
     		if st.isPR && st.open {
  2. BlockedOnSeenReason duplicates the locked read in blockedFilterReasonFor — new pr_blocked_depends_on.go:98-105 vs existing issue_blocked.go:788-794 (same lock/lookup/blockedFilterReasonPrefix + " on " shape). A small blockedOnSeenRef(repo, number) string accessor next to noteBlockedOnSeen/forgetBlockedOnSeen would let both call it and keep the map's access surface in one file.

Nit: first := append(append([]blockedPRTarget(nil), open...), fresh...)[0] (pr_blocked_depends_on.go:278) allocates a slice to pick one element; first := fresh[0]; if len(open) > 0 { first = open[0] } reads more directly.

Review findings from adjudicator (folded here because this head already carries its hive review):

Correctness review at head f221525.

The lift rule treats every hive-written blocked-on marker on a PR as a Depends-on placement, so a blocked the watcher parked on a PR is lifted on the first pass while its target is still open.

liftOrKeepBlockedPR (src/pkg/github/pr_blocked_depends_on.go:263-277) lifts the hive's blocked when every marked target is either settled or "no longer listed under Depends-on:" — without reading the target in the not-listed case. blockedPRMarkedTargets (:408) collects any <!-- hive:blocked-on pr=… --> marker, not just this sweep's.

But this sweep is not the only hive writer of that marker + label on a pull request: hive-open-issue comment <number|url> --blocked-on-pr N goes through handleOneIssueRequest, which never checks that req.Number is an issue (src/pkg/github/issue_request_watcher.go:741-760 posts blockedNoteBody with the marker and :862 adds BlockedLabel; the only issueIsPullRequest guard there is on the NeedsDecision branch, :767-775). Before this PR such a PR was simply never swept (listOpenBlockedIssues skips IsPullRequest(), issue_blocked.go:1179), so the block held until a person lifted it. After this PR, on the next pass: label is App-latest, marked=[N], body has no Depends-on: → clause "no longer listed" → unblockItem + note, even though N is open. That inverts the watcher's contract ("the blocked sweep lifts the label when the PR merges or closes", issue_request_watcher.go:166-167). TestSweepBlockedPRDependencies_Lift (pr_blocked_depends_on_test.go:348) only exercises markers this sweep wrote, so it does not catch this.

Suggested fix: only let "dropped from the body" count as settled for markers this sweep wrote; for any other hive marker, read the target and keep the block while it is open.

--- a/src/pkg/github/pr_blocked_depends_on.go
+++ b/src/pkg/github/pr_blocked_depends_on.go
@@ func (c *Client) liftOrKeepBlockedPR(
-	for _, t := range marked {
-		if !listed[t.key()] {
+	for _, t := range marked {
+		// Only a marker this sweep wrote came from a Depends-on line, so only
+		// that one lifts when the line is dropped; another hive writer's
+		// marker (the watcher's --blocked-on-pr) holds until its PR settles.
+		if !listed[t.key()] && t.dependsOn {
 			clauses = append(clauses, t.ref()+" is no longer listed under `Depends-on:`")
 			continue
 		}
@@ type blockedPRTarget struct {
 	repo   string
 	number int
+	// dependsOn: the marker comment was written by this sweep.
+	dependsOn bool
 }
@@ func blockedPRMarkedTargets(comments []*gh.IssueComment) []blockedPRTarget {
-			t := blockedPRTarget{repo: m[1], number: n}
+			t := blockedPRTarget{repo: m[1], number: n, dependsOn: strings.Contains(cm.GetBody(), "says `Depends-on: ")}

(key() ignores the new field, so dedupe is unchanged; a target both this sweep and the watcher marked keeps the first-seen flag — if you want "any Depends-on marker wins", OR the flag when seen[t.key()].) A test with a blockedNoteBody-style marker, no Depends-on: line and an open target asserting the label is kept would pin it.

Advisory (not blocking): listOpenPRsForBlockedSweep caps at blockedPRSweepMaxPages = 5 (:47, 500 open PRs); a hive-blocked PR past that page is neither re-marked nor lifted and nothing logs the truncation.

Evidence bundle `#1768993948aa` sha256:c369b00e219c19d870234cdd0841080af7990b292b96854c42599dd2e67a82c6 — download from the dashboard or `hivectl review evidence #17689`.

@hivecommons-hive

hivecommons-hive Bot commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

Review follow-ups (below review.backlog.min_severity: P2)

Non-blocking findings below the backlog filing line are listed here instead of as issues, one comment per PR, updated in place. Tick them off as they are fixed; set review.backlog.min_severity: P3 to file every finding.

  • P3 low correctness — Open-PR listing capped at 5 pages silently skips hive-blocked PRs beyond 500 (src/pkg/github/pr_blocked_depends_on.go:47): listOpenPRsForBlockedSweep stops after blockedPRSweepMaxPages=5 (500 open PRs) with no log; a PR this sweep blocked that falls past that page is neither re-marked nor lifted. Advisory; large repos on…
  • P3 low intent-alignment — Depends-on PR sweep has no per-pass lookup budget, unlike the issue sweep (src/pkg/github/pr_blocked_depends_on.go:383): SweepBlockedPRDependencies (pr_blocked_depends_on.go:117) lists every open PR in every configured repo (up to 5 pages each) and, for each PR with a Depends-on line, issues one Issues.Get per distinct…

hive-open-issue refuses acme/widget as a documentation placeholder, so
the new section exited 2 before its checks ran.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BQYnPGFJx81c8JXR3o1sg1
Signed-off-by: Danathar <Danathar@users.noreply.github.com>
@hivecommons-hive
hivecommons-hive Bot merged commit 7b3f1aa into hivecommons:v6 Nov 11, 2026
69 of 70 checks passed
@hivecommons-hive

Copy link
Copy Markdown
Contributor

merged by Hive trusted-author auto-merge: author @Danathar holds role owner and GitHub permission maintain on repo; CI green at 93948aa.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dco-signoff: yes Indicates the PR's author has signed the DCO. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant