Skip to content

Commit 7eb2641

Browse files
authored
Add a stale-assignment sweep: warn-then-release ownership decay (#209)
Add skills/pr-batch/bin/stale-assignment-sweep, a dry-run-first warn-then-release sweep that decays inactive human assignments (nudge -> 4-day grace -> release). Automation ([bot]-suffixed / trusted_bots) is never swept; agent-claimed items are skipped; multi-human items are surfaced for manual review; single failures degrade gracefully. Hardened over review for identity-verified nudge attribution, release/nudge re-validation, login-less commit handling, empty-trusted_bots defaults, input validation, and closed-item guards. Follow-ups: #218 (gh timeout kill), #219 (dead field + timeline perf). Closes #209.
1 parent 0b2f048 commit 7eb2641

5 files changed

Lines changed: 1485 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ All notable changes to this portable workflow pack are documented here.
88

99
#### Added
1010

11+
- **Add `skills/pr-batch/bin/stale-assignment-sweep`, a dry-run-first warn-then-release sweep that decays inactive single-human assignments (nudge → 4-day grace → release) using anti-squatting and inactivity clocks renewed only by the assignee's own comments, reviews, and issue-referencing commits or linked-PR events, pausing on exempt labels, skipping `agent-claimed` items, surfacing items with two or more human assignees for manual review instead of auto-sweeping them, skipping closed/merged items, layering `--exempt-label` on top of the built-in defaults, re-checking live state before every comment or release, recognizing its own prior nudges by the union of the gh-authenticated login and any configured comment identity, degrading gracefully so one item's or repo's gh failure is reported as `UNKNOWN`/skipped rather than aborting the run, validating that day-count flags are non-negative, and never touching automation-set assignees — any `[bot]`-suffixed login when `trusted_bots` is empty (the packaged-fallback default) or an allowlisted bot otherwise — failing closed when the automation set or its posting identity is unresolved.** [issue 209](https://github.com/shakacode/agent-workflows/issues/209).
1112
- **Make batch selection and triage assignee-aware: a human assignee reserves an issue or PR (owned means skip), reserved items are listed with their assignee names instead of silently dropped, and items with no assignee or only an automation identity stay eligible.**
1213
- **Add a bounded exact-head PR merge helper that safely selects direct merge or merge-queue submission, reconciles ambiguous outcomes, and preserves unknown provenance.**
1314
- **Add Codex picker metadata (`agents/openai.yaml`) for `continue`, `status`, `tdd`, `run-ci`, `verify-pr-fix`, and `untrusted-contributor-intake`, and document `qa-stress` as the deliberate picker-metadata exclusion.**

bin/validate

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ ruby skills/pr-batch/bin/pr-merge-submit-test.rb
110110
ruby skills/pr-batch/bin/stage-dependency-gate-test.rb
111111
ruby skills/pr-batch/bin/agent-coord-bounded-test.rb
112112
ruby skills/pr-batch/bin/pr-security-preflight-test.rb
113+
ruby skills/pr-batch/bin/stale-assignment-sweep-test.rb
113114
ruby skills/task-observer/bin/task-observer-test.rb
114115
ruby skills/untrusted-contributor-intake/bin/untrusted-contributor-intake-contract-test.rb
115116
ruby skills/update-changelog/bin/changelog-merged-prs-test.rb

docs/pr-batch-skills.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,72 @@ The `agents/openai.yaml` file under a skill is optional Codex UI metadata for sk
6262

6363
Beyond permissions, selection itself is assignee-aware: a human assignee — any assignee outside the repo's resolved automation set — marks an issue or PR as reserved: owned means skip. The automation set is resolved from the trust config's `trusted_bots` plus `[bot]`-suffixed logins via the `pr-security-preflight` chain (`trusted_users` are human actors and stay reservable), failing closed to skip when unresolved. `$plan-pr-batch`, `$triage`, and `$plan-issue-triage` classify assignees after fetching the full scoped set (`no:assignee` alone omits automation-only-assigned eligible items), exclude reserved items from actionable batches, and list them with their assignee names; items with no assignee, or only an automation identity, stay eligible.
6464

65+
## Stale-Assignment Sweep
66+
67+
Because owned means skip, an assignment left with no follow-through would block
68+
that work forever. `skills/pr-batch/bin/stale-assignment-sweep` treats assignment
69+
as a lease, not a deed: it nudges, then (only after an unanswered grace) releases
70+
inactive human assignments back to the batch pool. It is the human-timescale
71+
analog of the coordination backend's agent heartbeat leases.
72+
73+
- **Default is dry-run.** With no `--apply`, it makes zero GitHub mutations and
74+
only prints a digest of would-nudge / would-release items, each with its clock,
75+
days inactive, and assignee. Run it report-only for ~2 weeks to tune TTLs
76+
before enabling writes. `--apply` posts the nudge/audit comments and removes
77+
assignees.
78+
- **Clocks (config-driven).** `time-to-first-activity` (default 7 days): assigned
79+
but zero activity by the assignee since assignment — the primary anti-squatting
80+
clock. `inactivity-after-start` (default 14 days for issues, 7 for PRs): no
81+
assignee activity for the TTL. Activity that renews a lease is the assignee's
82+
own comments, reviews, and issue-referencing commits or linked-PR events — not
83+
raw timeline commits, which carry no GitHub login and so cannot be attributed
84+
(a commit renews only when it surfaces as a `referenced`/`cross-referenced`
85+
timeline event). Other people's activity does not renew it. The report-only
86+
rollout is how you validate this activity coverage before enabling writes.
87+
- **Flow: nudge → grace → release.** At threshold it posts a nudge comment; four
88+
days (`--grace-days`) after an *unanswered* nudge it removes the assignee and
89+
posts an audit comment. It never releases without a prior unanswered nudge. Any
90+
assignee reply resets the clock; exempt labels pause it (defaults `blocked`,
91+
`on-hold`; `--exempt-label` *adds* to those defaults, it does not replace them).
92+
Closed or merged items are skipped — including any that close between the
93+
listing and the live re-check. Before each nudge and each release the live item
94+
is re-fetched and re-classified, so a reply or a state/label/assignee change
95+
between scan and mutation aborts the action. A prior nudge only counts when it was
96+
posted by one of the sweep's own identities — the gh-authenticated login
97+
(`gh api user`) unioned with any `--comment-identity`; a mismatch warns, and if
98+
no identity resolves, releases are disabled (so a misconfigured
99+
`--comment-identity` can never cause a silent re-nudge loop).
100+
- **Automation is never swept.** An assignee is automation when its login carries
101+
the `[bot]` suffix. If `trusted_bots` is configured (resolved via the
102+
`pr-security-preflight` chain) the base name must also be a member, mirroring
103+
`pr-security-preflight`'s own bot check; if `trusted_bots` is empty — the
104+
packaged-fallback default for a consumer repo — any `[bot]`-suffixed login
105+
qualifies, so bots are never swept by default. A bare login is always human even
106+
if it matches a bot's base name, and `trusted_users` are humans and remain
107+
reservable/sweepable. Items carrying the `agent-claimed` label are skipped
108+
entirely (agent-claim staleness is owned by backend heartbeats). When the trust
109+
config cannot be resolved it fails closed: human assignments are left untouched.
110+
Every skip is reported, so nothing is silently dropped.
111+
- **Multi-human items are surfaced, not swept.** In this version the sweep acts
112+
only on items with exactly one human assignee (plus any number of bot
113+
co-assignees). An item with two or more human assignees is skipped and reported
114+
as `reserved (N human assignees) — manual review`, because per-assignee decay is
115+
out of scope: one active co-assignee must not shield an inactive squatter, and a
116+
release must not remove an active co-assignee.
117+
- **Config & determinism.** `--repo` (repeatable or comma-separated; defaults to
118+
`gh repo view`), `--first-activity-ttl-days`, `--issue-inactivity-ttl-days`,
119+
`--pr-inactivity-ttl-days`, `--grace-days`, `--exempt-label`,
120+
`--comment-identity` (unioned with the gh login for marker detection),
121+
`--trust-config`. Inject the reference clock with `--now` or
122+
`STALE_ASSIGNMENT_SWEEP_NOW`; bound gh with
123+
`STALE_ASSIGNMENT_SWEEP_GH_TIMEOUT_SECONDS`. Run it on a schedule (Actions cron
124+
or the coordination daemon).
125+
- **Resilient reads.** A gh failure while reading/classifying one item is
126+
reported as an `UNKNOWN … skipped` digest line and does not lose the rest of the
127+
digest; a repo whose listing fails is warned and skipped so the other repos
128+
still run. Read failures keep the run at exit 0 (they are reported, not fatal);
129+
a per-item `--apply` mutation failure aborts only that item.
130+
65131
## Whole-Surface Triage Flow
66132

67133
Use `$triage` when the coordinator wants the generated equivalent of a manual

0 commit comments

Comments
 (0)