Skip to content

🐛 Delegated decision on a capability gap: model the blocker, stop the un-park/re-park loop, name the missing App permission - #15134

Merged
hivecommons-hive[bot] merged 10 commits into
v5from
fix/capability-blocker-decision
Oct 29, 2026
Merged

hivecommons-hive[bot] merged 10 commits into
v5from
fix/capability-blocker-decision

Conversation

@clubanderson

Copy link
Copy Markdown
Member

Fixes #15099

What happened on hive's hive

#14976 was parked needs-human by the scanner because the remaining retry wrappers live in .github/workflows/*, "which the App token cannot push (no workflows permission)". The operator ran Let hive decide → the delegate applied approved-direction and un-parked it → the scanner re-applied needs-human with the same text an hour later → "I tried 'let hive decide' and it still needs human." The delegated decision answered a question nobody asked: the blocker is a capability gap, which no direction can clear, and the un-park → re-park loop would repeat forever. stage/045-needs-decision and approved-direction were also both on the issue.

What this PR does

  1. Blockers are first-class (pkg/blocker): kinds capability:workflows-permission, capability:repo-write, capability:branch-protection, capability:secret, human-only:cla, human-only:re-asked; a standard marker <!-- hive:blocker kind=… -->; prose inference for reports written without it; a persisted ledger (/data/item-blockers.json). Recorded at every comment path: issue_comment webhook, task-list sweep (adds the marker to the remainder comment it writes), and bin/gh-wrapper.sh (adds the marker to a needs-human comment that names a missing capability).
  2. "Let hive decide" on a capability blocker returns no-effect (reason blocker, in the glossary) and writes nothing: "Hive can't do this one: the GitHub App has no workflows permission, so it cannot push changes to .github/workflows/*." decision_needed.options carries the ways out with real handlers: (a) Grant the App workflows permission → App settings link + Re-check permissions (POST …/blocker/recheck probes the installation and releases everything blocked on that permission); (b) Prepare the patch for a person to push — disabled with its reason (no human-authorised push identity path exists today; checked shared_uid_read_token / operator PAT paths, both read-only or hive-identity); (c) Mark partially addressed and close (POST …/blocker/close-partial); (d) Keep waiting. "Let hive decide" stays listed but disabled with the sentence, not greyed silently.
  3. Loop guard: a delegated un-park records the reason it cleared; the same reason re-applied afterwards parks the row on a blocker (human-only:re-asked when no capability is named) and disables the delegate until the reason changes. A cleared_at guard stops re-observed old comments from re-blocking after a permission grant.
  4. Label consistency: 045 and approved-direction never coexist. Rule: needs-human next to approved-direction is by construction the newer signal (the un-park removes needs-human when it applies the ack), so the stage pass keeps 045 and clears approved-direction, in both the blocker and plain cases. This deviates from "approved-direction wins" in the issue on purpose: a person or sweep that re-parks after a delegated decision must never be silently un-parked again — that is exactly the loop.
  5. Global surfacing: /api/status.capabilities = {missing: [...], blocked_items: N, by_permission: {...}}; ⚙ → Security shows GitHub App permissions: missing workflows · N items blocked with the settings link.
  6. Tracker: 14976's pill reads Blocked · App lacks workflows permission with the popover above (and 🐛 Activity Monitor: Ready for human merge offers Approve & merge / let hive merge; every needs-human state has its action #15087-style consequences on each option).

Tests

Go: pkg/blocker (marker parse, inference, ledger persist/loop guard/cleared-at), pkg/github (webhook issue_comment → blocker, sweep marker, stage facts → blocked: … + ack cleared), pkg/stageengine (derive reconcile), pkg/dashboard (decisionForBlocker options, delegated no-effect with no GitHub writes, loop guard, recheck 404/409/401/403/missing/granted/probe-failure, close-partial, status payload), glossary family lookup + SPA mirror. Node fixtures via stdin: pill word, blocker actions/options/url/role gating, popover HTML, request shapes, capabilityGapsText/securityCapabilitiesHtml. bin/gh-wrapper.test.sh: marker added / not duplicated / plain needs-human untouched (94 pass).

Docs

labels-and-control-signals.md (Blockers section: marker, kinds, reconcile rule; glossary row), dashboard-sections.md (Blocked pill — capability gaps), operator-reference.md (GitHub App permissions table and what each unblocks), api-reference.md (new endpoints, capabilities, blocker on tracker items), changelog.d/fixed-capability-blocker-decision.md.

@kubestellar-prow kubestellar-prow Bot added the dco-signoff: yes Indicates the PR's author has signed the DCO. label Oct 28, 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 clubanderson 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 Oct 28, 2026
@hivecommons-hive

Copy link
Copy Markdown
Contributor

@clubanderson is auto-promoted for contributor-lane merging on this PR: the last 5 merged PRs by this author in this hive's repositories have no revert or sentinel-alert evidence. CI, reviewer verdict, diff fences, soak, holds and branch protection still apply.

@github-actions

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Open PR overlap check

Base branch: v5

Conflicts are checked with every PR merged onto the current tip of v5, so a PR that only needs a rebase is not reported as conflicting with its siblings.

PR Title Relation Files
#15297 🐛 fix: clarify dashboard item and row counters same files src/docs/dashboard-sections.md
src/pkg/dashboard/static/index.html
#15301 🐛 Let hive decide: close already-fixed PRs instead of adopting them same files src/docs/api-reference.md
src/docs/labels-and-control-signals.md
src/pkg/dashboard/api_repo_item_decide_delegated.go
src/pkg/dashboard/stage_tracker_row_actions_test.go
src/pkg/dashboard/static/index.html
src/pkg/delegatedecision/decision_glossary.go
src/pkg/delegatedecision/decision_glossary_test.go
#15294 🐛 fix(writeback): gate adoption close-first writes instead of baselining them same files src/docs/operator-reference.md
#15306 ✨ React to failed CI checks via webhook instead of waiting for the next sweep CONFLICT src/docs/api-reference.md
#15303 🌱 refactor: split API reference into area files CONFLICT src/docs/api-reference.md
#15305 ✨ feat(webhook): react to failed CI checks within seconds of the delivery same files src/pkg/github/webhook_cache.go
#15304 🌱 docs: split api-reference into per-area files CONFLICT src/docs/api-reference.md
#15253 🐛 decision toast: issue decisions no longer read as 'approved for merge' CONFLICT src/pkg/dashboard/static/index.html
src/pkg/delegatedecision/decision_glossary.go
#15236 🐛 Prefer covering PR progress in Activity Monitor messages same files src/pkg/dashboard/stage_tracker.go
#15282 📖 docs: make Hive guides easier to follow same files src/docs/dashboard-sections.md
src/docs/labels-and-control-signals.md

@github-actions github-actions Bot added the conflicts-with-open-pr Open PR has a merge conflict with another open PR label Oct 28, 2026
@hivecommons-hive hivecommons-hive Bot added author/human A person pushed this PR themselves dco All commits are DCO signed-off role/trusted Author is trusted for unattended merge (trusted-author role or registry/claim tier in require_tiers) stage/071-needs-author Human-authored PR: CI red, changes requested or conflicting; diagnosis posted, the author must act labels Oct 28, 2026
@hivecommons-hive

Copy link
Copy Markdown
Contributor

@clubanderson this PR is held at stage/071-needs-author and the hive will not merge it yet.

Failing checks:

  • relative links and anchors resolve

Next: the hive does not push to your branch. Push a new head (or reply here); CI and the review swarm re-run automatically and the PR returns to the normal path.

@hivecommons-hive

Copy link
Copy Markdown
Contributor

@clubanderson this PR is held at stage/071-needs-author and the hive will not merge it yet.

Failing checks:

  • relative links and anchors resolve

Next: the hive does not push to your branch. Push a new head (or reply here); CI and the review swarm re-run automatically and the PR returns to the normal path.

@hivecommons-hive hivecommons-hive Bot added stage/050-review-pending Review swarm requested; awaiting a verdict for the current head stage/071-needs-author Human-authored PR: CI red, changes requested or conflicting; diagnosis posted, the author must act and removed stage/071-needs-author Human-authored PR: CI red, changes requested or conflicting; diagnosis posted, the author must act stage/050-review-pending Review swarm requested; awaiting a verdict for the current head labels Oct 28, 2026
@hivecommons-hive

Copy link
Copy Markdown
Contributor

@clubanderson this PR is held at stage/071-needs-author and the hive will not merge it yet.

Failing checks:

  • build (linux/amd64, true, false, 60)
  • build (linux/arm64, true, true, 75)
  • build-hub (linux/amd64, true, false, 60)
  • build-hub (linux/arm64, true, true, 75)
  • test (dashboard 1/4)
  • test (dashboard shuffle 1/3)

Next: the hive does not push to your branch. Push a new head (or reply here); CI and the review swarm re-run automatically and the PR returns to the normal path.

@hivecommons-hive hivecommons-hive Bot removed the stage/071-needs-author Human-authored PR: CI red, changes requested or conflicting; diagnosis posted, the author must act label Oct 28, 2026
@hivecommons-hive

Copy link
Copy Markdown
Contributor

@clubanderson this PR is held at stage/071-needs-author and the hive will not merge it yet.

Why:

  • the branch conflicts with the base and needs a rebase

Next: the hive does not push to your branch. Push a new head (or reply here); CI and the review swarm re-run automatically and the PR returns to the normal path.

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

Intent alignment vs #15099 — the diff covers every remaining item of the issue (blocker model, no-effect + options, loop guard, label reconcile, pill, /api/status.capabilities), but it was cut before #15127 landed on v5 as the partial fix for the same issue, and the PR is currently CONFLICTING.

1. Two mechanisms now answer the same "capability gap" case with different verdicts (in-scope).
This branch's merge-base 8785546 is 69 commits behind #15127's merge 77d935f. On v5, src/pkg/delegatedecision/policy.go already has CapabilityGap(in) → recommendOnly("capability-gap", …) and a capability-gap glossary rule. This PR adds, in src/pkg/dashboard/api_repo_item_decide_delegated.go:484-499, an earlier short-circuit that returns VerdictRefused with Rule: "blocker:"+b.Kind and the new blocker glossary reason (src/pkg/delegatedecision/decision_glossary.go:186-188). After rebase, which of the two fires depends on whether pkg/blocker.Infer (src/pkg/blocker/blocker.go:173-197) or capabilityGapRE matches first — same situation, two rule names, two verdicts, two popover texts. Neither the PR body nor changelog.d/fixed-capability-blocker-decision.md mentions #15127, and the fragment re-describes the loop fix that changelog.d/fixed-let-hive-decide-capability-gap.md already shipped.

Suggested remedy: rebase onto v5, then make the policy rule defer to the structured blocker so there is one path — e.g. in policy.go:

-	if CapabilityGap(in) {
-		return recommendOnly("capability-gap", …)
-	}
+	// Capability gaps are decided before policy runs, from the structured
+	// blocker (pkg/blocker); this rule only catches prose the inferrer
+	// does not recognise and reuses its glossary family.
+	if CapabilityGap(in) {
+		return recommendOnly("blocker:capability", …)
+	}

(or drop CapabilityGap and move its regex into pkg/blocker's inference table), and trim the fragment to the delta over #15127 (structured blocker, re-check endpoint, label reconcile, pill, capabilities).

2. Advisory — deliberate deviation from issue item 4. #15099 says "otherwise approved-direction wins"; src/pkg/stageengine/derive_issue.go issueParkClear keeps 045 and clears approved-direction in both cases. The PR body states and justifies this (the un-park removes needs-human in the same write, so a needs-human seen beside the ack is the newer signal). The reasoning holds; just make sure the docs row in labels-and-control-signals.md says this rather than the issue's wording. No change requested.

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

…stop re-parking loops

An issue the scanner parks because the GitHub App cannot push
.github/workflows/* (no workflows permission) is a capability gap, not a
question: a direction decision cannot clear it, and "Let hive decide"
un-parked #14976 only for the sweep to re-apply the same
needs-human text an hour later.

- pkg/blocker: structured blocker kinds, the <!-- hive:blocker kind=… -->
  marker, prose inference, and a persisted ledger with a delegated-unpark
  loop guard and a cleared-at staleness guard.
- Record blockers where comments are read: issue_comment webhook, task-list
  sweep (adds the marker to the remainder comment), gh-wrapper (adds the
  marker to a needs-human comment that names a missing capability).
- Stage engine: blocker parks 045 with "blocked: <short>"; needs-human
  beside approved-direction keeps 045 and clears the ack (needs-human is
  the newer signal by construction).
- Dashboard: decision_needed.blocker/options populated with real handlers
  (App settings link, Re-check permissions, close as partially addressed,
  wait; patch hand-off disabled with its reason), delegated decision
  short-circuits to no-effect with the glossary 'blocker' reason, the row
  pill reads "Blocked · App lacks workflows permission", ⚙ → Security and
  /api/status.capabilities name missing permissions and blocked counts.
- Tests: Go (blocker, ledger, webhook, sweep marker, stage facts/derive,
  dashboard handlers, glossary) and node fixtures (pill, options, popover,
  capability text); gh-wrapper bash tests.
- Docs: labels-and-control-signals (blocker marker + kinds + reconcile
  rule), dashboard-sections, operator-reference (App permissions table),
  api-reference; changelog fragment.

Fixes #15099

Signed-off-by: Andrew Anderson <andy@clubanderson.com>
Follows #15087's option consequences so a blocked row's ways out say
what each one does.

Signed-off-by: Andrew Anderson <andy@clubanderson.com>
Signed-off-by: Andrew Anderson <andy@clubanderson.com>
Signed-off-by: Andrew Anderson <andy@clubanderson.com>
…call sites, import ratchet

- pkg/github already has a test helper named blocker; import pkg/blocker
  as itemblocker there.
- Ledger.Record drove the loop guard off the preserved first-report time,
  so a capability re-report after a delegated un-park never tripped it;
  use the observation time. Observe returns changed=false for a report
  older than the entry's cleared-at.
- stageTrackerDecisionNeededWith test calls pass the clock.
- Allow pkg/dashboard → pkg/blocker in the import ratchet (the blocker
  ledger is the seam the tracker, delegate and status read).

Signed-off-by: Andrew Anderson <andy@clubanderson.com>
Signed-off-by: Andrew Anderson <andy@clubanderson.com>
… duplicate JS const

Signed-off-by: Andrew Anderson <andy@clubanderson.com>
…ases; resync api-reference citations after rebase

Signed-off-by: Andrew Anderson <andy@clubanderson.com>
…the write-back ratchet

Signed-off-by: Andrew Anderson <andy@clubanderson.com>
Signed-off-by: Andrew Anderson <andy@clubanderson.com>
@hivecommons-hive

Copy link
Copy Markdown
Contributor
1a0e000

ℹ️ Sentinel notice — informational; author is trusted (owner), merge is not blocked

Hive flagged this PR (author @clubanderson, head 1a0e000dbfee) because it matches behaviors that can override security controls, escalate privileges or damage the codebase. This is a heuristic, not an accusation — a maintainer should confirm the change is intended before it merges.

  • sensitive_path — changes 2 sensitive path(s) (Touches a sensitive path (OWNERS, workflows, policies, hive config, security docs, …))
    • bin/gh-wrapper.sh
    • bin/gh-wrapper.test.sh

Hive did not add the sentinel-alert label because this author is trusted. The finding remains recorded for audit and the dashboard Security tab; set sentinel.trusted_authors_block: true to require the blocking label for trusted authors too.

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

Security — delta re-review at 1a0e000 (previous head a35b45a). The earlier security finding is still present, and the delta adds a second ingestion path with the same gap.

Blocker ledger trusts comment text from any author (medium, in-scope).
pkg/blocker.Ledger.Observe (src/pkg/blocker/ledger.go:128-146) records a capability blocker or loop-guard reason from whatever text it is handed. Both callers added in this delta hand it every comment with no author check:

  • src/pkg/github/webhook_cache.go:495-502 — issue_comment created/edited → itemblocker.Shared().Observe(configured, n, body, at). p.Comment.User.Login is parsed (webhook_cache.go:449-451) but never read.
  • src/pkg/dashboard/api_repo_item_decide_delegated_plan.go delegatedCommentTexts — Issues.ListComments → commentText{Body, At}; c.GetUser() is dropped, and delegatedBlocker (src/pkg/dashboard/item_blocker.go:210-216) re-observes all of them before every delegated decision.

Consequence: anyone who can comment on a public repo issue can write "needs-human: the App cannot push to .github/workflows" (matches Infer, src/pkg/blocker/blocker.go:211-217) and thereby (a) make Let hive decide return no-effect on that issue — decideDelegatedOne short-circuits on the ledger for every KindIssue regardless of labels (api_repo_item_decide_delegated.go:492-500); (b) flip the global /api/status.capabilities / ⚙ Security card to "missing workflows · N items blocked" (capabilitiesStatus, item_blocker.go:240-250, reads the ledger with no label or author filter); (c) after a delegated un-park, trip the human-only:re-asked loop guard by repeating the recorded reason (ledger.go:180-204); (d) have up to 280 chars of their text (ReasonSentence, blocker.go:233) quoted under the hive's identity in the close-partial comment (item_blocker.go:394-403). No write to GitHub is gained, so this is control-plane DoS/spoofing, not privilege escalation — but it is a new trust boundary this PR introduces for the blocker model #15099 asks for.

Suggested fix — gate both callers on a hive-authored comment (the reports the PR describes all come from agents via bin/gh-wrapper.sh, the task-list sweep, or the hive bot login):

--- a/src/pkg/github/webhook_cache.go
+++ b/src/pkg/github/webhook_cache.go
@@ func (c *Client) HandleWebhookEvent(event string, payload []byte) (WebhookEventR
-		if body, at, ok := webhookIssueCommentBody(event, p); ok {
+		if body, at, ok := webhookIssueCommentBody(event, p); ok && p.Comment.User != nil && c.IsHiveAuthorLogin(p.Comment.User.Login) {
--- a/src/pkg/dashboard/api_repo_item_decide_delegated_plan.go
+++ b/src/pkg/dashboard/api_repo_item_decide_delegated_plan.go
@@ func (s *Server) delegatedCommentTexts(ctx context.Context, repoFull string, number int) []commentText {
 	for _, c := range comments {
+		if !s.deps.GHClient.IsHiveAuthorLogin(c.GetUser().GetLogin()) {
+			continue
+		}
 		out = append(out, commentText{Body: c.GetBody(), At: c.GetCreatedAt().Time})
 	}

If maintainer-written needs-human reports must also count, use the repo permission check already in gatherDelegatedInput (api_repo_item_decide_delegated.go:653-657) instead of accepting all authors. Please add a test with a non-hive commenter asserting Blocks() stays false.

Other delta items checked from this perspective, no findings: blocker/recheck and blocker/close-partial are gated by canToggleRepoHold + X-Hive-User and audited (item_blocker.go:277-291, 336-350); InstallationInfo.Permissions exposes only permission levels (src/pkg/github/app.go:642-663); _blocker_kind in bin/gh-wrapper.sh:1188-1215 only pattern-matches the body as a regex subject — no expansion of user text.

— hive: agent=adjudicator-3 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.

Intent-alignment re-review at head 1a0e000d (rebased onto v5 past #15127; the earlier merge-conflict finding is resolved). Judged against #15099.

1. issueParkClear strips a legitimately recorded approved-direction (still present; deviates from issue item 4) — src/pkg/stageengine/derive_issue.go:82-88 clears the ack whenever needs-human + 045 are present. The comment above it says the ack is "by construction" stale because "the un-park that applies the ack removes needs-human in the same write". That holds for /hive approve and the delegate (DecisionLiftsNeedsHuman always returns true, facts_sink.go:93-96), but not for the label-approval path: tryLabelApproval adds approved-direction while deliberately keeping a hive-added needs-human (src/pkg/github/issue_unpark_command.go:556-571; label_approval.go:282-285,394 — "The approval is still recorded, but the issue is not released while one is present", and the reply tells the maintainer "needs-human (held for a person; only a person removes it)"). The next stage pass now deletes that recorded approval, and f.Acted (label_approval.go:401, timeline-based) stops it being re-applied. #15099 item 4 says "otherwise approved-direction wins"; the PR argues for the opposite to stop the loop, but the loop guard already records human-only:re-asked as a blocker in that case, so gating the clear on a blocker keeps the fix and stops the regression:

	if !f.Park.NeedsHuman || label == "" || stage != stagelabels.IssueNeedsDecision || strings.TrimSpace(is.Blocker) == "" {

(and drop the "by construction" paragraph in the doc comment; adjust the derive_issue_test.go plain-case expectation.)

2. UI/doc text promises an automatic re-check that is not implemented — src/pkg/dashboard/item_blocker.go:121 ("the hive re-tries on its own once it sees …: write"), :170, :322 ("The hive re-tries on its next pass"), src/docs/operator-reference.md:1813 ("The hive re-checks on its own on the next stage pass"), src/docs/dashboard-sections.md:729. The only callers of InstallationInfo.Granted and Ledger.ClearKind are inside handleRepoItemBlockerRecheck (item_blocker.go:277,317,319); no stage pass or cadence tick probes permissions. Either add that probe or reword to "press Re-check permissions once the permission is granted".

3. Advisory (no change required): #15099 was already closed as completed by #15127 (capability-gap regex rule, delegatedecision/policy.go:374), which still runs as a fallback after the new blocker short-circuit at api_repo_item_decide_delegated.go:493-504. The PR body and changelog.d/fixed-capability-blocker-decision.md describe the whole fix without mentioning #15127; a one-clause "completes the remainder of #15127's partial fix" would keep the record straight.

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

@hivecommons-hive

Copy link
Copy Markdown
Contributor

@clubanderson this PR is held at stage/071-needs-author and the hive will not merge it yet.

Why:

  • the review swarm requested changes

Next: the hive does not push to your branch. Push a new head (or reply here); CI and the review swarm re-run automatically and the PR returns to the normal path.

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

Correctness — delta re-review at 1a0e000d (previous head a35b45a6; the compare is a rebase onto v5 plus fix commits 2dbd1fd…1a0e000d). Both earlier correctness findings are still present; the new commits don't touch either mechanism.

1. Still present — issueParkClear strips an approved-direction the label-approval path deliberately leaves next to needs-human (src/pkg/stageengine/derive_issue.go:82-89)

The label-approval sweep classifies needs-human as a kept park label (src/pkg/github/label_approval.go:282-285, :393-394), still adds approved-direction while it is present (src/pkg/github/issue_unpark_command.go:559-567 — Blocked() only looks at BlockingParkLabels, :322), and posts "Left in place: needs-human (held for a person; only a person removes it)" (label_approval.go:486-487). The next stage pass then clears that ack because f.Park.NeedsHuman && label != "" — no blocker, no loop guard involved. The posted comment and the label state now contradict each other, and the person's recorded acceptance is lost once they later remove needs-human.

The loop you are stopping already surfaces in the ledger as a blocker (human-only:re-asked is set as e.Blocker in src/pkg/blocker/ledger.go:195-198), so the reconcile can be scoped to it without losing the fix:

	if !f.Park.NeedsHuman || label == "" || stage != stagelabels.IssueNeedsDecision || is.Blocker == "" {
		return nil
	}

(and update derive_issue_test.go's plain-case expectation accordingly.)

2. Still present — a recorded blocker is never released when the gap is resolved out-of-band (src/pkg/dashboard/item_blocker.go:53-58, :210-216)

delegatedBlocker returns whatever the ledger holds; the only writers that drop a blocker are close-partial (item_blocker.go:384) and a granted recheck (:319). Observe of a later plain needs-human report keeps the existing Blocker (ledger.go:180-203 only sets e.Blocker when nil). So if a person pushes the workflow change themselves and removes needs-human, and an agent later parks the issue for an unrelated reason, "Let hive decide" still answers no-effect "App has no workflows permission" (api_repo_item_decide_delegated.go:493-501), and the stage pass still reports blocked: App lacks workflows permission (derive_issue.go:135-141). Nothing clears the entry on issue close either (Clear( has one caller), so /api/status.capabilities.blocked_items keeps counting closed issues.

Concrete fix: when a newer needs-human report carries no detectable kind, treat it as superseding the capability blocker —

--- a/src/pkg/blocker/ledger.go
+++ b/src/pkg/blocker/ledger.go
@@ func (l *Ledger) RecordReason(repo string, number int, reason string, at time.Time) Entry {
 	e.Repo, e.Number = repo, number
+	// A later park that names no capability supersedes a recorded one: the
+	// gap was resolved out-of-band and the issue is held for a new reason.
+	if e.Blocker != nil && e.Blocker.Source != SourceLoopGuard && at.After(e.ReportedAt) && reason != e.Reason {
+		e.Blocker = nil
+		e.Reapplied = false
+	}
 	e.Reason = reason
 	e.ReportedAt = at

plus a blockerLedger().Clear(repo, n) from the closed-issue sweep (src/pkg/github/closed_issue_stage_sweep.go) so closed items leave the capabilities count.

Other perspectives' earlier findings (docs, intent/#15127 overlap, webhook author gate, close-partial body) were not re-judged here.

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

@hivecommons-hive

Copy link
Copy Markdown
Contributor
Evidence bundle `#151341a0e000` sha256:618e5fa77406b164661f11d858a21605a566721279a0a53141079f032a881875 — download from the dashboard or `hivectl review evidence #15134`.

@hivecommons-hive

Copy link
Copy Markdown
Contributor

merged by Hive trusted-author auto-merge: author @clubanderson holds role owner and GitHub permission admin on repo; CI green at 1a0e000.

@github-actions

Copy link
Copy Markdown
Contributor

Thank you for your contribution! Your PR has been merged.

We'd love to hear how your experience was: share feedback

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

Labels

author/human A person pushed this PR themselves conflicts-with-open-pr Open PR has a merge conflict with another open PR dco All commits are DCO signed-off 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.

"Let hive decide" loops forever on capability-gap needs-human (App lacks workflows permission)

1 participant