Home

boring / v2

publicBoring-Software-Inc/v2
Overview Code History Branches Pull requestsIssuesInsights Campfire Docs
main
HomeOverview Code PRsIssues

#167 [Detail Bug] Manual re-run is disabled for repos with only standalone custom rules enabled

open

Opened by detail-app[bot] · 6 days ago

detail-app[bot]opened this issueAuthor· 6 days ago

Sign in to comment.

Linked pull requests

No linked pull requests yet.

Detail Bug Report

app.detail.dev/org_0f605249-55f3-46eb-880c-20ae3e45ae99/…/bug_806570f6-6007-4733-9c08-e8e60536d358

Introduced in #4 by @ripgrim on Jul 20, 2026

Summary

  • Context: apps/web/src/lib/activity.functions.ts gates the manual "Re-run rules" feature: rerunChangeRequest refuses to enqueue a re-run when executableRuleNames says no rules would evaluate, and getRerunPreview feeds the confirm dialog the same list. The worker (apps/worker/src/jobs/run-workflows.ts) composes the actual rule set for every evaluation, including re-runs, via the same runWorkflows path.
  • Bug: executableRuleNames builds its list only from RULE_CATALOG entries and saved-workflow nodes, so enabled standalone custom rules are invisible to it — while the worker explicitly joins enabled standalone custom rules into the derived toggle set and would evaluate them on a re-run.
  • Actual vs. expected: For a repo whose only enabled rules are standalone custom rules, the web app returns { status: "no-workflow" } and removes the Re-run button / tells the admin to "install a rule", even though the worker would actually run those custom rules; the confirm dialog can also display "no rules will run" for rules that would run.
  • Impact: Admins cannot use manual re-run (or the onboarding "run first check" action) at all for custom-rule-only repos, and the confirm dialog under-reports the rules that will evaluate for any repo with standalone custom rules.

Code with Bug

apps/web/src/lib/activity.functions.ts

function executableRuleNames(
	configs: { ruleId: string; enabled: boolean }[],
	enabledWorkflows: {
		nodes: { type: string; ref?: string }[];
	}[],
): string[] {
	const byId = new Map(configs.map((c) => [c.ruleId, c]));
	if (enabledWorkflows.length > 0) {
		...
		// Leftover standalone rules (not owned by any workflow) still run.
		for (const entry of RULE_CATALOG) {        // <-- BUG 🔴 iterates RULE_CATALOG only — custom rule ids never appear here
			...
		}
		return names;
	}
	return RULE_CATALOG.filter((entry) =>          // <-- BUG 🔴 no saved workflows ⇒ only builtin catalog rules considered
		ruleExecutes(
			`${entry.ruleId}@${entry.version}`,
			byId.get(entry.ruleId)?.enabled,
		),
	).map((entry) => entry.name);
}

Callers never fetch custom rules, so the gate can incorrectly refuse re-runs:

const [workflows, configs] = await Promise.all([
	repoServices.listEnabledWorkflows(db, repo.fullName, repo.forge),
	repoServices.listRuleConfigs(db, repo.id),
]);
const names = executableRuleNames(
	configs.map((c) => ({ ruleId: c.ruleId, enabled: c.enabled })),
	workflows,
);
if (names.length === 0) {
	return { status: "no-workflow" };              // <-- 🔴 false refusal when only custom rules are enabled
}

Worker side includes standalone custom rules in the evaluated set:

apps/worker/src/jobs/run-workflows.ts

const customRows = repo
	? await repoServices.listCustomRules(db, repo.id)
	: [];
...
const derived = deriveDefaultWorkflow(
	[
		...ruleConfigs.map((config) => resolveEffectiveRuleConfig({...})),
		// Custom rules join the toggle set like any non-baseline rule: they
		// run when enabled, and workflow ownership excludes them like any id.
		...customRows.map((row) => ({
			ref: customRuleRef(row.id),            // <-- the rules the web gate cannot see
			enabled: row.enabled,
			config: {} as JsonValue,
			held: false,
		})),
	],
	ownedRuleIds,
	event.forge,
);

Explanation

The web gate and preview compute “rules that would run” using only builtin rule configs (listRuleConfigs) + saved workflows (listEnabledWorkflows) and then filter exclusively over RULE_CATALOG. Standalone custom rules are stored separately and are not in the static catalog, so they never appear in executableRuleNames.

The worker’s runWorkflows path (also used by the re-run job handler) explicitly fetches custom rules via listCustomRules and adds enabled custom rule refs into the derived workflow toggle set. This causes a divergence: the UI can claim “no rules will run” and block the rerun job while the worker would actually evaluate enabled custom rules.

Codebase Inconsistency

  • Web gate/prompt: assumes runnable rules are a subset of RULE_CATALOG + saved workflows.
  • Worker runtime: treats enabled standalone custom rules as runnable toggles (via listCustomRules + deriveDefaultWorkflow).

Recommended Fix

In executableRuleNames (apps/web/src/lib/activity.functions.ts), fetch custom rules alongside configs/workflows in both callers (repoServices.listCustomRules(db, repo.id)) and fold them into the name list, mirroring the worker: treat each enabled custom rule as a leftover standalone rule (keyed by customRuleRef(row.id) / ruleIdOf for the seen-set, display name via the rule's stored name — run-view.ts already resolves custom names this way), and let workflow ownership exclude its node-owned ids. Then rerunChangeRequest will admit custom-rule-only repos and getRerunPreview will list them.

History

This bug was introduced in commit 3504b0a4 ("fix(rerun): show live re-run state and fix rule resolution feedback", 2026-07-20). Two days later, 67bd7145 (2026-07-22) added worker-side inclusion of enabled standalone custom rules, but the web-side gate/preview logic was never updated to account for custom rules.