Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions src/settings/agent-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -792,7 +792,7 @@ export function planAgentMaintenanceActions(input: AgentActionPlanInput): Planne
actions.push({
actionClass: "label",
autonomyClass: "merge",
requiresApproval: false,
requiresApproval: approval("merge"),
reason: `verdict=${conclusion}; ${guardrailReason}`,
label: labels.manualReview,
labelOp: "add",
Expand All @@ -811,7 +811,7 @@ export function planAgentMaintenanceActions(input: AgentActionPlanInput): Planne
actions.push({
actionClass: "label",
autonomyClass: "merge",
requiresApproval: false,
requiresApproval: approval("merge"),
reason: `verdict=${conclusion}; ${input.migrationCollisionHold.reason}`,
label: labels.manualReview,
labelOp: "add",
Expand All @@ -827,7 +827,7 @@ export function planAgentMaintenanceActions(input: AgentActionPlanInput): Planne
actions.push({
actionClass: "label",
autonomyClass: "merge",
requiresApproval: false,
requiresApproval: approval("merge"),
reason: `verdict=${conclusion}; ${input.unlinkedIssueMatchHold.reason}`,
label: labels.manualReview,
labelOp: "add",
Expand All @@ -843,7 +843,7 @@ export function planAgentMaintenanceActions(input: AgentActionPlanInput): Planne
actions.push({
actionClass: "label",
autonomyClass: "merge",
requiresApproval: false,
requiresApproval: approval("merge"),
reason: `verdict=${conclusion}; ${input.unlinkedIssueMatchClose.reason}`,
label: labels.manualReview,
labelOp: "add",
Expand Down Expand Up @@ -1124,6 +1124,7 @@ export function planAgentMaintenanceActions(input: AgentActionPlanInput): Planne
// at a non-acting level". For the owner/admin/automation-bot branch (`!closeEligible`) this is unchanged: those
// authors are never close-eligible regardless of the `close` autonomy dial, so the hold must still surface.
// (manualHoldReason itself is now computed earlier, above section 1 — see its doc comment there.)
const manualHoldAutonomyClass: AgentActionClass = reviewGood ? "merge" : "close";
if (
manualHoldReason !== null &&
labels.manualReview !== null &&
Expand All @@ -1132,8 +1133,8 @@ export function planAgentMaintenanceActions(input: AgentActionPlanInput): Planne
) {
actions.push({
actionClass: "label",
autonomyClass: reviewGood ? "merge" : "close",
requiresApproval: false,
autonomyClass: manualHoldAutonomyClass,
requiresApproval: approval(manualHoldAutonomyClass),
reason: manualHoldReason,
label: labels.manualReview,
labelOp: "add",
Expand Down
59 changes: 59 additions & 0 deletions test/unit/agent-actions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,65 @@ describe("planAgentMaintenanceActions (#778)", () => {
}));
expect(plan).toEqual([]);
});

it("requires approval for merge-governed manual-review labels when merge is auto_with_approval", () => {
const guarded = planAgentMaintenanceActions(input({
conclusion: "success",
autonomy: { merge: "auto_with_approval" },
changedPaths: ["src/settings/agent-actions.ts"],
hardGuardrailGlobs: ["src/settings/**"],
pr: { labels: [], mergeableState: "clean", reviewDecision: "APPROVED" },
}));
expect(guarded).toEqual([
expect.objectContaining({ actionClass: "label", autonomyClass: "merge", requiresApproval: true, label: AGENT_LABEL_NEEDS_REVIEW }),
]);

const collision = planAgentMaintenanceActions(input({
conclusion: "success",
autonomy: { merge: "auto_with_approval" },
migrationCollisionHold: { reason: "live migrations/** collision on main", comment: "Please rebase." },
pr: { labels: [], mergeableState: "clean", reviewDecision: "APPROVED" },
}));
expect(collision).toEqual([
expect.objectContaining({ actionClass: "label", autonomyClass: "merge", requiresApproval: true, label: AGENT_LABEL_NEEDS_REVIEW, comment: "Please rebase." }),
]);
});

it("requires approval for close-governed manual-review labels when close is auto_with_approval", () => {
const actionRequired = planAgentMaintenanceActions(input({ conclusion: "action_required", autonomy: { close: "auto_with_approval" }, pr: { labels: [] } }));
expect(actionRequired).toEqual([
expect.objectContaining({ actionClass: "label", autonomyClass: "close", requiresApproval: true, label: AGENT_LABEL_NEEDS_REVIEW }),
]);

const auto = planAgentMaintenanceActions(input({ conclusion: "action_required", autonomy: { close: "auto" }, pr: { labels: [] } }));
expect(auto).toEqual([
expect.objectContaining({ actionClass: "label", autonomyClass: "close", requiresApproval: false, label: AGENT_LABEL_NEEDS_REVIEW }),
]);
});

it("requires approval for the unlinked-issue-match manual-review fallbacks (1d/1e) when merge is auto_with_approval", () => {
const holdMatched = { unlinkedIssueMatchHold: { reason: "this PR links no issue, but appears to directly solve open issue #42 without linking it", comment: "Please add a linking reference." } };
const hold = planAgentMaintenanceActions(input({
conclusion: "success",
autonomy: { merge: "auto_with_approval" },
...holdMatched,
pr: { labels: [], mergeableState: "clean", reviewDecision: "APPROVED" },
}));
expect(hold).toEqual([
expect.objectContaining({ actionClass: "label", autonomyClass: "merge", requiresApproval: true, label: AGENT_LABEL_NEEDS_REVIEW, comment: holdMatched.unlinkedIssueMatchHold.comment }),
]);

const closeRepeated = { unlinkedIssueMatchClose: { reason: "repeat of the same unlinked-issue pattern already flagged", comment: "Closing: please link the issue you're solving going forward." } };
const closeFallback = planAgentMaintenanceActions(input({
conclusion: "success",
autonomy: { merge: "auto_with_approval" },
...closeRepeated,
pr: { labels: [], mergeableState: "clean", reviewDecision: "APPROVED" },
}));
expect(closeFallback).toEqual([
expect.objectContaining({ actionClass: "label", autonomyClass: "merge", requiresApproval: true, label: AGENT_LABEL_NEEDS_REVIEW, comment: closeRepeated.unlinkedIssueMatchClose.comment }),
]);
});
});

describe("AI/review blockers remain blocking even when CI is green", () => {
Expand Down
Loading