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

feat(automation): auto-repair off-by-default + auto-issues/doc-drift toggles

feat(automation): auto-repair off-by-default + auto-issues/doc-drift toggles

autoRepairMode default flipped to 'off' — the bot no longer commits to
developer branches unless explicitly enabled. migration 0107 default
updated to match.

New per-repo modes autoIssuesMode + docDriftMode (migration 0108,
default 'off'). Both were previously gated only by server-level env
vars with no per-repo override. Post-receive now skips both unless the
repo has the mode set to 'suggest'.

Automation settings page gains a "Direct actions" quick-access card
at the top — checkbox toggles for the three automations that modify
the repo directly, without needing to scroll the full table.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WZuWnHv9RwxE87W6KESB9V
Claude committed on June 22, 2026Parent: 64aa989
7 files changed+12719f183fdfc55d824b8d014f7a76c42301340539197
7 changed files+127−21
Modifieddrizzle/0107_auto_repair_mode.sql+2−3View fileUnifiedSplit
11-- Add auto_repair_mode to per-repo automation settings.
2-- Default 'off' — bot never commits to branches unless the owner enables it.
23ALTER TABLE repo_automation_settings
3 ADD COLUMN IF NOT EXISTS auto_repair_mode TEXT NOT NULL DEFAULT 'suggest';
4 ADD COLUMN IF NOT EXISTS auto_repair_mode TEXT NOT NULL DEFAULT 'off';
Addeddrizzle/0108_push_automation_modes.sql+6−0View fileUnifiedSplit
1-- Add per-repo toggles for push-time AI automations that open issues or PRs.
2-- Default 'off' — both features were previously gated by server-level env vars
3-- (AI_AUTO_ISSUES=1, no per-repo opt-in). 'suggest' = enabled.
4ALTER TABLE repo_automation_settings
5 ADD COLUMN IF NOT EXISTS auto_issues_mode TEXT NOT NULL DEFAULT 'off',
6 ADD COLUMN IF NOT EXISTS doc_drift_mode TEXT NOT NULL DEFAULT 'off';
Modifiedsrc/__tests__/automation-settings.test.ts+10−2View fileUnifiedSplit
6969 expect(AUTOMATION_DEFAULTS.autoMergeMode).toBe("off");
7070 });
7171
72 it("auto-repair defaults to 'suggest' (runs but preserves pre-0107 behavior)", () => {
73 expect(AUTOMATION_DEFAULTS.autoRepairMode).toBe("suggest");
72 it("auto-repair defaults to 'off' — bot never commits to branches unless owner enables it", () => {
73 expect(AUTOMATION_DEFAULTS.autoRepairMode).toBe("off");
74 });
75
76 it("auto-issues defaults to 'off' — off by default, owner opts in", () => {
77 expect(AUTOMATION_DEFAULTS.autoIssuesMode).toBe("off");
78 });
79
80 it("doc-drift defaults to 'off' — off by default, owner opts in", () => {
81 expect(AUTOMATION_DEFAULTS.docDriftMode).toBe("off");
7482 });
7583});
7684
Modifiedsrc/db/schema.ts+3−1View fileUnifiedSplit
46914691 issueTriageMode: text("issue_triage_mode").notNull().default("suggest"),
46924692 autoMergeMode: text("auto_merge_mode").notNull().default("auto"),
46934693 ciAutofixMode: text("ci_autofix_mode").notNull().default("suggest"),
4694 autoRepairMode: text("auto_repair_mode").notNull().default("suggest"),
4694 autoRepairMode: text("auto_repair_mode").notNull().default("off"),
4695 autoIssuesMode: text("auto_issues_mode").notNull().default("off"),
4696 docDriftMode: text("doc_drift_mode").notNull().default("off"),
46954697 createdAt: timestamp("created_at").defaultNow(),
46964698 updatedAt: timestamp("updated_at").defaultNow(),
46974699 },
Modifiedsrc/hooks/post-receive.ts+19−12View fileUnifiedSplit
5252 refs: PushRef[],
5353 pusherUserId: string = ""
5454): Promise<void> {
55 // Resolve repo ID once for automation settings lookups.
55 // Resolve per-repo automation settings once for this push. Fails open —
56 // any DB error leaves automationSettings null and all per-repo gates pass
57 // (unchanged behavior for repos without a settings row).
5658 let repoId = "";
5759 try {
5860 const [repoRow] = await db
151153 // 4e. AI Auto-Issue Opener. Scans the diff for each pushed ref for
152154 // TODO/FIXME/HACK comments, hardcoded secrets, SQL injection patterns,
153155 // and debug console.log calls. Opens one issue per finding type per
154 // file (capped at MAX_ISSUES_PER_PUSH). Gated on AI_AUTO_ISSUES=1.
155 // Fire-and-forget; never blocks the push path.
156 // file (capped at MAX_ISSUES_PER_PUSH). Gated on AI_AUTO_ISSUES=1 and
157 // per-repo autoIssuesMode. Fire-and-forget; never blocks the push path.
156158 for (const ref of refs) {
157159 if (ref.newSha.startsWith("0000")) continue;
158 scanDiffForIssues(owner, repo, ref.oldSha, ref.newSha, pusherUserId).catch(
159 (err) => console.warn("[ai-auto-issues] dispatch error:", err)
160 );
160 if (automationSettings && isAutomationOn(automationSettings.autoIssuesMode)) {
161 scanDiffForIssues(owner, repo, ref.oldSha, ref.newSha, pusherUserId).catch(
162 (err) => console.warn("[ai-auto-issues] dispatch error:", err)
163 );
164 }
161165 }
162166
163167 // 4f. Dependency CVE scanner — when DEPENDENCY_SCAN_ENABLED=1, scan
174178 // 4d. AI-tracked documentation drift check (migration 0068). Walks the
175179 // repo's markdown files for `<!-- gluecron:doc-track ... -->`
176180 // regions, hashes the referenced source, and opens a PR labelled
177 // `ai:doc-update` when the prose drifts. Fire-and-forget; failures
178 // are swallowed inside ai-doc-updater.ts so a missing anthropic key
179 // or empty doc_tracking table never breaks the push.
180 void fireDocDriftCheck(owner, repo).catch((err) =>
181 console.warn("[ai-doc-updater] dispatch error:", err)
182 );
181 // `ai:doc-update` when the prose drifts. Gated on per-repo
182 // docDriftMode. Fire-and-forget; failures are swallowed inside
183 // ai-doc-updater.ts so a missing anthropic key or empty
184 // doc_tracking table never breaks the push.
185 if (automationSettings && isAutomationOn(automationSettings.docDriftMode)) {
186 void fireDocDriftCheck(owner, repo).catch((err) =>
187 console.warn("[ai-doc-updater] dispatch error:", err)
188 );
189 }
183190
184191 // 4g. Smart empty states — repo onboarding. On the very first push to a
185192 // repo's default branch (oldSha all-zeros), generate a README draft,
Modifiedsrc/lib/automation-settings.ts+12−2View fileUnifiedSplit
2424 * issueTriageMode 'off' | 'suggest' (default 'suggest')
2525 * autoMergeMode 'off' | 'suggest' | 'auto' (default 'off')
2626 * ciAutofixMode 'off' | 'suggest' | 'auto' (default 'suggest')
27 * autoRepairMode 'off' | 'suggest' (default 'suggest')
27 * autoRepairMode 'off' | 'suggest' (default 'off')
28 * autoIssuesMode 'off' | 'suggest' (default 'off')
29 * docDriftMode 'off' | 'suggest' (default 'off')
2830 *
2931 * Where only on/off is meaningful, 'auto' is treated the same as 'suggest'
3032 * (i.e. "on") — `isAutomationOn` encodes that rule.
4749 autoMergeMode: AutomationMode;
4850 ciAutofixMode: AutomationMode;
4951 autoRepairMode: AutomationMode;
52 autoIssuesMode: AutomationMode;
53 docDriftMode: AutomationMode;
5054}
5155
5256/** Loader signature — the DI seam dispatch sites accept for tests. */
6771 issueTriageMode: "suggest",
6872 autoMergeMode: "off",
6973 ciAutofixMode: "suggest",
70 autoRepairMode: "suggest",
74 autoRepairMode: "off",
75 autoIssuesMode: "off",
76 docDriftMode: "off",
7177});
7278
7379// ---------------------------------------------------------------------------
120126 autoMergeMode: normalizeMode(row.autoMergeMode, AUTOMATION_DEFAULTS.autoMergeMode),
121127 ciAutofixMode: normalizeMode(row.ciAutofixMode, AUTOMATION_DEFAULTS.ciAutofixMode),
122128 autoRepairMode: normalizeMode(row.autoRepairMode, AUTOMATION_DEFAULTS.autoRepairMode),
129 autoIssuesMode: normalizeMode(row.autoIssuesMode, AUTOMATION_DEFAULTS.autoIssuesMode),
130 docDriftMode: normalizeMode(row.docDriftMode, AUTOMATION_DEFAULTS.docDriftMode),
123131 };
124132}
125133
169177 autoMergeMode: normalizeMode(patch.autoMergeMode, current.autoMergeMode),
170178 ciAutofixMode: normalizeMode(patch.ciAutofixMode, current.ciAutofixMode),
171179 autoRepairMode: normalizeMode(patch.autoRepairMode, current.autoRepairMode),
180 autoIssuesMode: normalizeMode(patch.autoIssuesMode, current.autoIssuesMode),
181 docDriftMode: normalizeMode(patch.docDriftMode, current.docDriftMode),
172182 };
173183
174184 await db
Modifiedsrc/routes/automation-settings.tsx+75−1View fileUnifiedSplit
275275 )}
276276
277277 <form method="post" action={`/${ownerName}/${repoName}/settings/automation`}>
278 <div class="automation-card" style="margin-bottom: var(--space-4);">
279 <div style="padding: 14px var(--space-5); border-bottom: 1px solid var(--border);">
280 <div style="font-size: 11px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-muted); margin-bottom: 4px;">Direct actions — these automations modify your repository</div>
281 <div style="font-size: 12.5px; color: var(--text-muted);">These three automations commit code or open PRs/issues on your behalf. Off by default.</div>
282 </div>
283 <div style="display: flex; gap: var(--space-4); padding: var(--space-4) var(--space-5); flex-wrap: wrap;">
284 <label style="display: flex; align-items: center; gap: 10px; cursor: pointer; min-width: 200px;">
285 <input type="checkbox" name="auto_repair_quick" value="suggest"
286 checked={settings.autoRepairMode !== "off"}
287 onChange="this.form.querySelector('[name=auto_repair_mode]').value = this.checked ? 'suggest' : 'off'"
288 style="width: 16px; height: 16px; cursor: pointer;" />
289 <div>
290 <div style="font-size: 13px; font-weight: 600; color: var(--text-strong);">Auto-repair</div>
291 <div style="font-size: 11.5px; color: var(--text-muted);">Commits fixes to your branch</div>
292 </div>
293 </label>
294 <label style="display: flex; align-items: center; gap: 10px; cursor: pointer; min-width: 200px;">
295 <input type="checkbox" name="auto_issues_quick" value="suggest"
296 checked={settings.autoIssuesMode !== "off"}
297 onChange="this.form.querySelector('[name=auto_issues_mode]').value = this.checked ? 'suggest' : 'off'"
298 style="width: 16px; height: 16px; cursor: pointer;" />
299 <div>
300 <div style="font-size: 13px; font-weight: 600; color: var(--text-strong);">Auto-issues</div>
301 <div style="font-size: 11.5px; color: var(--text-muted);">Opens issues for TODOs and security patterns</div>
302 </div>
303 </label>
304 <label style="display: flex; align-items: center; gap: 10px; cursor: pointer; min-width: 200px;">
305 <input type="checkbox" name="doc_drift_quick" value="suggest"
306 checked={settings.docDriftMode !== "off"}
307 onChange="this.form.querySelector('[name=doc_drift_mode]').value = this.checked ? 'suggest' : 'off'"
308 style="width: 16px; height: 16px; cursor: pointer;" />
309 <div>
310 <div style="font-size: 13px; font-weight: 600; color: var(--text-strong);">Doc-drift PRs</div>
311 <div style="font-size: 11.5px; color: var(--text-muted);">Opens PRs when docs drift from source</div>
312 </div>
313 </label>
314 </div>
315 </div>
278316 <div class="automation-card">
279317 <table class="automation-table">
280318 <thead>
424462 />
425463 </td>
426464 </tr>
465 <tr>
466 <td class="automation-feature-name">Auto-issues</td>
467 <td class="automation-feature-desc">
468 Scans the diff on each push for TODO/FIXME comments,
469 hardcoded secrets, and SQL injection patterns. Opens one
470 issue per finding type per file (capped at 5 per push).
471 Requires <code>AI_AUTO_ISSUES=1</code> server-side.
472 </td>
473 <td>
474 <ModeSelect
475 name="auto_issues_mode"
476 value={settings.autoIssuesMode}
477 modes={["off", "suggest"]}
478 labels={{ suggest: "On" }}
479 />
480 </td>
481 </tr>
482 <tr>
483 <td class="automation-feature-name">Doc-drift PRs</td>
484 <td class="automation-feature-desc">
485 After each push, checks whether docs marked with{" "}
486 <code>{"<!-- gluecron:doc-track -->"}</code> have drifted
487 from their referenced source. Opens a PR labelled{" "}
488 <code>ai:doc-update</code> when they have.
489 </td>
490 <td>
491 <ModeSelect
492 name="doc_drift_mode"
493 value={settings.docDriftMode}
494 modes={["off", "suggest"]}
495 labels={{ suggest: "On" }}
496 />
497 </td>
498 </tr>
427499 <tr>
428500 <td class="automation-feature-name">AI loop (issue → PR → merge)</td>
429501 <td class="automation-feature-desc">
471543 issueTriageMode: normalizeMode(body["issue_triage_mode"], "suggest"),
472544 autoMergeMode: normalizeMode(body["auto_merge_mode"], "off"),
473545 ciAutofixMode: normalizeMode(body["ci_autofix_mode"], "suggest"),
474 autoRepairMode: normalizeMode(body["auto_repair_mode"], "suggest"),
546 autoRepairMode: normalizeMode(body["auto_repair_mode"], "off"),
547 autoIssuesMode: normalizeMode(body["auto_issues_mode"], "off"),
548 docDriftMode: normalizeMode(body["doc_drift_mode"], "off"),
475549 });
476550
477551 // The two automations that already lived on the repositories row keep
478552