From c2aaf6ace8b646e4bb1fcae8331117fa1b90add5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=82CHES?= Date: Thu, 26 Mar 2026 08:11:01 -0600 Subject: [PATCH] refactor: extract runSafely helper for try-catch-debug-continue pattern (#2611) * refactor: extract runSafely helper for try-catch-debug-continue pattern Reduces boilerplate in auto-post-unit.ts by extracting the repeated try { op() } catch (e) { debugLog(ctx, { phase, error }) } pattern into a reusable runSafely() helper in auto-utils.ts. Replaces 6 sequential blocks (github-sync, prune-bg-shell, browser-teardown, worktree-sync, rewrite-docs-resolve, reactive-state-cleanup). Co-Authored-By: Claude Opus 4.6 (1M context) * ci: trigger rebuild after null-safety fix * chore: trigger CI rebuild --------- Co-authored-by: Claude Opus 4.6 (1M context) --- .../extensions/gsd/auto-post-unit.ts | 44 +++++++------------ src/resources/extensions/gsd/auto-utils.ts | 25 +++++++++++ 2 files changed, 42 insertions(+), 27 deletions(-) create mode 100644 src/resources/extensions/gsd/auto-utils.ts diff --git a/src/resources/extensions/gsd/auto-post-unit.ts b/src/resources/extensions/gsd/auto-post-unit.ts index b9fcd32c4..847a0635a 100644 --- a/src/resources/extensions/gsd/auto-post-unit.ts +++ b/src/resources/extensions/gsd/auto-post-unit.ts @@ -47,6 +47,7 @@ import { } from "./post-unit-hooks.js"; import { hasPendingCaptures, loadPendingCaptures } from "./captures.js"; import { debugLog } from "./debug-logger.js"; +import { runSafely } from "./auto-utils.js"; import type { AutoSession } from "./auto/session.js"; /** Unit types that only touch `.gsd/` internal state files (no code changes). @@ -239,6 +240,7 @@ export async function postUnitPreVerification(pctx: PostUnitContext, opts?: PreV // Auto-commit if (s.currentUnit) { + const unit = s.currentUnit; try { let taskContext: TaskCommitContext | undefined; @@ -297,64 +299,52 @@ export async function postUnitPreVerification(pctx: PostUnitContext, opts?: PreV } // GitHub sync (non-blocking, opt-in) - try { + await runSafely("postUnit", "github-sync", async () => { const { runGitHubSync } = await import("../github-sync/sync.js"); - await runGitHubSync(s.basePath, s.currentUnit.type, s.currentUnit.id); - } catch (e) { - debugLog("postUnit", { phase: "github-sync", error: String(e) }); - } + await runGitHubSync(s.basePath, unit.type, unit.id); + }); // Prune dead bg-shell processes - try { + await runSafely("postUnit", "prune-bg-shell", async () => { const { pruneDeadProcesses } = await import("../bg-shell/process-manager.js"); pruneDeadProcesses(); - } catch (e) { - debugLog("postUnit", { phase: "prune-bg-shell", error: String(e) }); - } + }); // Tear down browser between units to prevent Chrome process accumulation (#1733) - try { + await runSafely("postUnit", "browser-teardown", async () => { const { getBrowser } = await import("../browser-tools/state.js"); if (getBrowser()) { const { closeBrowser } = await import("../browser-tools/lifecycle.js"); await closeBrowser(); debugLog("postUnit", { phase: "browser-teardown", status: "closed" }); } - } catch (e) { - debugLog("postUnit", { phase: "browser-teardown", error: String(e) }); - } + }); // Sync worktree state back to project root (skipped for lightweight sidecars) if (!opts?.skipWorktreeSync && s.originalBasePath && s.originalBasePath !== s.basePath) { - try { - syncStateToProjectRoot(s.basePath, s.originalBasePath, s.currentMilestoneId); - } catch (e) { - debugLog("postUnit", { phase: "worktree-sync", error: String(e) }); - } + await runSafely("postUnit", "worktree-sync", () => { + syncStateToProjectRoot(s.basePath, s.originalBasePath!, s.currentMilestoneId); + }); } // Rewrite-docs completion if (s.currentUnit.type === "rewrite-docs") { - try { + await runSafely("postUnit", "rewrite-docs-resolve", async () => { await resolveAllOverrides(s.basePath); s.rewriteAttemptCount = 0; ctx.ui.notify("Override(s) resolved — rewrite-docs completed.", "info"); - } catch (e) { - debugLog("postUnit", { phase: "rewrite-docs-resolve", error: String(e) }); - } + }); } // Reactive state cleanup on slice completion if (s.currentUnit.type === "complete-slice") { - try { - const { milestone: mid, slice: sid } = parseUnitId(s.currentUnit.id); + await runSafely("postUnit", "reactive-state-cleanup", async () => { + const { milestone: mid, slice: sid } = parseUnitId(unit.id); if (mid && sid) { const { clearReactiveState } = await import("./reactive-graph.js"); clearReactiveState(s.basePath, mid, sid); } - } catch (e) { - debugLog("postUnit", { phase: "reactive-state-cleanup", error: String(e) }); - } + }); } // Post-triage: execute actionable resolutions diff --git a/src/resources/extensions/gsd/auto-utils.ts b/src/resources/extensions/gsd/auto-utils.ts new file mode 100644 index 000000000..ec8b23c6f --- /dev/null +++ b/src/resources/extensions/gsd/auto-utils.ts @@ -0,0 +1,25 @@ +// Shared utilities for the auto-loop modules (auto-post-unit, auto, etc.). + +import { debugLog } from "./debug-logger.js"; + +/** + * Run a non-fatal operation, logging any error via `debugLog` and continuing. + * + * Replaces the repeated try-catch-debugLog-continue boilerplate that wraps + * operations whose failure should not abort the post-unit pipeline. + * + * @param context - The debugLog event name (e.g. "postUnit") + * @param phase - The phase label attached to the debug entry + * @param fn - The operation to execute (may be sync or async) + */ +export async function runSafely( + context: string, + phase: string, + fn: () => Promise | void, +): Promise { + try { + await fn(); + } catch (e) { + debugLog(context, { phase, error: String(e) }); + } +}