Skip to content

fix(selfhost): scope label automation for one-shot reviews - #2719

Merged
JSONbored merged 1 commit into
mainfrom
fix/selfhost-scope-label-automation
Jul 3, 2026
Merged

fix(selfhost): scope label automation for one-shot reviews#2719
JSONbored merged 1 commit into
mainfrom
fix/selfhost-scope-label-automation

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Summary

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 the contributor-cap label also
surfaced 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:

  • Anti-abuse enforcement labels (blacklist/contributor-cap/review-nag) now ride on the same
    close autonomy as their accompanying close — a label there is inseparable metadata on the
    close, never applied independently, so autonomy.close: auto alone is enough for both. Each of
    settings.blacklistLabel/contributorCapLabel/reviewNagLabel also accepts an explicit null
    (not just omitted) to close without any label at all, mirroring the existing
    contributorOpenPrCap/accountAgeThresholdDays load-bearing-null idiom.
  • The planner's own disposition-communication labels (ready-to-merge/changes-requested/
    needs-human-review/migration-collision, plus the account-age new-account label) move to a new,
    separate review_state_label autonomy class — default observe 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 previously resolved autonomy from the literal
    actionClass field (always "label" for any label mutation), which would have silently
    re-denied a label authorized via close/review_state_label against the unrelated label dial.
    Fixed by threading a new autonomyClass field through PlannedAgentAction (falls back to
    actionClass for every non-label action, byte-identical there).

Also adds maintainer-mention nag moderation: settings.reviewNagMonitoredMentions extends the
existing @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 @gittensory counter.

Confirmed already-safe, no change needed: a formal GitHub REQUEST_CHANGES review is deliberately
never 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

  • The PR title follows type(scope): short summary Conventional Commit format.
  • This PR is focused and does not mix unrelated backend, UI, MCP, docs, dependency, and deploy changes.
  • This follows CONTRIBUTING.md and does not reintroduce GitHub Pages, VitePress, site/, or CNAME.
  • I linked an issue, or this is small enough that the summary explains why an issue is not needed.

Validation

  • git diff --check
  • npm run actionlint
  • npm run typecheck
  • npm run test:coverage locally (unsharded, full run) — 7013 tests passed, 6 pre-existing skips; the
    changed 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:workers
  • npm run build:mcp
  • npm run test:mcp-pack
  • npm run ui:openapi:check
  • npm run ui:lint
  • npm run ui:typecheck
  • npm run ui:build
  • npm audit --audit-level=moderate
  • New or changed behavior has unit/integration tests for new branches, fallback paths, and sanitizer boundaries

Ran the full npm run test:ci gate (actionlint, migrations/schema-drift checks, typecheck, unsharded
coverage, workers tests, MCP build/pack, UI lint/typecheck/test/build) plus
npm audit --audit-level=moderate locally; both green.

Safety

  • No secrets, wallet details, hotkeys, coldkeys, user PATs, private keys, raw trust scores, private rankings, or private maintainer evidence are exposed.
  • Public GitHub text stays sanitized, low-noise, and does not imply compensation guarantees or optimization tactics.
  • Auth, cookie, CORS, GitHub App, Cloudflare, or session changes include negative-path tests. — N/A, no auth/cookie/CORS/session code touched.
  • API/OpenAPI/MCP behavior is updated and tested where needed. — review_state_label added to the autonomy enum in src/openapi/schemas.ts and src/mcp/server.ts's proposeAction tool; reviewNagMonitoredMentions + nullable label fields added to RepositorySettingsSchema; openapi.json regenerated; ui:openapi:settings-parity passes.
  • UI changes use live API data or real empty/error/loading states, not production mock/demo fallbacks. — N/A, no UI changed.
  • Visible UI changes include a UI Evidence section below. — N/A, backend/config-as-code only, no visible UI change.
  • Public docs/changelogs are updated where needed; changelogs are only edited for release-prep PRs. — .gittensory.yml.example and config/examples/ (README + both templates) updated; CHANGELOG.md untouched.

Notes

  • New migration migrations/0100_review_nag_monitored_mentions.sql adds the
    review_nag_monitored_mentions_json column (NOT NULL DEFAULT '[]') — a plain, hand-authored
    ALTER TABLE ADD COLUMN matching every prior migration's style. I deliberately did not make
    blacklist_label/contributor_cap_label/review_nag_label nullable at the DB/dashboard layer:
    drizzle-kit generate wanted to do this via a full SQLite table-recreate (CREATE __new_... / INSERT ... SELECT / DROP / RENAME), a pattern with zero precedent across the other 99 migrations
    in this repo. The explicit-null-clears-a-label capability is confined to the
    .gittensory.yml/private-config layer instead (a Partial<> TypeScript type, no DB impact),
    which is the primary lever self-host operators already use for this kind of policy.
  • Rebased onto origin/main twice during development to pick up two other merged PRs
    (contributorCapCancelCi and the migration-numbering conflict this created, plus two small
    unrelated fixes) — conflicts resolved by hand, re-verified with a full typecheck + targeted test
    run after each rebase.

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.
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

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

@JSONbored
JSONbored merged commit 8d67c4a into main Jul 3, 2026
11 checks passed
@JSONbored
JSONbored deleted the fix/selfhost-scope-label-automation branch July 3, 2026 07:43
@codecov

codecov Bot commented Jul 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.34177% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 96.08%. Comparing base (a6f4a36) to head (659026c).
⚠️ Report is 14 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/queue/processors.ts 80.39% 2 Missing and 8 partials ⚠️
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     
Files with missing lines Coverage Δ
src/db/repositories.ts 96.56% <100.00%> (ø)
src/db/schema.ts 69.46% <ø> (ø)
src/mcp/server.ts 95.80% <ø> (ø)
src/openapi/schemas.ts 100.00% <ø> (ø)
src/services/agent-action-executor.ts 95.29% <100.00%> (ø)
src/settings/agent-actions.ts 94.32% <100.00%> (ø)
src/settings/autonomy.ts 100.00% <100.00%> (ø)
src/signals/focus-manifest.ts 99.34% <100.00%> (+<0.01%) ⬆️
src/queue/processors.ts 92.42% <80.39%> (-0.29%) ⬇️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant