refactor: extract getErrorMessage() helper to eliminate 65 inline duplicates (#1280)

Consolidate the repeated `err instanceof Error ? err.message : String(err)`
pattern into a single `getErrorMessage(err)` utility. Reduces visual noise in
catch blocks across 20 files in the GSD extension.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
TÂCHES 2026-03-18 19:12:44 -06:00 committed by GitHub
parent 922826ba8a
commit 2a2056bcd7
21 changed files with 91 additions and 65 deletions

View file

@ -63,6 +63,7 @@ import { debugLog, enableDebug, isDebugEnabled, getDebugLogPath } from "./debug-
import type { AutoSession } from "./auto/session.js";
import { existsSync, mkdirSync, readdirSync, statSync, unlinkSync } from "node:fs";
import { join } from "node:path";
import { getErrorMessage } from "./error-utils.js";
export interface BootstrapDeps {
shouldUseWorktreeIsolation: () => boolean;
@ -201,11 +202,11 @@ export async function bootstrapAutoSession(
if (!midMatch) continue;
const mid = midMatch[1];
if (resolveMilestoneFile(base, mid, "SUMMARY")) {
try { unlinkSync(join(runtimeUnitsDir, file)); } catch (e) { debugLog("stale-unit-cleanup-failed", { file, error: e instanceof Error ? e.message : String(e) }); }
try { unlinkSync(join(runtimeUnitsDir, file)); } catch (e) { debugLog("stale-unit-cleanup-failed", { file, error: getErrorMessage(e) }); }
}
}
}
} catch (e) { debugLog("stale-unit-dir-cleanup-failed", { error: e instanceof Error ? e.message : String(e) }); }
} catch (e) { debugLog("stale-unit-dir-cleanup-failed", { error: getErrorMessage(e) }); }
let state = await deriveState(base);
@ -343,7 +344,7 @@ export async function bootstrapAutoSession(
registerSigtermHandler(s.originalBasePath);
} catch (err) {
ctx.ui.notify(
`Auto-worktree setup failed: ${err instanceof Error ? err.message : String(err)}. Continuing in project root.`,
`Auto-worktree setup failed: ${getErrorMessage(err)}. Continuing in project root.`,
"warning",
);
}
@ -435,7 +436,7 @@ export async function bootstrapAutoSession(
}
} catch (err) {
ctx.ui.notify(
`Secrets check error: ${err instanceof Error ? err.message : String(err)}. Continuing without secrets.`,
`Secrets check error: ${getErrorMessage(err)}. Continuing without secrets.`,
"warning",
);
}
@ -453,7 +454,7 @@ export async function bootstrapAutoSession(
ctx.ui.notify("Removed stale .git/index.lock from prior crash.", "info");
}
}
} catch (e) { debugLog("git-lock-cleanup-failed", { error: e instanceof Error ? e.message : String(e) }); }
} catch (e) { debugLog("git-lock-cleanup-failed", { error: getErrorMessage(e) }); }
// Pre-flight: validate milestone queue
try {

View file

@ -20,6 +20,7 @@ import { closeoutUnit, type CloseoutOptions } from "./auto-unit-closeout.js";
import { saveActivityLog } from "./activity-log.js";
import { recoverTimedOutUnit, type RecoveryContext } from "./auto-timeout-recovery.js";
import type { AutoSession } from "./auto/session.js";
import { getErrorMessage } from "./error-utils.js";
export interface SupervisionContext {
s: AutoSession;
@ -127,7 +128,7 @@ export function startUnitSupervision(sctx: SupervisionContext): void {
);
await pauseAuto(ctx, pi);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
const message = getErrorMessage(err);
console.error(`[idle-watchdog] Unhandled error: ${message}`);
try {
ctx.ui.notify(`Idle watchdog error: ${message}`, "warning");
@ -159,7 +160,7 @@ export function startUnitSupervision(sctx: SupervisionContext): void {
);
await pauseAuto(ctx, pi);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
const message = getErrorMessage(err);
console.error(`[hard-timeout] Unhandled error: ${message}`);
try {
ctx.ui.notify(`Hard timeout error: ${message}`, "warning");

View file

@ -24,6 +24,7 @@ import { writeVerificationJSON } from "./verification-evidence.js";
import { removePersistedKey } from "./auto-recovery.js";
import type { AutoSession, PendingVerificationRetry } from "./auto/session.js";
import { join } from "node:path";
import { getErrorMessage } from "./error-utils.js";
export interface VerificationContext {
s: AutoSession;
@ -204,7 +205,7 @@ export async function runPostUnitVerification(
try {
await dispatchNextUnit(ctx, pi);
} catch (retryDispatchErr) {
const msg = retryDispatchErr instanceof Error ? retryDispatchErr.message : String(retryDispatchErr);
const msg = getErrorMessage(retryDispatchErr);
ctx.ui.notify(`Verification retry dispatch error: ${msg}`, "error");
startDispatchGapWatchdog(ctx, pi);
}

View file

@ -38,6 +38,7 @@ import {
nativeBranchDelete,
nativeBranchExists,
} from "./native-git-bridge.js";
import { getErrorMessage } from "./error-utils.js";
// ─── Module State ──────────────────────────────────────────────────────────
@ -81,7 +82,7 @@ export function runWorktreePostCreateHook(sourceDir: string, worktreeDir: string
});
return null;
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
return `Worktree post-create hook failed: ${msg}`;
}
}
@ -141,7 +142,7 @@ export function createAutoWorktree(basePath: string, milestoneId: string): strin
// Don't store originalBase -- caller can retry or clean up.
throw new GSDError(
GSD_IO_ERROR,
`Auto-worktree created at ${info.path} but chdir failed: ${err instanceof Error ? err.message : String(err)}`,
`Auto-worktree created at ${info.path} but chdir failed: ${getErrorMessage(err)}`,
);
}
@ -168,7 +169,7 @@ export function teardownAutoWorktree(
} catch (err) {
throw new GSDError(
GSD_IO_ERROR,
`Failed to chdir back to ${originalBasePath} during teardown: ${err instanceof Error ? err.message : String(err)}`,
`Failed to chdir back to ${originalBasePath} during teardown: ${getErrorMessage(err)}`,
);
}
@ -274,7 +275,7 @@ export function enterAutoWorktree(basePath: string, milestoneId: string): string
} catch (err) {
throw new GSDError(
GSD_IO_ERROR,
`Failed to enter auto-worktree at ${p}: ${err instanceof Error ? err.message : String(err)}`,
`Failed to enter auto-worktree at ${p}: ${getErrorMessage(err)}`,
);
}

View file

@ -189,6 +189,7 @@ import {
NEW_SESSION_TIMEOUT_MS, DISPATCH_HANG_TIMEOUT_MS,
} from "./auto/session.js";
import type { CompletedUnit, CurrentUnit, UnitRouting, StartModel, PendingVerificationRetry } from "./auto/session.js";
import { getErrorMessage } from "./error-utils.js";
// ── ENCAPSULATION INVARIANT ─────────────────────────────────────────────────
// ALL mutable auto-mode state lives in the AutoSession class (auto/session.ts).
@ -428,7 +429,7 @@ function startDispatchGapWatchdog(ctx: ExtensionContext, pi: ExtensionAPI): void
try {
await dispatchNextUnit(ctx, pi);
} catch (retryErr) {
const message = retryErr instanceof Error ? retryErr.message : String(retryErr);
const message = getErrorMessage(retryErr);
await stopAuto(ctx, pi, `Dispatch gap recovery failed: ${message}`);
return;
}
@ -458,14 +459,14 @@ export async function stopAuto(ctx?: ExtensionContext, pi?: ExtensionAPI, reason
// ── Auto-worktree: exit worktree and reset s.basePath on stop ──
if (s.currentMilestoneId && isInAutoWorktree(s.basePath)) {
try {
try { autoCommitCurrentBranch(s.basePath, "stop", s.currentMilestoneId); } catch (e) { debugLog("stop-auto-commit-failed", { error: e instanceof Error ? e.message : String(e) }); }
try { autoCommitCurrentBranch(s.basePath, "stop", s.currentMilestoneId); } catch (e) { debugLog("stop-auto-commit-failed", { error: getErrorMessage(e) }); }
teardownAutoWorktree(s.originalBasePath, s.currentMilestoneId, { preserveBranch: true });
s.basePath = s.originalBasePath;
s.gitService = createGitService(s.basePath);
ctx?.ui.notify("Exited auto-worktree (branch preserved for resume).", "info");
} catch (err) {
ctx?.ui.notify(
`Auto-worktree teardown failed: ${err instanceof Error ? err.message : String(err)}`,
`Auto-worktree teardown failed: ${getErrorMessage(err)}`,
"warning",
);
}
@ -476,7 +477,7 @@ export async function stopAuto(ctx?: ExtensionContext, pi?: ExtensionAPI, reason
try {
const { closeDatabase } = await import("./gsd-db.js");
closeDatabase();
} catch (e) { debugLog("db-close-failed", { error: e instanceof Error ? e.message : String(e) }); }
} catch (e) { debugLog("db-close-failed", { error: getErrorMessage(e) }); }
}
if (s.originalBasePath) {
@ -496,7 +497,7 @@ export async function stopAuto(ctx?: ExtensionContext, pi?: ExtensionAPI, reason
}
if (s.basePath) {
try { await rebuildState(s.basePath); } catch (e) { debugLog("stop-rebuild-state-failed", { error: e instanceof Error ? e.message : String(e) }); }
try { await rebuildState(s.basePath); } catch (e) { debugLog("stop-rebuild-state-failed", { error: getErrorMessage(e) }); }
}
if (isDebugEnabled()) {
@ -635,7 +636,7 @@ export async function startAuto(
}
} catch (err) {
ctx.ui.notify(
`Auto-worktree re-entry failed: ${err instanceof Error ? err.message : String(err)}. Continuing at current path.`,
`Auto-worktree re-entry failed: ${getErrorMessage(err)}. Continuing at current path.`,
"warning",
);
}
@ -647,13 +648,13 @@ export async function startAuto(
ctx.ui.setFooter(hideFooter);
ctx.ui.notify(s.stepMode ? "Step-mode resumed." : "Auto-mode resumed.", "info");
restoreHookState(s.basePath);
try { await rebuildState(s.basePath); } catch (e) { debugLog("resume-rebuild-state-failed", { error: e instanceof Error ? e.message : String(e) }); }
try { await rebuildState(s.basePath); } catch (e) { debugLog("resume-rebuild-state-failed", { error: getErrorMessage(e) }); }
try {
const report = await runGSDDoctor(s.basePath, { fix: true });
if (report.fixesApplied.length > 0) {
ctx.ui.notify(`Resume: applied ${report.fixesApplied.length} fix(es) to state.`, "info");
}
} catch (e) { debugLog("resume-doctor-failed", { error: e instanceof Error ? e.message : String(e) }); }
} catch (e) { debugLog("resume-doctor-failed", { error: getErrorMessage(e) }); }
await selfHealRuntimeRecords(s.basePath, ctx, s.completedKeySet);
invalidateAllCaches();
@ -700,7 +701,7 @@ export async function startAuto(
}
} catch (err) {
ctx.ui.notify(
`Secrets check error: ${err instanceof Error ? err.message : String(err)}. Continuing without secrets.`,
`Secrets check error: ${getErrorMessage(err)}. Continuing without secrets.`,
"warning",
);
}
@ -807,7 +808,7 @@ export async function handleAgentEnd(
try {
await dispatchNextUnit(ctx, pi);
} catch (dispatchErr) {
const message = dispatchErr instanceof Error ? dispatchErr.message : String(dispatchErr);
const message = getErrorMessage(dispatchErr);
ctx.ui.notify(
`Dispatch error after unit completion: ${message}. Retrying in ${DISPATCH_GAP_TIMEOUT_MS / 1000}s.`,
"error",
@ -838,7 +839,7 @@ export async function handleAgentEnd(
clearDispatchGapWatchdog();
setImmediate(() => {
handleAgentEnd(ctx, pi).catch((err) => {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
ctx.ui.notify(`Deferred agent_end retry failed: ${msg}`, "error");
pauseAuto(ctx, pi).catch(() => {});
});
@ -1086,7 +1087,7 @@ async function dispatchNextUnit(
);
} catch (err) {
ctx.ui.notify(
`Report generation failed: ${err instanceof Error ? err.message : String(err)}`,
`Report generation failed: ${getErrorMessage(err)}`,
"warning",
);
}
@ -1102,7 +1103,7 @@ async function dispatchNextUnit(
atomicWriteSync(file, JSON.stringify([]));
}
s.completedKeySet.clear();
} catch (e) { debugLog("completed-keys-reset-failed", { error: e instanceof Error ? e.message : String(e) }); }
} catch (e) { debugLog("completed-keys-reset-failed", { error: getErrorMessage(e) }); }
// ── Worktree lifecycle on milestone transition (#616) ──
if (isInAutoWorktree(s.basePath) && s.originalBasePath && shouldUseWorktreeIsolation()) {
@ -1121,7 +1122,7 @@ async function dispatchNextUnit(
}
} catch (err) {
ctx.ui.notify(
`Milestone merge failed during transition: ${err instanceof Error ? err.message : String(err)}`,
`Milestone merge failed during transition: ${getErrorMessage(err)}`,
"warning",
);
if (s.originalBasePath) {
@ -1146,7 +1147,7 @@ async function dispatchNextUnit(
ctx.ui.notify(`Created auto-worktree for ${mid} at ${wtPath}`, "info");
} catch (err) {
ctx.ui.notify(
`Auto-worktree creation for ${mid} failed: ${err instanceof Error ? err.message : String(err)}. Continuing in project root.`,
`Auto-worktree creation for ${mid} failed: ${getErrorMessage(err)}. Continuing in project root.`,
"warning",
);
}
@ -1190,7 +1191,7 @@ async function dispatchNextUnit(
}
} catch (err) {
ctx.ui.notify(
`Milestone merge failed: ${err instanceof Error ? err.message : String(err)}`,
`Milestone merge failed: ${getErrorMessage(err)}`,
"warning",
);
if (s.originalBasePath) {
@ -1216,7 +1217,7 @@ async function dispatchNextUnit(
}
} catch (err) {
ctx.ui.notify(
`Milestone merge failed (branch mode): ${err instanceof Error ? err.message : String(err)}`,
`Milestone merge failed (branch mode): ${getErrorMessage(err)}`,
"warning",
);
}
@ -1276,7 +1277,7 @@ async function dispatchNextUnit(
atomicWriteSync(file, JSON.stringify([]));
}
s.completedKeySet.clear();
} catch (e) { debugLog("completed-keys-reset-failed", { error: e instanceof Error ? e.message : String(e) }); }
} catch (e) { debugLog("completed-keys-reset-failed", { error: getErrorMessage(e) }); }
// ── Milestone merge ──
if (s.currentMilestoneId && isInAutoWorktree(s.basePath) && s.originalBasePath) {
try {
@ -1292,7 +1293,7 @@ async function dispatchNextUnit(
);
} catch (err) {
ctx.ui.notify(
`Milestone merge failed: ${err instanceof Error ? err.message : String(err)}`,
`Milestone merge failed: ${getErrorMessage(err)}`,
"warning",
);
if (s.originalBasePath) {
@ -1318,7 +1319,7 @@ async function dispatchNextUnit(
}
} catch (err) {
ctx.ui.notify(
`Milestone merge failed (branch mode): ${err instanceof Error ? err.message : String(err)}`,
`Milestone merge failed (branch mode): ${getErrorMessage(err)}`,
"warning",
);
}
@ -1417,7 +1418,7 @@ async function dispatchNextUnit(
}
} catch (err) {
ctx.ui.notify(
`Secrets collection error: ${err instanceof Error ? err.message : String(err)}. Continuing with next task.`,
`Secrets collection error: ${getErrorMessage(err)}. Continuing with next task.`,
"warning",
);
}
@ -1628,7 +1629,7 @@ async function dispatchNextUnit(
);
result = await Promise.race([sessionPromise, timeoutPromise]);
} catch (sessionErr) {
const msg = sessionErr instanceof Error ? sessionErr.message : String(sessionErr);
const msg = getErrorMessage(sessionErr);
ctx.ui.notify(`Session creation failed: ${msg}. Retrying via watchdog.`, "error");
throw new Error(`newSession() failed: ${msg}`);
}
@ -1704,7 +1705,7 @@ async function dispatchNextUnit(
const { reorderForCaching } = await import("./prompt-ordering.js");
finalPrompt = reorderForCaching(finalPrompt);
} catch (reorderErr) {
const msg = reorderErr instanceof Error ? reorderErr.message : String(reorderErr);
const msg = getErrorMessage(reorderErr);
process.stderr.write(`[gsd] prompt reorder failed (non-fatal): ${msg}\n`);
}

View file

@ -5,6 +5,7 @@
*/
import type { ExtensionCommandContext } from "@gsd/pi-coding-agent";
import { getErrorMessage } from "./error-utils.js";
export interface InspectData {
schemaVersion: number | null;
@ -84,7 +85,7 @@ export async function handleInspect(ctx: ExtensionCommandContext): Promise<void>
ctx.ui.notify(formatInspectOutput(data), "info");
} catch (err) {
process.stderr.write(`gsd-db: /gsd inspect failed: ${err instanceof Error ? err.message : String(err)}\n`);
process.stderr.write(`gsd-db: /gsd inspect failed: ${getErrorMessage(err)}\n`);
ctx.ui.notify("Failed to inspect GSD database. Check stderr for details.", "error");
}
}

View file

@ -21,6 +21,7 @@ import { loadPrompt } from "./prompt-loader.js";
import { gsdRoot } from "./paths.js";
import { createGitService, runGit } from "./git-service.js";
import { isAutoActive, isAutoPaused } from "./auto.js";
import { getErrorMessage } from "./error-utils.js";
// ─── Helpers ─────────────────────────────────────────────────────────────────
@ -439,7 +440,7 @@ export async function handleStart(
branchCreated = true;
}
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
const message = getErrorMessage(err);
ctx.ui.notify(
`Could not create branch ${branchName}: ${message}. Working on current branch.`,
"warning",

View file

@ -0,0 +1,6 @@
/**
* Extract a human-readable message from an unknown caught value.
*/
export function getErrorMessage(err: unknown): string {
return err instanceof Error ? err.message : String(err);
}

View file

@ -12,6 +12,7 @@ import {
import type { UnitMetrics } from "./metrics.js";
import { gsdRoot } from "./paths.js";
import { formatDuration, fileLink } from "../shared/mod.js";
import { getErrorMessage } from "./error-utils.js";
/**
* Open a file in the user's default browser.
@ -226,7 +227,7 @@ export async function handleExport(args: string, ctx: ExtensionCommandContext, b
}
} catch (err) {
ctx.ui.notify(
`HTML export failed: ${err instanceof Error ? err.message : String(err)}`,
`HTML export failed: ${getErrorMessage(err)}`,
"error",
);
}

View file

@ -33,6 +33,7 @@ import {
nativeAddPaths,
} from "./native-git-bridge.js";
import { GSDError, GSD_MERGE_CONFLICT, GSD_GIT_ERROR } from "./errors.js";
import { getErrorMessage } from "./error-utils.js";
// ─── Types ─────────────────────────────────────────────────────────────────
@ -281,7 +282,7 @@ export function runGit(basePath: string, args: string[], options: { allowFailure
}).trim();
} catch (error) {
if (options.allowFailure) return "";
const message = error instanceof Error ? error.message : String(error);
const message = getErrorMessage(error);
throw new GSDError(GSD_GIT_ERROR, `git ${args.join(" ")} failed in ${basePath}: ${filterGitSvnNoise(message)}`);
}
}
@ -533,7 +534,7 @@ export class GitServiceImpl {
execSync(command, { cwd: this.basePath, stdio: "pipe", encoding: "utf-8" });
return { passed: true, skipped: false, command };
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
return { passed: false, skipped: false, command, error: msg };
}
}

View file

@ -44,6 +44,7 @@ export {
showQueue, handleQueueReorder, showQueueAdd,
buildExistingMilestonesContext,
} from "./guided-flow-queue.js";
import { getErrorMessage } from "./error-utils.js";
// ─── Commit Instruction Helpers ──────────────────────────────────────────────
@ -158,9 +159,9 @@ export function checkAutoStartAfterDiscuss(): boolean {
pendingAutoStart = null;
startAuto(ctx, pi, basePath, false, { step }).catch((err) => {
ctx.ui.notify(`Auto-start failed: ${err instanceof Error ? err.message : String(err)}`, "error");
ctx.ui.notify(`Auto-start failed: ${getErrorMessage(err)}`, "error");
if (process.env.GSD_DEBUG) console.error('[gsd] auto start error:', err);
debugLog("auto-start-failed", { error: err instanceof Error ? err.message : String(err) });
debugLog("auto-start-failed", { error: getErrorMessage(err) });
});
return true;
}

View file

@ -64,6 +64,7 @@ import { pauseAutoForProviderError, classifyProviderError } from "./provider-err
import { toPosixPath } from "../shared/mod.js";
import { isParallelActive, shutdownParallel } from "./parallel-orchestrator.js";
import { DEFAULT_BASH_TIMEOUT_SECS } from "./constants.js";
import { getErrorMessage } from "./error-utils.js";
/**
* Ensure the GSD database is available, auto-initializing if needed.
@ -374,7 +375,7 @@ export default function (pi: ExtensionAPI) {
details: { operation: "save_decision", id },
};
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
process.stderr.write(`gsd-db: gsd_save_decision tool failed: ${msg}\n`);
return {
content: [{ type: "text" as const, text: `Error saving decision: ${msg}` }],
@ -445,7 +446,7 @@ export default function (pi: ExtensionAPI) {
details: { operation: "update_requirement", id: params.id },
};
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
process.stderr.write(`gsd-db: gsd_update_requirement tool failed: ${msg}\n`);
return {
content: [{ type: "text" as const, text: `Error updating requirement: ${msg}` }],
@ -525,7 +526,7 @@ export default function (pi: ExtensionAPI) {
details: { operation: "save_summary", path: relativePath, artifact_type: params.artifact_type },
};
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
process.stderr.write(`gsd-db: gsd_save_summary tool failed: ${msg}\n`);
return {
content: [{ type: "text" as const, text: `Error saving artifact: ${msg}` }],
@ -574,7 +575,7 @@ export default function (pi: ExtensionAPI) {
details: { operation: "generate_milestone_id", id: newId, existingCount: existingIds.length, reservedCount: reservedMilestoneIds.size, uniqueEnabled },
};
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
return {
content: [{ type: "text" as const, text: `Error generating milestone ID: ${msg}` }],
isError: true,
@ -993,7 +994,7 @@ export default function (pi: ExtensionAPI) {
} catch (err) {
// Safety net: if handleAgentEnd throws despite its internal try-catch,
// ensure auto-mode stops gracefully instead of silently stalling (#381).
const message = err instanceof Error ? err.message : String(err);
const message = getErrorMessage(err);
ctx.ui.notify(
`Auto-mode error in agent_end handler: ${message}. Stopping auto-mode.`,
"error",

View file

@ -16,6 +16,7 @@ import { getEnvApiKey } from "@gsd/pi-ai";
import { existsSync, statSync, chmodSync } from "node:fs";
import { join, dirname } from "node:path";
import { mkdirSync } from "node:fs";
import { getErrorMessage } from "./error-utils.js";
// ─── Provider Registry ─────────────────────────────────────────────────────────
@ -552,7 +553,7 @@ export async function testProviderKey(
return { provider, status: "error", message: `HTTP ${res.status}`, latencyMs };
} catch (err) {
const latencyMs = Date.now() - start;
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
if (msg.includes("timeout") || msg.includes("AbortError")) {
return { provider, status: "error", message: "timeout (15s)", latencyMs };
}

View file

@ -16,6 +16,7 @@
import * as fs from 'node:fs';
import * as path from 'node:path';
import { getErrorMessage } from "./error-utils.js";
// ============================================================================
// Type Definitions
@ -194,7 +195,7 @@ export function parseMarketplaceJson(repoRoot: string):
} catch (err) {
return {
success: false,
error: `Failed to read marketplace.json: ${err instanceof Error ? err.message : String(err)}`
error: `Failed to read marketplace.json: ${getErrorMessage(err)}`
};
}
@ -204,7 +205,7 @@ export function parseMarketplaceJson(repoRoot: string):
} catch (err) {
return {
success: false,
error: `Failed to parse marketplace.json: ${err instanceof Error ? err.message : String(err)}`
error: `Failed to parse marketplace.json: ${getErrorMessage(err)}`
};
}
@ -293,7 +294,7 @@ export function inspectPlugin(
}
} catch (err) {
// Fall back to marketplace inline or derived
result.error = `Failed to parse plugin.json: ${err instanceof Error ? err.message : String(err)}`;
result.error = `Failed to parse plugin.json: ${getErrorMessage(err)}`;
}
}

View file

@ -9,6 +9,7 @@
import { existsSync, lstatSync, mkdirSync, readdirSync, renameSync, cpSync, rmSync, symlinkSync } from "node:fs";
import { join } from "node:path";
import { externalGsdRoot } from "./repo-identity.js";
import { getErrorMessage } from "./error-utils.js";
export interface MigrationResult {
migrated: boolean;
@ -47,7 +48,7 @@ export function migrateToExternalState(basePath: string): MigrationResult {
return { migrated: false, error: ".gsd exists but is not a directory or symlink" };
}
} catch (err) {
return { migrated: false, error: `Cannot stat .gsd: ${err instanceof Error ? err.message : String(err)}` };
return { migrated: false, error: `Cannot stat .gsd: ${getErrorMessage(err)}` };
}
const externalPath = externalGsdRoot(basePath);
@ -114,7 +115,7 @@ export function migrateToExternalState(basePath: string): MigrationResult {
return {
migrated: false,
error: `Migration failed: ${err instanceof Error ? err.message : String(err)}`,
error: `Migration failed: ${getErrorMessage(err)}`,
};
}
}

View file

@ -9,6 +9,7 @@ import { randomInt } from "node:crypto";
import { readdirSync, existsSync } from "node:fs";
import { milestonesDir } from "./paths.js";
import { loadQueueOrder, sortByQueueOrder } from "./queue-order.js";
import { getErrorMessage } from "./error-utils.js";
// ─── Regex ──────────────────────────────────────────────────────────────────
@ -88,7 +89,7 @@ export function findMilestoneIds(basePath: string): string[] {
} catch (err) {
// Log why milestone scanning failed — silent [] here causes infinite loops (#456)
if (existsSync(dir)) {
console.error(`[gsd] findMilestoneIds: .gsd/milestones/ exists but readdirSync failed — ${err instanceof Error ? err.message : String(err)}`);
console.error(`[gsd] findMilestoneIds: .gsd/milestones/ exists but readdirSync failed — ${getErrorMessage(err)}`);
}
return [];
}

View file

@ -10,6 +10,7 @@ import { existsSync, readFileSync, unlinkSync, rmSync } from "node:fs";
import { join } from "node:path";
import { GSDError, GSD_GIT_ERROR } from "./errors.js";
import { GIT_NO_PROMPT_ENV } from "./git-constants.js";
import { getErrorMessage } from "./error-utils.js";
// Issue #453: keep auto-mode bookkeeping on the stable git CLI path unless a
// caller explicitly opts into the native helper.
@ -716,7 +717,7 @@ export function nativeCommit(
try {
return native.gitCommit(basePath, message, options?.allowEmpty);
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
const msg = getErrorMessage(e);
if (msg.includes("nothing to commit")) return null;
throw e;
}

View file

@ -11,6 +11,7 @@ import { mergeMilestoneToMain } from "./auto-worktree.js";
import { MergeConflictError } from "./git-service.js";
import { removeSessionStatus } from "./session-status-io.js";
import type { WorkerInfo } from "./parallel-orchestrator.js";
import { getErrorMessage } from "./error-utils.js";
// ─── Types ─────────────────────────────────────────────────────────────────
@ -99,7 +100,7 @@ export async function mergeCompletedMilestone(
return {
milestoneId,
success: false,
error: err instanceof Error ? err.message : String(err),
error: getErrorMessage(err),
};
}
}

View file

@ -38,6 +38,7 @@ import {
analyzeParallelEligibility,
type ParallelCandidates,
} from "./parallel-eligibility.js";
import { getErrorMessage } from "./error-utils.js";
// ─── Types ─────────────────────────────────────────────────────────────────
@ -363,7 +364,7 @@ export async function startParallel(
started.push(mid);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
const message = getErrorMessage(err);
errors.push({ mid, error: message });
}
}

View file

@ -15,6 +15,7 @@ import { join } from "node:path";
import { loadPrompt } from "./prompt-loader.js";
import { gsdRoot } from "./paths.js";
import { createGitService, runGit } from "./git-service.js";
import { getErrorMessage } from "./error-utils.js";
// ─── Quick Task Helpers ───────────────────────────────────────────────────────
@ -122,7 +123,7 @@ export async function handleQuick(
}
} catch (err) {
// Branch creation failed — continue on current branch
const message = err instanceof Error ? err.message : String(err);
const message = getErrorMessage(err);
ctx.ui.notify(`Could not create branch ${branchName}: ${message}. Working on current branch.`, "warning");
}
}

View file

@ -34,6 +34,7 @@ import type { FileLineStat } from "./worktree-manager.js";
import { existsSync, realpathSync, readdirSync, rmSync, unlinkSync } from "node:fs";
import { nativeMergeAbort } from "./native-git-bridge.js";
import { join, sep } from "node:path";
import { getErrorMessage } from "./error-utils.js";
/**
* Tracks the original project root so we can switch back.
@ -370,7 +371,7 @@ async function handleCreate(
"info",
);
} catch (error) {
const msg = error instanceof Error ? error.message : String(error);
const msg = getErrorMessage(error);
ctx.ui.notify(`Failed to create worktree: ${msg}`, "error");
}
}
@ -418,7 +419,7 @@ async function handleSwitch(
"info",
);
} catch (error) {
const msg = error instanceof Error ? error.message : String(error);
const msg = getErrorMessage(error);
ctx.ui.notify(`Failed to switch to worktree: ${msg}`, "error");
}
}
@ -528,7 +529,7 @@ async function handleList(
ctx.ui.notify(lines.join("\n"), "info");
} catch (error) {
const msg = error instanceof Error ? error.message : String(error);
const msg = getErrorMessage(error);
ctx.ui.notify(`Failed to list worktrees: ${msg}`, "error");
}
}
@ -646,7 +647,7 @@ async function handleMerge(
);
return;
} catch (mergeErr) {
const mergeMsg = mergeErr instanceof Error ? mergeErr.message : String(mergeErr);
const mergeMsg = getErrorMessage(mergeErr);
const isConflict = /conflict/i.test(mergeMsg);
if (isConflict) {
@ -703,7 +704,7 @@ async function handleMerge(
"info",
);
} catch (error) {
const msg = error instanceof Error ? error.message : String(error);
const msg = getErrorMessage(error);
ctx.ui.notify(`Failed to start merge: ${msg}`, "error");
}
}
@ -746,7 +747,7 @@ async function handleRemove(
ctx.ui.notify(`${CLR.ok("✓")} Worktree ${CLR.name(name)} removed ${CLR.muted("(branch deleted)")}.`, "info");
} catch (error) {
const msg = error instanceof Error ? error.message : String(error);
const msg = getErrorMessage(error);
ctx.ui.notify(`Failed to remove worktree: ${msg}`, "error");
}
}
@ -800,7 +801,7 @@ async function handleRemoveAll(
if (failed.length > 0) lines.push(`${CLR.warn("✗")} Failed: ${failed.map(n => CLR.name(n)).join(", ")}`);
ctx.ui.notify(lines.join("\n"), failed.length > 0 ? "warning" : "info");
} catch (error) {
const msg = error instanceof Error ? error.message : String(error);
const msg = getErrorMessage(error);
ctx.ui.notify(`Failed to remove worktrees: ${msg}`, "error");
}
}