feat: allow forced AgentRun runner cleanup

This commit is contained in:
Codex
2026-06-19 14:26:47 +00:00
parent 0a5a7bcb66
commit 6f1699a541
+17 -5
View File
@@ -1677,6 +1677,7 @@ interface CleanupRunnersOptions extends ConfirmOptions {
node: string | null;
lane: string | null;
timeoutSeconds: number;
forceActive: boolean;
}
interface CleanupRunsOptions extends ConfirmOptions {
@@ -1935,13 +1936,14 @@ function parseGitMirrorOptions(args: string[]): GitMirrorOptions {
}
function parseCleanupRunnersOptions(args: string[]): CleanupRunnersOptions {
validateOptions(args, new Set(["--confirm", "--dry-run"]), new Set(["--timeout-seconds", "--node", "--lane"]));
validateOptions(args, new Set(["--confirm", "--dry-run", "--force-active"]), new Set(["--timeout-seconds", "--node", "--lane"]));
const base = parseConfirmOptions(args);
return {
...base,
node: optionValue(args, "--node") ?? null,
lane: optionValue(args, "--lane") ?? null,
timeoutSeconds: positiveIntegerOption(args, "--timeout-seconds", 180, 600),
forceActive: args.includes("--force-active"),
};
}
@@ -2925,7 +2927,7 @@ async function cleanupRunners(config: UniDeskConfig, options: CleanupRunnersOpti
dryRun: true,
mutation: false,
next: {
confirm: `bun scripts/cli.ts agentrun control-plane cleanup-runners --node ${spec.nodeId} --lane ${spec.lane} --confirm`,
confirm: `bun scripts/cli.ts agentrun control-plane cleanup-runners --node ${spec.nodeId} --lane ${spec.lane}${options.forceActive ? " --force-active" : ""} --confirm`,
},
};
}
@@ -2934,7 +2936,7 @@ async function cleanupRunners(config: UniDeskConfig, options: CleanupRunnersOpti
dryRun: false,
mutation: true,
followUp: {
dryRun: `bun scripts/cli.ts agentrun control-plane cleanup-runners --node ${spec.nodeId} --lane ${spec.lane} --dry-run`,
dryRun: `bun scripts/cli.ts agentrun control-plane cleanup-runners --node ${spec.nodeId} --lane ${spec.lane}${options.forceActive ? " --force-active" : ""} --dry-run`,
status: `bun scripts/cli.ts agentrun control-plane status --node ${spec.nodeId} --lane ${spec.lane}`,
},
};
@@ -3030,6 +3032,7 @@ function cleanupRunnersScript(options: CleanupRunnersOptions, spec: AgentRunLane
`age_based_cleanup_enabled=${retention.ageBasedCleanup.enabled ? "true" : "false"}`,
`age_based_max_age_hours=${retention.ageBasedCleanup.maxAgeHours === null ? "" : String(retention.ageBasedCleanup.maxAgeHours)}`,
`timeout_seconds=${String(options.timeoutSeconds)}`,
`force_active=${options.forceActive ? "true" : "false"}`,
`match_labels_json_b64=${shQuote(matchLabelsB64)}`,
`job_name_prefixes_json_b64=${shQuote(jobNamePrefixesB64)}`,
"match_labels_json=$(printf '%s' \"$match_labels_json_b64\" | base64 -d)",
@@ -3048,7 +3051,7 @@ function cleanupRunnersScript(options: CleanupRunnersOptions, spec: AgentRunLane
"if [ \"$facts_exit\" -ne 0 ]; then",
" printf '%s\\n' '{\"ok\":false,\"items\":[],\"failureKind\":\"manager-facts-unavailable\",\"valuesPrinted\":false}' > \"$tmp_dir/runner-facts.json\"",
"fi",
"MATCH_LABELS_JSON=\"$match_labels_json\" JOB_NAME_PREFIXES_JSON=\"$job_name_prefixes_json\" MAX_RUNNERS=\"$max_runners\" CLEANUP_ORDER=\"$cleanup_order\" ACTIVE_HEARTBEAT_MAX_AGE_MS=\"$active_heartbeat_max_age_ms\" AGE_BASED_CLEANUP_ENABLED=\"$age_based_cleanup_enabled\" AGE_BASED_MAX_AGE_HOURS=\"$age_based_max_age_hours\" FACTS_EXIT=\"$facts_exit\" TMP_DIR=\"$tmp_dir\" NAMESPACE=\"$namespace\" node <<'NODE' > \"$tmp_dir/plan.json\"",
"MATCH_LABELS_JSON=\"$match_labels_json\" JOB_NAME_PREFIXES_JSON=\"$job_name_prefixes_json\" MAX_RUNNERS=\"$max_runners\" CLEANUP_ORDER=\"$cleanup_order\" ACTIVE_HEARTBEAT_MAX_AGE_MS=\"$active_heartbeat_max_age_ms\" AGE_BASED_CLEANUP_ENABLED=\"$age_based_cleanup_enabled\" AGE_BASED_MAX_AGE_HOURS=\"$age_based_max_age_hours\" FORCE_ACTIVE=\"$force_active\" FACTS_EXIT=\"$facts_exit\" TMP_DIR=\"$tmp_dir\" NAMESPACE=\"$namespace\" node <<'NODE' > \"$tmp_dir/plan.json\"",
cleanupRunnersPlanNodeScript(),
"NODE",
"if [ " + shQuote(options.confirm && !options.dryRun ? "true" : "false") + " != true ]; then",
@@ -4273,6 +4276,7 @@ const cleanupOrder = process.env.CLEANUP_ORDER || "";
const activeHeartbeatMaxAgeMs = Number(process.env.ACTIVE_HEARTBEAT_MAX_AGE_MS || 0);
const ageBasedCleanupEnabled = process.env.AGE_BASED_CLEANUP_ENABLED === "true";
const ageBasedMaxAgeHours = process.env.AGE_BASED_MAX_AGE_HOURS ? Number(process.env.AGE_BASED_MAX_AGE_HOURS) : null;
const forceActive = process.env.FORCE_ACTIVE === "true";
const matchLabels = JSON.parse(process.env.MATCH_LABELS_JSON || "{}");
const jobNamePrefixes = JSON.parse(process.env.JOB_NAME_PREFIXES_JSON || "[]");
const now = Date.now();
@@ -4457,8 +4461,15 @@ if (ageBasedCleanupEnabled && ageBasedMaxAgeHours !== null) {
}
}
}
if (forceActive) {
for (const item of runnerJobs) {
selectedByName.set(item.name, item);
selectionReasons.set(item.name, item.protectedActive ? "force-active-runner-cleanup" : "force-runner-cleanup");
}
}
const selected = inactiveCandidates
const selectionPool = forceActive ? runnerJobs : inactiveCandidates;
const selected = selectionPool
.filter((item) => selectedByName.has(item.name))
.map((item) => ({ ...item, selectionReason: selectionReasons.get(item.name) || "selected" }));
const matchedPodNames = new Set(runnerJobs.map((item) => item.name));
@@ -4476,6 +4487,7 @@ console.log(JSON.stringify({
maxRunners,
cleanupOrder,
activeHeartbeatMaxAgeMs,
forceActive,
selectors: { matchLabels, jobNamePrefixes },
ageBasedCleanup: { enabled: ageBasedCleanupEnabled, maxAgeHours: ageBasedMaxAgeHours },
},