Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
Commit44ed968unknown_key

feat: wire preview expiry autopilot task, surface K3+preview tasks in admin UI

feat: wire preview expiry autopilot task, surface K3+preview tasks in admin UI

- Add `expireOldPreviews` import to autopilot.ts and register a new
  `preview-expiry` task following the exact same try/catch + console.log
  pattern as the surrounding tasks (dev-env-idle-sweep, pr-sandbox-cleanup).
- Add `preview-expiry` to AUTOPILOT_TASK_CATALOG in admin.tsx so it
  appears in the /admin/autopilot "Configured tasks" table with its
  description. `auto-merge-sweep` and `ai-build-from-issues` were already
  present in the catalog.
- Bump the "N tasks · runs every tick" badge from 9 → 10.

https://claude.ai/code/session_01DzJMTFASjMHt2f5ze4cNLR
Claude committed on June 6, 2026Parent: 81e0e12
2 files changed+21144ed9686e11616f0f39c06d7d96db31c6dfdfcef
2 changed files+21−1
Modifiedsrc/lib/autopilot.ts+19−0View fileUnifiedSplit
6868import { runAdvancementScan } from "./advancement-scanner";
6969import { expireOldSandboxes } from "./pr-sandbox";
7070import { expireIdleEnvs } from "./dev-env";
71import { expireOldPreviews } from "./branch-previews";
7172
7273export interface AutopilotTaskResult {
7374 name: string;
621622 }
622623 },
623624 },
625 {
626 // Preview expiry — migration 0062. Flips every branch-preview row
627 // whose `expires_at` is in the past to status='expired'. Runs on
628 // every tick; the lib itself is a cheap SQL UPDATE and is a no-op
629 // when there's nothing to expire, so this never adds meaningful
630 // overhead.
631 name: "preview-expiry",
632 run: async () => {
633 try {
634 const expired = await expireOldPreviews();
635 if (expired > 0) {
636 console.log(`[autopilot] preview-expiry: expired=${expired}`);
637 }
638 } catch (err) {
639 console.error("[autopilot] preview-expiry: threw:", err);
640 }
641 },
642 },
624643 ];
625644}
626645
Modifiedsrc/routes/admin.tsx+2−1View fileUnifiedSplit
34373437 { name: "auto-merge-sweep", desc: "AI-gated auto-merge: close eligible PRs" },
34383438 { name: "ai-build-from-issues", desc: "Dispatch ai:build issues → draft PRs" },
34393439 { name: "sleep-mode-digest", desc: "Send AI-hours-saved digest emails" },
3440 { name: "preview-expiry", desc: "Expire stale branch-preview rows past their TTL" },
34403441] as const;
34413442
34423443admin.get("/admin/autopilot", async (c) => {
35853586 )}
35863587 <div class="adm-autopilot-h3" style="margin-top:28px">
35873588 <h3>Configured tasks</h3>
3588 <span class="adm-autopilot-h3-meta">9 tasks · runs every tick</span>
3589 <span class="adm-autopilot-h3-meta">10 tasks · runs every tick</span>
35893590 </div>
35903591 <div class="adm-autopilot-tasks">
35913592 {AUTOPILOT_TASK_CATALOG.map((t) => {
35923593