fix(selfhost): scope label automation for one-shot reviews - #2719
Merged
Conversation
A single autonomy.label dial gated five unrelated label purposes: the planner's own disposition labels (gittensory:ready-to-merge/changes-requested/ needs-human-review/migration-collision) and the anti-abuse enforcement labels tied to blacklist/contributor-cap/review-nag closes. That coarseness caused a live incident: enabling autonomy.label to get contributor-cap labels also surfaced unwanted advisory labels in one-shot review mode, and the stopgap (dropping to close-only) silently lost the cap label too. Splits this into two independent, purpose-scoped classes. The anti-abuse enforcement labels now ride on the same `close` autonomy as their accompanying close (a label there is inseparable metadata on the close, never applied independently) and support an explicit `null` label name to close without any label at all. The planner's own disposition labels move to a new, separate `review_state_label` autonomy class, default-off like every other class, so a one-shot-mode repo never sees them without an explicit opt-in. The executor's durable pending-approval re-check now resolves autonomy via each action's own authorizing class (a new `autonomyClass` field, falling back to `actionClass`) rather than the literal GitHub-mutation kind, so a label authorized via `close`/`review_state_label` isn't wrongly re-denied against the separate `label` dial. Also extends the existing @gittensory review-nag cooldown to throttle a thread's own author for repeatedly @-mentioning a configured maintainer login (`reviewNagMonitoredMentions`), reusing the same policy/threshold/ cooldown/label settings and counted independently per mentioned login.
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
gittensory-ui | 659026c | Commit Preview URL Branch Preview URL |
Jul 03 2026, 07:42 AM |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #2719 +/- ##
==========================================
- Coverage 96.13% 96.08% -0.05%
==========================================
Files 241 241
Lines 27033 27117 +84
Branches 9822 9863 +41
==========================================
+ Hits 25988 26056 +68
- Misses 433 435 +2
- Partials 612 626 +14
🚀 New features to boost your workflow:
|
24 tasks
This was referenced Jul 3, 2026
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A single
autonomy.labeldial gated five unrelated label purposes: the planner's own dispositionlabels (
gittensory:ready-to-merge/changes-requested/needs-human-review/migration-collision)and the anti-abuse enforcement labels tied to blacklist/contributor-cap/review-nag closes. That
coarseness caused a live incident — enabling
autonomy.labelto get the contributor-cap label alsosurfaced unwanted advisory labels in one-shot review mode, and the stopgap (dropping to
close-only) silently lost the cap label too, since both rode on the exact same dial.This splits label authorization by purpose:
closeautonomy as their accompanying close — a label there is inseparable metadata on theclose, never applied independently, so
autonomy.close: autoalone is enough for both. Each ofsettings.blacklistLabel/contributorCapLabel/reviewNagLabelalso accepts an explicitnull(not just omitted) to close without any label at all, mirroring the existing
contributorOpenPrCap/accountAgeThresholdDaysload-bearing-null idiom.needs-human-review/migration-collision, plus the account-age new-account label) move to a new,
separate
review_state_labelautonomy class — defaultobservelike every other class, so aone-shot-mode repo never sees them without an explicit opt-in.
actionClassfield (always"label"for any label mutation), which would have silentlyre-denied a label authorized via
close/review_state_labelagainst the unrelatedlabeldial.Fixed by threading a new
autonomyClassfield throughPlannedAgentAction(falls back toactionClassfor every non-label action, byte-identical there).Also adds maintainer-mention nag moderation:
settings.reviewNagMonitoredMentionsextends theexisting
@gittensory-ping review-nag cooldown to throttle a thread's own author for repeatedly@-mentioning a configured maintainer login too — reusing the exact same
policy/threshold/cooldown/label settings (one cooldown policy, multiple watched mention targets),
counted independently per mentioned login and independently of the
@gittensorycounter.Confirmed already-safe, no change needed: a formal GitHub
REQUEST_CHANGESreview is deliberatelynever planned (a stale blocking review would strand a PR once it goes green) — this was already
true before this PR.
No cleanup path for pre-existing mis-applied labels is included (explicitly out of scope per
direction — only future behavior changes, not historical remediation).
No linked issue: this is a direct fix for a live self-host incident with no public behavior change
to the hosted product (self-host autonomy config only), so a tracking issue didn't seem necessary
before implementing.
Scope
type(scope): short summaryConventional Commit format.CONTRIBUTING.mdand does not reintroduce GitHub Pages, VitePress,site/, orCNAME.Validation
git diff --checknpm run actionlintnpm run typechecknpm run test:coveragelocally (unsharded, full run) — 7013 tests passed, 6 pre-existing skips; thechanged files (
src/settings/agent-actions.ts,src/settings/autonomy.ts,src/services/agent-action-executor.ts,src/queue/processors.ts,src/signals/focus-manifest.ts)are 100% line-covered, and I manually traced every uncovered branch v8 reported in those files back to
pre-existing lines outside this diff.
npm run test:workersnpm run build:mcpnpm run test:mcp-packnpm run ui:openapi:checknpm run ui:lintnpm run ui:typechecknpm run ui:buildnpm audit --audit-level=moderateRan the full
npm run test:cigate (actionlint, migrations/schema-drift checks, typecheck, unshardedcoverage, workers tests, MCP build/pack, UI lint/typecheck/test/build) plus
npm audit --audit-level=moderatelocally; both green.Safety
review_state_labeladded to the autonomy enum insrc/openapi/schemas.tsandsrc/mcp/server.ts'sproposeActiontool;reviewNagMonitoredMentions+ nullable label fields added toRepositorySettingsSchema;openapi.jsonregenerated;ui:openapi:settings-paritypasses.UI Evidencesection below. — N/A, backend/config-as-code only, no visible UI change..gittensory.yml.exampleandconfig/examples/(README + both templates) updated;CHANGELOG.mduntouched.Notes
migrations/0100_review_nag_monitored_mentions.sqladds thereview_nag_monitored_mentions_jsoncolumn (NOT NULL DEFAULT '[]') — a plain, hand-authoredALTER TABLE ADD COLUMNmatching every prior migration's style. I deliberately did not makeblacklist_label/contributor_cap_label/review_nag_labelnullable at the DB/dashboard layer:drizzle-kit generatewanted to do this via a full SQLite table-recreate (CREATE __new_... / INSERT ... SELECT / DROP / RENAME), a pattern with zero precedent across the other 99 migrationsin this repo. The explicit-
null-clears-a-label capability is confined to the.gittensory.yml/private-config layer instead (aPartial<>TypeScript type, no DB impact),which is the primary lever self-host operators already use for this kind of policy.
origin/maintwice during development to pick up two other merged PRs(
contributorCapCancelCiand the migration-numbering conflict this created, plus two smallunrelated fixes) — conflicts resolved by hand, re-verified with a full typecheck + targeted test
run after each rebase.