refactor: replace all inline error message ternaries with getErrorMessage()

Eliminates ~120 repetitions of `err instanceof Error ? err.message : String(err)`
across the entire extension source tree. All callers now import and use
`getErrorMessage` from the canonical `./error-utils.js`.

Files updated (56 files):
- auto.js, auto-worktree.js, auto-recovery.js, auto-dashboard.js, auto-timers.js
- auto-prompts.js, auto-start.js, auto-post-unit.js, auto-model-selection.js
- auto/phases.js, auto/loop.js, auto/infra-errors.js
- autonomous-solver-eval.js, bootstrap/agent-end-recovery.js, bootstrap/db-tools.js
- bootstrap/exec-tools.js, bootstrap/journal-tools.js, bootstrap/register-extension.js
- bootstrap/register-hooks.js, canonical-milestone-plan.js, changelog.js
- clean-root-preflight.js, code-intelligence.js, commands-add-tests.js
- commands-debug.js, commands-eval-review.js, commands-handlers.js
- commands-maintenance.js, commands-pr-branch.js, commands-scan.js, commands-ship.js
- commands-todo.js, commands-worktree.js, definition-io.js, doctor.js
- doctor-config-checks.js, doctor-engine-checks.js, ecosystem/loader.js
- eval-review-schema.js, exec-sandbox.js, execution-instruction-guard.js
- graph-context.js, hook-emitter.js, index.js, learning/runtime.js
- lifecycle-hooks.js, onboarding-state.js, orphan-worktree-sweep.js
- planning-depth.js, quick.js, scaffold-keeper.js, sf-db/sf-db-core.js
- slice-cadence.js, sm-client.js, spec-projections.js, subagent/background-jobs.js
- subagent/isolation.js, sync-scheduler.js, tools/exec-tool.js
- tools/sift-search-tool.js, tools/workflow-tool-executors.js, ui/index.js
- uok/a2a-agent-server.js, uok/auto-dispatch.js, uok/auto-unit-closeout.js
- uok/auto-verification.js, uok/chaos-monkey.js, uok/gate-runner.js
- vault-resolver.js, workflow-install.js, workflow-plugins.js, worktree-manager.js
- worktree-resolver.js

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Mikael Hugo 2026-05-11 14:46:30 +02:00
parent 8a7f6de782
commit 04322f110a
74 changed files with 333 additions and 262 deletions

View file

@ -348,7 +348,7 @@ export function updateSliceProgressCache(_base, mid, activeSid) {
// Non-fatal — just omit task count
logWarning(
"dashboard",
`operation failed: ${err instanceof Error ? err.message : String(err)}`,
`operation failed: ${getErrorMessage(err)}`,
);
}
}
@ -363,7 +363,7 @@ export function updateSliceProgressCache(_base, mid, activeSid) {
// Non-fatal — widget just won't show progress bar
logWarning(
"dashboard",
`operation failed: ${err instanceof Error ? err.message : String(err)}`,
`operation failed: ${getErrorMessage(err)}`,
);
}
}
@ -397,7 +397,7 @@ function refreshLastCommit(basePath) {
// Non-fatal — just skip last commit display
logWarning(
"dashboard",
`operation failed: ${err instanceof Error ? err.message : String(err)}`,
`operation failed: ${getErrorMessage(err)}`,
);
}
}
@ -521,7 +521,7 @@ function persistWidgetMode(
/* non-fatal — mode still set in memory */
logWarning(
"dashboard",
`file write failed: ${err instanceof Error ? err.message : String(err)}`,
`file write failed: ${getErrorMessage(err)}`,
);
}
}
@ -588,7 +588,7 @@ export function updateProgressWidget(
/* not in git repo */
logWarning(
"dashboard",
`git branch detection failed: ${err instanceof Error ? err.message : String(err)}`,
`git branch detection failed: ${getErrorMessage(err)}`,
);
}
// Cache short pwd (last 2 path segments only) + worktree/branch info
@ -632,7 +632,7 @@ export function updateProgressWidget(
} catch (err) {
logWarning(
"dashboard",
`RTK savings lookup failed: ${err instanceof Error ? err.message : String(err)}`,
`RTK savings lookup failed: ${getErrorMessage(err)}`,
);
cachedRtkLabel = null;
}
@ -656,7 +656,7 @@ export function updateProgressWidget(
/* non-fatal */
logWarning(
"dashboard",
`DB status update failed: ${err instanceof Error ? err.message : String(err)}`,
`DB status update failed: ${getErrorMessage(err)}`,
);
}
}, 15_000);

View file

@ -31,6 +31,7 @@ import { resolveUokFlags } from "./uok/flags.js";
import { applyModelPolicyFilter } from "./uok/model-policy.js";
import { logWarning } from "./workflow-logger.js";
import { getRequiredWorkflowToolsForAutoUnit } from "./workflow-tools.js";
import { getErrorMessage } from "./error-utils.js";
/**
* Thrown when the model-policy gate rejects every candidate model for a unit
* dispatch (#4959 / #4681 / #4850). The auto-loop catches this specifically
@ -941,7 +942,7 @@ export function buildFlatRateContext(provider, ctx, prefs) {
// fall through with authMode undefined and surface the cause.
logWarning(
"dispatch",
`flat-rate auth-mode lookup failed for ${provider}: ${err instanceof Error ? err.message : String(err)}`,
`flat-rate auth-mode lookup failed for ${provider}: ${getErrorMessage(err)}`,
);
}
}

View file

@ -168,6 +168,7 @@ import { getAutoSession } from "./auto/session.js";
import { describeNextUnit } from "./auto-dashboard.js";
import { _resetHasChangesCache } from "./native-git-bridge.js";
import { autoCommitCurrentBranch } from "./worktree.js";
import { getErrorMessage } from "./error-utils.js";
/**
* Detect summary files written directly to disk without the LLM calling
@ -312,7 +313,7 @@ export async function autoCommitUnit(basePath, unitType, unitId, ctx) {
} catch (err) {
logWarning(
"engine",
`GitHub issue lookup failed: ${err instanceof Error ? err.message : String(err)}`,
`GitHub issue lookup failed: ${getErrorMessage(err)}`,
);
}
taskContext = {
@ -465,7 +466,7 @@ export async function postUnitPreVerification(pctx, opts) {
// GitHub sync not available — skip
logWarning(
"engine",
`GitHub issue lookup failed: ${err instanceof Error ? err.message : String(err)}`,
`GitHub issue lookup failed: ${getErrorMessage(err)}`,
);
}
taskContext = {
@ -810,7 +811,7 @@ export async function postUnitPreVerification(pctx, opts) {
} catch (err) {
logWarning(
"engine",
`slice-cadence: failed to record milestone start SHA: ${err instanceof Error ? err.message : String(err)}`,
`slice-cadence: failed to record milestone start SHA: ${getErrorMessage(err)}`,
);
}
}
@ -845,7 +846,7 @@ export async function postUnitPreVerification(pctx, opts) {
return;
}
logError("engine", `slice-cadence merge failed for ${sid}`, {
error: err instanceof Error ? err.message : String(err),
error: getErrorMessage(err),
});
// Non-conflict failures (dirty main, rev-walk error, etc.) can
// leave the checkout in an unexpected state. Stop autonomous mode so

View file

@ -88,6 +88,7 @@ import {
buildResumeSection,
} from "./workflow-helpers.js";
import { logWarning } from "./workflow-logger.js";
import { getErrorMessage } from "./error-utils.js";
// ─── Preamble Cap ─────────────────────────────────────────────────────────────
/**
@ -160,7 +161,7 @@ function buildCompleteSliceControlBlock(mid, sid, base) {
} catch (err) {
logWarning(
"prompt",
`complete-slice task ledger failed: ${err instanceof Error ? err.message : String(err)}`,
`complete-slice task ledger failed: ${getErrorMessage(err)}`,
);
return "";
}
@ -361,7 +362,7 @@ async function inlineDependencySummaries(mid, sid, base, budgetChars) {
} catch (err) {
logWarning(
"prompt",
`inlineDependencySummaries DB lookup failed: ${err instanceof Error ? err.message : String(err)}`,
`inlineDependencySummaries DB lookup failed: ${getErrorMessage(err)}`,
);
}
// If DB didn't provide depends, fall back to roadmap parsing
@ -449,7 +450,7 @@ export async function inlineDecisionsFromDb(base, milestoneId, scope, level) {
} catch (err) {
logWarning(
"prompt",
`inlineDecisionsFromDb failed: ${err instanceof Error ? err.message : String(err)}`,
`inlineDecisionsFromDb failed: ${getErrorMessage(err)}`,
);
}
// DB unavailable — fall back to filesystem
@ -485,7 +486,7 @@ export async function inlineRequirementsFromDb(
} catch (err) {
logWarning(
"prompt",
`inlineRequirementsFromDb failed: ${err instanceof Error ? err.message : String(err)}`,
`inlineRequirementsFromDb failed: ${getErrorMessage(err)}`,
);
}
return inlineSfRootFile(base, "requirements.md", "Requirements");
@ -507,7 +508,7 @@ export async function inlineProjectFromDb(base) {
} catch (err) {
logWarning(
"prompt",
`inlineProjectFromDb failed: ${err instanceof Error ? err.message : String(err)}`,
`inlineProjectFromDb failed: ${getErrorMessage(err)}`,
);
}
return inlineSfRootFile(base, "project.md", "Project");
@ -936,7 +937,7 @@ export function buildSkillActivationBlock(params) {
} catch (err) {
logWarning(
"prompt",
`parseTaskPlanFile failed: ${err instanceof Error ? err.message : String(err)}`,
`parseTaskPlanFile failed: ${getErrorMessage(err)}`,
);
}
}
@ -2132,7 +2133,7 @@ export async function buildCompleteMilestonePrompt(mid, midTitle, base, level) {
} catch (err) {
logWarning(
"prompt",
`buildCompleteMilestonePrompt DB lookup failed: ${err instanceof Error ? err.message : String(err)}`,
`buildCompleteMilestonePrompt DB lookup failed: ${getErrorMessage(err)}`,
);
}
// File-based fallback: parse roadmap for slice IDs when DB has no data
@ -2278,7 +2279,7 @@ export async function buildValidateMilestonePrompt(mid, midTitle, base, level) {
} catch (err) {
logWarning(
"prompt",
`buildValidateMilestonePrompt verification classes lookup failed: ${err instanceof Error ? err.message : String(err)}`,
`buildValidateMilestonePrompt verification classes lookup failed: ${getErrorMessage(err)}`,
);
}
// Inline all slice summaries and assessment results
@ -2293,7 +2294,7 @@ export async function buildValidateMilestonePrompt(mid, midTitle, base, level) {
} catch (err) {
logWarning(
"prompt",
`buildValidateMilestonePrompt slice IDs lookup failed: ${err instanceof Error ? err.message : String(err)}`,
`buildValidateMilestonePrompt slice IDs lookup failed: ${getErrorMessage(err)}`,
);
}
// File-based fallback: parse roadmap for slice IDs when DB has no data
@ -2511,7 +2512,7 @@ export async function buildReplanSlicePrompt(mid, midTitle, sid, sTitle, base) {
} catch (err) {
logWarning(
"prompt",
`loadReplanCaptures failed: ${err instanceof Error ? err.message : String(err)}`,
`loadReplanCaptures failed: ${getErrorMessage(err)}`,
);
}
return loadPrompt("replan-slice", {
@ -2684,7 +2685,7 @@ export async function buildReassessRoadmapPrompt(
} catch (err) {
logWarning(
"prompt",
`loadDeferredCaptures failed: ${err instanceof Error ? err.message : String(err)}`,
`loadDeferredCaptures failed: ${getErrorMessage(err)}`,
);
}
const reassessCommitInstruction =
@ -3016,7 +3017,7 @@ export async function buildRewriteDocsPrompt(
} catch (err) {
logWarning(
"prompt",
`buildRewriteDocsPrompt DB task lookup failed: ${err instanceof Error ? err.message : String(err)}`,
`buildRewriteDocsPrompt DB task lookup failed: ${getErrorMessage(err)}`,
);
}
if (!incompleteTasks) {

View file

@ -163,7 +163,7 @@ function getChangedFilesSinceBranch(basePath, targetBranch) {
// merge-base failed — fall back
logWarning(
"recovery",
`merge-base detection failed: ${err instanceof Error ? err.message : String(err)}`,
`merge-base detection failed: ${getErrorMessage(err)}`,
);
}
// Fallback: check last 20 commits
@ -259,7 +259,7 @@ export function verifyExpectedArtifact(unitType, unitId, base) {
// DB unavailable — treat as verified to avoid blocking
logWarning(
"recovery",
`gate-evaluate DB check failed: ${err instanceof Error ? err.message : String(err)}`,
`gate-evaluate DB check failed: ${getErrorMessage(err)}`,
);
}
return true;
@ -306,7 +306,7 @@ export function verifyExpectedArtifact(unitType, unitId, base) {
} catch (err) {
logWarning(
"recovery",
`parallel-research verification failed: ${err instanceof Error ? err.message : String(err)}`,
`parallel-research verification failed: ${getErrorMessage(err)}`,
);
return false;
}
@ -329,7 +329,7 @@ export function verifyExpectedArtifact(unitType, unitId, base) {
} catch (err) {
logWarning(
"recovery",
`plan-milestone roadmap verification failed: ${err instanceof Error ? err.message : String(err)}`,
`plan-milestone roadmap verification failed: ${getErrorMessage(err)}`,
);
return false;
}
@ -410,7 +410,7 @@ export function verifyExpectedArtifact(unitType, unitId, base) {
// Parse failure — don't block; slice plan may have non-standard format
logWarning(
"recovery",
`plan-slice task plan verification failed: ${err instanceof Error ? err.message : String(err)}`,
`plan-slice task plan verification failed: ${getErrorMessage(err)}`,
);
}
}
@ -555,7 +555,7 @@ function abortAndResetMerge(basePath, hasMergeHead, squashMsgPath) {
/* best-effort */
logWarning(
"recovery",
`git merge-abort failed: ${err instanceof Error ? err.message : String(err)}`,
`git merge-abort failed: ${getErrorMessage(err)}`,
);
}
} else if (squashMsgPath) {
@ -565,7 +565,7 @@ function abortAndResetMerge(basePath, hasMergeHead, squashMsgPath) {
/* best-effort */
logWarning(
"recovery",
`file unlink failed: ${err instanceof Error ? err.message : String(err)}`,
`file unlink failed: ${getErrorMessage(err)}`,
);
}
}
@ -575,7 +575,7 @@ function abortAndResetMerge(basePath, hasMergeHead, squashMsgPath) {
/* best-effort */
logError(
"recovery",
`git reset failed: ${err instanceof Error ? err.message : String(err)}`,
`git reset failed: ${getErrorMessage(err)}`,
);
}
}

View file

@ -112,6 +112,7 @@ import {
isInsideWorktreesDir,
} from "./worktree-manager.js";
import { emitWorktreeOrphaned } from "./worktree-telemetry.js";
import { getErrorMessage } from "./error-utils.js";
/**
* Bootstrap a fresh autonomous mode session. Handles everything from git init
@ -143,7 +144,7 @@ export async function openProjectDbIfPresent(basePath) {
} catch (err) {
logWarning(
"engine",
`sf-db: failed to open existing database: ${err instanceof Error ? err.message : String(err)}`,
`sf-db: failed to open existing database: ${getErrorMessage(err)}`,
);
}
}
@ -242,7 +243,7 @@ export function auditOrphanedMilestoneBranches(basePath, isolationMode) {
} catch (err) {
logWarning(
"engine",
`worktree-orphaned telemetry failed for ${milestoneId}: ${err instanceof Error ? err.message : String(err)}`,
`worktree-orphaned telemetry failed for ${milestoneId}: ${getErrorMessage(err)}`,
);
}
continue;
@ -261,7 +262,7 @@ export function auditOrphanedMilestoneBranches(basePath, isolationMode) {
);
} catch (err) {
warnings.push(
`Failed to delete merged branch ${branch}: ${err instanceof Error ? err.message : String(err)}`,
`Failed to delete merged branch ${branch}: ${getErrorMessage(err)}`,
);
}
// Clean up orphaned worktree directory if it exists
@ -318,7 +319,7 @@ export function auditOrphanedMilestoneBranches(basePath, isolationMode) {
} catch (err) {
logWarning(
"engine",
`worktree-orphaned telemetry failed for ${milestoneId}: ${err instanceof Error ? err.message : String(err)}`,
`worktree-orphaned telemetry failed for ${milestoneId}: ${getErrorMessage(err)}`,
);
}
}
@ -470,7 +471,7 @@ export async function bootstrapAutoSession(
/* nothing to commit */
logWarning(
"engine",
`mkdir failed: ${err instanceof Error ? err.message : String(err)}`,
`mkdir failed: ${getErrorMessage(err)}`,
);
}
}
@ -524,7 +525,7 @@ export async function bootstrapAutoSession(
// Non-fatal — defensive cleanup, never block bootstrap
logWarning(
"bootstrap",
`stale slice runtime reconciliation failed: ${err instanceof Error ? err.message : String(err)}`,
`stale slice runtime reconciliation failed: ${getErrorMessage(err)}`,
);
}
// Open the project-root DB before deriveState so DB-backed state
@ -543,7 +544,7 @@ export async function bootstrapAutoSession(
// Non-fatal — defensive cleanup, never block bootstrap
logWarning(
"bootstrap",
`durable complete runtime reconciliation failed: ${err instanceof Error ? err.message : String(err)}`,
`durable complete runtime reconciliation failed: ${getErrorMessage(err)}`,
);
}
// ── Orphaned milestone branch audit ──
@ -571,7 +572,7 @@ export async function bootstrapAutoSession(
// Non-fatal — the audit is defensive, never block bootstrap
logWarning(
"bootstrap",
`orphaned milestone branch audit failed: ${err instanceof Error ? err.message : String(err)}`,
`orphaned milestone branch audit failed: ${getErrorMessage(err)}`,
);
}
let state = await deriveState(base);
@ -953,7 +954,7 @@ export async function bootstrapAutoSession(
} catch (err) {
logWarning(
"bootstrap",
`Could not auto-checkout from stale milestone branch: ${err instanceof Error ? err.message : String(err)}`,
`Could not auto-checkout from stale milestone branch: ${getErrorMessage(err)}`,
);
}
}
@ -1190,7 +1191,7 @@ export async function bootstrapAutoSession(
}
} 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",
);
}
@ -1252,7 +1253,7 @@ export async function bootstrapAutoSession(
/* non-fatal */
logWarning(
"engine",
`preflight validation failed: ${err instanceof Error ? err.message : String(err)}`,
`preflight validation failed: ${getErrorMessage(err)}`,
);
}
return true;

View file

@ -38,6 +38,7 @@ import {
writeUnitRuntimeRecord,
} from "./uok/unit-runtime.js";
import { logError, logWarning } from "./workflow-logger.js";
import { getErrorMessage } from "./error-utils.js";
/**
* Set up all four supervision timers for the current unit:
* 1. Soft timeout warning (wrapup)
@ -104,7 +105,7 @@ export function startUnitSupervision(sctx) {
// Non-fatal — fall through with no estimate
logWarning(
"timer",
`operation failed: ${err instanceof Error ? err.message : String(err)}`,
`operation failed: ${getErrorMessage(err)}`,
);
}
}
@ -424,7 +425,7 @@ export function startUnitSupervision(sctx) {
);
await pauseAuto(ctx, pi);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
const message = getErrorMessage(err);
logError("timer", `[idle-watchdog] Unhandled error: ${message}`);
// Unblock any pending unit promise so the auto-loop is not orphaned.
resolveAgentEndCancelled({
@ -438,7 +439,7 @@ export function startUnitSupervision(sctx) {
/* best effort */
logWarning(
"timer",
`notification failed: ${err instanceof Error ? err.message : String(err)}`,
`notification failed: ${getErrorMessage(err)}`,
);
}
}
@ -485,7 +486,7 @@ export function startUnitSupervision(sctx) {
);
await pauseAuto(ctx, pi);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
const message = getErrorMessage(err);
logError("timer", `[hard-timeout] Unhandled error: ${message}`);
// Unblock any pending unit promise so the auto-loop is not orphaned.
resolveAgentEndCancelled({
@ -499,7 +500,7 @@ export function startUnitSupervision(sctx) {
/* best effort */
logWarning(
"timer",
`notification failed: ${err instanceof Error ? err.message : String(err)}`,
`notification failed: ${getErrorMessage(err)}`,
);
}
}

View file

@ -68,6 +68,7 @@ import {
resolveGitDir,
worktreePath,
} from "./worktree-manager.js";
import { getErrorMessage } from "./error-utils.js";
const PROJECT_PREFERENCES_FILE = "preferences.yaml";
// ─── Shared Constants & Helpers ─────────────────────────────────────────────
@ -152,7 +153,7 @@ function forceOverwriteAssessmentsWithVerdict(
/* non-fatal per file */
logWarning(
"worktree",
`assessment force-copy failed: ${err instanceof Error ? err.message : String(err)}`,
`assessment force-copy failed: ${getErrorMessage(err)}`,
);
}
}
@ -160,7 +161,7 @@ function forceOverwriteAssessmentsWithVerdict(
/* non-fatal per slice */
logWarning(
"worktree",
`assessment slice scan failed: ${err instanceof Error ? err.message : String(err)}`,
`assessment slice scan failed: ${getErrorMessage(err)}`,
);
}
}
@ -168,7 +169,7 @@ function forceOverwriteAssessmentsWithVerdict(
/* non-fatal */
logWarning(
"worktree",
`assessment sync failed: ${err instanceof Error ? err.message : String(err)}`,
`assessment sync failed: ${getErrorMessage(err)}`,
);
}
}
@ -190,7 +191,7 @@ function clearProjectRootStateFiles(basePath, milestoneId) {
if (err.code !== "ENOENT") {
logWarning(
"worktree",
`file unlink failed: ${err instanceof Error ? err.message : String(err)}`,
`file unlink failed: ${getErrorMessage(err)}`,
);
}
}
@ -228,7 +229,7 @@ function clearProjectRootStateFiles(basePath, milestoneId) {
if (code !== "ENOENT" && code !== "EISDIR") {
logWarning(
"worktree",
`untracked file unlink failed: ${err instanceof Error ? err.message : String(err)}`,
`untracked file unlink failed: ${getErrorMessage(err)}`,
);
}
}
@ -239,7 +240,7 @@ function clearProjectRootStateFiles(basePath, milestoneId) {
/* non-fatal — git command may fail if not in repo */
logWarning(
"worktree",
`untracked file cleanup failed: ${err instanceof Error ? err.message : String(err)}`,
`untracked file cleanup failed: ${getErrorMessage(err)}`,
);
}
}
@ -346,7 +347,7 @@ export function syncProjectRootToWorktree(
/* non-fatal */
logWarning(
"worktree",
`worktree DB cleanup failed: ${err instanceof Error ? err.message : String(err)}`,
`worktree DB cleanup failed: ${getErrorMessage(err)}`,
);
}
}
@ -496,7 +497,7 @@ export function cleanStaleRuntimeUnits(sfRootPath, hasMilestoneSummary) {
/* non-fatal */
logWarning(
"worktree",
`stale runtime unit unlink failed (${file}): ${err instanceof Error ? err.message : String(err)}`,
`stale runtime unit unlink failed (${file}): ${getErrorMessage(err)}`,
);
}
}
@ -505,7 +506,7 @@ export function cleanStaleRuntimeUnits(sfRootPath, hasMilestoneSummary) {
/* non-fatal */
logWarning(
"worktree",
`stale runtime unit cleanup failed: ${err instanceof Error ? err.message : String(err)}`,
`stale runtime unit cleanup failed: ${getErrorMessage(err)}`,
);
}
return cleaned;
@ -544,7 +545,7 @@ export function syncSfStateToWorktree(mainBasePath, worktreePath_) {
/* non-fatal */
logWarning(
"worktree",
`file copy failed (${f}): ${err instanceof Error ? err.message : String(err)}`,
`file copy failed (${f}): ${getErrorMessage(err)}`,
);
}
}
@ -565,7 +566,7 @@ export function syncSfStateToWorktree(mainBasePath, worktreePath_) {
/* non-fatal */
logWarning(
"worktree",
`preferences copy failed (${PROJECT_PREFERENCES_FILE}): ${err instanceof Error ? err.message : String(err)}`,
`preferences copy failed (${PROJECT_PREFERENCES_FILE}): ${getErrorMessage(err)}`,
);
}
}
@ -594,7 +595,7 @@ export function syncSfStateToWorktree(mainBasePath, worktreePath_) {
/* non-fatal */
logWarning(
"worktree",
`milestone copy failed (${mid}): ${err instanceof Error ? err.message : String(err)}`,
`milestone copy failed (${mid}): ${getErrorMessage(err)}`,
);
}
} else {
@ -618,7 +619,7 @@ export function syncSfStateToWorktree(mainBasePath, worktreePath_) {
/* non-fatal */
logWarning(
"worktree",
`milestone file copy failed (${mid}/${f}): ${err instanceof Error ? err.message : String(err)}`,
`milestone file copy failed (${mid}/${f}): ${getErrorMessage(err)}`,
);
}
}
@ -634,7 +635,7 @@ export function syncSfStateToWorktree(mainBasePath, worktreePath_) {
/* non-fatal */
logWarning(
"worktree",
`slices copy failed (${mid}): ${err instanceof Error ? err.message : String(err)}`,
`slices copy failed (${mid}): ${getErrorMessage(err)}`,
);
}
} else if (existsSync(srcSlicesDir) && existsSync(dstSlicesDir)) {
@ -655,7 +656,7 @@ export function syncSfStateToWorktree(mainBasePath, worktreePath_) {
/* non-fatal */
logWarning(
"worktree",
`slice copy failed (${mid}/${sid}): ${err instanceof Error ? err.message : String(err)}`,
`slice copy failed (${mid}/${sid}): ${getErrorMessage(err)}`,
);
}
}
@ -665,7 +666,7 @@ export function syncSfStateToWorktree(mainBasePath, worktreePath_) {
/* non-fatal */
logWarning(
"worktree",
`milestone file sync failed: ${err instanceof Error ? err.message : String(err)}`,
`milestone file sync failed: ${getErrorMessage(err)}`,
);
}
}
@ -674,7 +675,7 @@ export function syncSfStateToWorktree(mainBasePath, worktreePath_) {
/* non-fatal */
logWarning(
"worktree",
`milestone directory sync failed: ${err instanceof Error ? err.message : String(err)}`,
`milestone directory sync failed: ${getErrorMessage(err)}`,
);
}
}
@ -722,7 +723,7 @@ export function syncWorktreeStateBack(mainBasePath, worktreePath, milestoneId) {
// Non-fatal — file sync below is the fallback
logError(
"worktree",
`DB reconciliation failed: ${err instanceof Error ? err.message : String(err)}`,
`DB reconciliation failed: ${getErrorMessage(err)}`,
);
}
}
@ -743,7 +744,7 @@ export function syncWorktreeStateBack(mainBasePath, worktreePath, milestoneId) {
/* non-fatal */
logWarning(
"worktree",
`state file copy-back failed (${f}): ${err instanceof Error ? err.message : String(err)}`,
`state file copy-back failed (${f}): ${getErrorMessage(err)}`,
);
}
}
@ -768,7 +769,7 @@ export function syncWorktreeStateBack(mainBasePath, worktreePath, milestoneId) {
/* non-fatal */
logWarning(
"worktree",
`milestone sync-back failed: ${err instanceof Error ? err.message : String(err)}`,
`milestone sync-back failed: ${getErrorMessage(err)}`,
);
}
return { synced };
@ -791,7 +792,7 @@ function syncDirFiles(srcDir, dstDir, filter, synced, prefix) {
/* non-fatal */
logWarning(
"worktree",
`file copy failed (${prefix}${entry.name}): ${err instanceof Error ? err.message : String(err)}`,
`file copy failed (${prefix}${entry.name}): ${getErrorMessage(err)}`,
);
}
}
@ -799,7 +800,7 @@ function syncDirFiles(srcDir, dstDir, filter, synced, prefix) {
/* non-fatal — srcDir may not be readable */
logWarning(
"worktree",
`directory read failed: ${err instanceof Error ? err.message : String(err)}`,
`directory read failed: ${getErrorMessage(err)}`,
);
}
}
@ -854,7 +855,7 @@ function syncMilestoneDir(wtSf, mainSf, mid, synced) {
/* non-fatal */
logWarning(
"worktree",
`milestone slice sync failed (${mid}): ${err instanceof Error ? err.message : String(err)}`,
`milestone slice sync failed (${mid}): ${getErrorMessage(err)}`,
);
}
}
@ -893,7 +894,7 @@ export function runWorktreePostCreateHook(sourceDir, worktreeDir, hookPath) {
/* keep original */
logWarning(
"worktree",
`realpath failed: ${err instanceof Error ? err.message : String(err)}`,
`realpath failed: ${getErrorMessage(err)}`,
);
}
}
@ -915,7 +916,7 @@ export function runWorktreePostCreateHook(sourceDir, worktreeDir, hookPath) {
shell: needsShell,
});
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
errors.push(`Worktree post-create hook failed: ${msg}`);
}
}
@ -939,7 +940,7 @@ export function runWorktreePostCreateHook(sourceDir, worktreeDir, hookPath) {
timeout: 60_000,
});
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
errors.push(`workspace.after_create hook failed: ${msg}`);
}
}
@ -999,7 +1000,7 @@ function reconcilePlanCheckboxes(projectRoot, wtPath, milestoneId) {
/* non-fatal */
logWarning(
"worktree",
`walkMd directory read failed: ${err instanceof Error ? err.message : String(err)}`,
`walkMd directory read failed: ${getErrorMessage(err)}`,
);
}
return results;
@ -1051,7 +1052,7 @@ function reconcilePlanCheckboxes(projectRoot, wtPath, milestoneId) {
/* non-fatal */
logWarning(
"worktree",
`plan checkbox reconcile write failed: ${err instanceof Error ? err.message : String(err)}`,
`plan checkbox reconcile write failed: ${getErrorMessage(err)}`,
);
}
}
@ -1148,7 +1149,7 @@ export function createAutoWorktree(basePath, milestoneId) {
// Don't store originalBase -- caller can retry or clean up.
throw new SFError(
SF_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)}`,
);
}
nudgeGitBranchCache(previousCwd);
@ -1212,7 +1213,7 @@ export function teardownAutoWorktree(originalBasePath, milestoneId, opts = {}) {
} catch (err) {
throw new SFError(
SF_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)}`,
);
}
nudgeGitBranchCache(previousCwd);
@ -1241,7 +1242,7 @@ export function teardownAutoWorktree(originalBasePath, milestoneId, opts = {}) {
// Non-fatal — the warning above tells the user how to clean up
logWarning(
"worktree",
`worktree directory removal failed: ${err instanceof Error ? err.message : String(err)}`,
`worktree directory removal failed: ${getErrorMessage(err)}`,
);
}
} else {
@ -1335,7 +1336,7 @@ export function enterAutoWorktree(basePath, milestoneId) {
} catch (err) {
throw new SFError(
SF_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)}`,
);
}
nudgeGitBranchCache(previousCwd);
@ -1460,7 +1461,7 @@ export function mergeMilestoneToMain(
/* non-fatal */
logError(
"worktree",
`DB reconciliation failed: ${err instanceof Error ? err.message : String(err)}`,
`DB reconciliation failed: ${getErrorMessage(err)}`,
);
}
}
@ -1632,7 +1633,7 @@ export function mergeMilestoneToMain(
// report the dirty tree if it fails.
logWarning(
"worktree",
`git stash failed: ${err instanceof Error ? err.message : String(err)}`,
`git stash failed: ${getErrorMessage(err)}`,
);
}
// 7a. Shelter queued milestone directories before the squash merge (#2505).
@ -1659,7 +1660,7 @@ export function mergeMilestoneToMain(
/* best-effort */
logError(
"worktree",
`shelter restore failed: ${err instanceof Error ? err.message : String(err)}`,
`shelter restore failed: ${getErrorMessage(err)}`,
);
}
}
@ -1669,7 +1670,7 @@ export function mergeMilestoneToMain(
/* best-effort */
logWarning(
"worktree",
`shelter cleanup failed: ${err instanceof Error ? err.message : String(err)}`,
`shelter cleanup failed: ${getErrorMessage(err)}`,
);
}
};
@ -1691,7 +1692,7 @@ export function mergeMilestoneToMain(
// Non-fatal — if shelter fails, the merge may still succeed
logWarning(
"worktree",
`milestone shelter failed (${entry.name}): ${err instanceof Error ? err.message : String(err)}`,
`milestone shelter failed (${entry.name}): ${getErrorMessage(err)}`,
);
}
}
@ -1700,7 +1701,7 @@ export function mergeMilestoneToMain(
// Non-fatal — proceed with merge; untracked files may block it
logWarning(
"worktree",
`milestone shelter operation failed: ${err instanceof Error ? err.message : String(err)}`,
`milestone shelter operation failed: ${getErrorMessage(err)}`,
);
}
// 7b. Clean up stale merge state before attempting squash merge (#2912).
@ -1718,7 +1719,7 @@ export function mergeMilestoneToMain(
/* best-effort */
logError(
"worktree",
`merge state cleanup failed: ${err instanceof Error ? err.message : String(err)}`,
`merge state cleanup failed: ${getErrorMessage(err)}`,
);
}
// 8. Squash merge — auto-resolve .sf/ state file conflicts (#530)
@ -1740,7 +1741,7 @@ export function mergeMilestoneToMain(
/* best-effort */
logError(
"worktree",
`merge state cleanup failed: ${err instanceof Error ? err.message : String(err)}`,
`merge state cleanup failed: ${getErrorMessage(err)}`,
);
}
// Pop stash before throwing so local work is not lost.
@ -1755,7 +1756,7 @@ export function mergeMilestoneToMain(
/* stash pop conflict is non-fatal */
logWarning(
"worktree",
`git stash pop failed: ${err instanceof Error ? err.message : String(err)}`,
`git stash pop failed: ${getErrorMessage(err)}`,
);
}
}
@ -1815,7 +1816,7 @@ export function mergeMilestoneToMain(
/* best-effort */
logError(
"worktree",
`git merge-abort failed: ${err instanceof Error ? err.message : String(err)}`,
`git merge-abort failed: ${getErrorMessage(err)}`,
);
}
try {
@ -1828,7 +1829,7 @@ export function mergeMilestoneToMain(
/* best-effort */
logError(
"worktree",
`merge state file cleanup failed: ${err instanceof Error ? err.message : String(err)}`,
`merge state file cleanup failed: ${getErrorMessage(err)}`,
);
}
// Pop stash before throwing so local work is not lost (#2151).
@ -1843,7 +1844,7 @@ export function mergeMilestoneToMain(
/* stash pop conflict is non-fatal */
logWarning(
"worktree",
`git stash pop failed: ${err instanceof Error ? err.message : String(err)}`,
`git stash pop failed: ${getErrorMessage(err)}`,
);
}
}
@ -1883,7 +1884,7 @@ export function mergeMilestoneToMain(
/* best-effort */
logError(
"worktree",
`post-commit merge state cleanup failed: ${err instanceof Error ? err.message : String(err)}`,
`post-commit merge state cleanup failed: ${getErrorMessage(err)}`,
);
}
// 9a-ii. Restore stashed files now that the merge+commit is complete (#2151).
@ -1942,7 +1943,7 @@ export function mergeMilestoneToMain(
/* stash may already be consumed */
logWarning(
"worktree",
`git stash drop failed: ${err instanceof Error ? err.message : String(err)}`,
`git stash drop failed: ${getErrorMessage(err)}`,
);
}
} else {
@ -2022,7 +2023,7 @@ export function mergeMilestoneToMain(
// Push failure is non-fatal
logWarning(
"worktree",
`git push failed: ${err instanceof Error ? err.message : String(err)}`,
`git push failed: ${getErrorMessage(err)}`,
);
}
}
@ -2065,7 +2066,7 @@ export function mergeMilestoneToMain(
// PR creation failure is non-fatal — gh may not be installed or authenticated
logWarning(
"worktree",
`PR creation failed: ${err instanceof Error ? err.message : String(err)}`,
`PR creation failed: ${getErrorMessage(err)}`,
);
}
}
@ -2125,7 +2126,7 @@ export function mergeMilestoneToMain(
// Best-effort -- worktree dir may already be gone
logWarning(
"worktree",
`worktree removal failed: ${err instanceof Error ? err.message : String(err)}`,
`worktree removal failed: ${getErrorMessage(err)}`,
);
}
// 13. Delete milestone branch (after worktree removal so ref is unlocked)
@ -2135,7 +2136,7 @@ export function mergeMilestoneToMain(
// Best-effort
logWarning(
"worktree",
`git branch-delete failed: ${err instanceof Error ? err.message : String(err)}`,
`git branch-delete failed: ${getErrorMessage(err)}`,
);
}
// 14. Clear module state

View file

@ -418,7 +418,7 @@ export function getAutoDashboardData() {
// Non-fatal — captures module may not be loaded
logWarning(
"engine",
`capture count failed: ${err instanceof Error ? err.message : String(err)}`,
`capture count failed: ${getErrorMessage(err)}`,
{ file: "auto.ts" },
);
}
@ -748,7 +748,7 @@ function cleanupAfterLoopExit(ctx) {
/* best-effort — mirror stopAuto cleanup */
logWarning(
"session",
`lock cleanup failed: ${err instanceof Error ? err.message : String(err)}`,
`lock cleanup failed: ${getErrorMessage(err)}`,
{ file: "auto.ts" },
);
}
@ -769,7 +769,7 @@ function cleanupAfterLoopExit(ctx) {
/* best-effort */
logWarning(
"engine",
`chdir failed: ${err instanceof Error ? err.message : String(err)}`,
`chdir failed: ${getErrorMessage(err)}`,
{ file: "auto.ts" },
);
}
@ -869,7 +869,7 @@ export async function stopAuto(ctx, pi, reason) {
// Non-fatal — fall through to preserveBranch path
logWarning(
"engine",
`milestone summary check failed: ${err instanceof Error ? err.message : String(err)}`,
`milestone summary check failed: ${getErrorMessage(err)}`,
{ file: "auto.ts" },
);
}
@ -923,7 +923,7 @@ export async function stopAuto(ctx, pi, reason) {
/* best-effort */
logWarning(
"engine",
`chdir failed: ${err instanceof Error ? err.message : String(err)}`,
`chdir failed: ${getErrorMessage(err)}`,
{ file: "auto.ts" },
);
}
@ -1051,7 +1051,7 @@ export async function stopAuto(ctx, pi, reason) {
/* non-fatal */
logWarning(
"engine",
`file unlink failed: ${err instanceof Error ? err.message : String(err)}`,
`file unlink failed: ${getErrorMessage(err)}`,
{ file: "auto.ts" },
);
}
@ -1094,7 +1094,7 @@ export async function stopAuto(ctx, pi, reason) {
/* non-fatal: browser-tools may not be loaded */
logWarning(
"engine",
`browser teardown failed: ${err instanceof Error ? err.message : String(err)}`,
`browser teardown failed: ${getErrorMessage(err)}`,
{ file: "auto.ts" },
);
}
@ -1151,7 +1151,7 @@ export async function stopAuto(ctx, pi, reason) {
} catch (err) {
logWarning(
"engine",
`auto-exit telemetry failed: ${err instanceof Error ? err.message : String(err)}`,
`auto-exit telemetry failed: ${getErrorMessage(err)}`,
);
}
// Drop the active-tool baseline so a subsequent /autonomous run on the
@ -1222,7 +1222,7 @@ export async function pauseAuto(ctx, _pi, _errorContext) {
// Non-fatal — resume will still work via full bootstrap, just without worktree context
logWarning(
"engine",
`paused-session file write failed: ${err instanceof Error ? err.message : String(err)}`,
`paused-session file write failed: ${getErrorMessage(err)}`,
{ file: "auto.ts" },
);
}
@ -1240,7 +1240,7 @@ export async function pauseAuto(ctx, _pi, _errorContext) {
// Non-fatal — best-effort closeout on pause
logWarning(
"engine",
`unit closeout on pause failed: ${err instanceof Error ? err.message : String(err)}`,
`unit closeout on pause failed: ${getErrorMessage(err)}`,
{ file: "auto.ts" },
);
}
@ -1593,7 +1593,7 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
if (err.code !== "ENOENT") {
logWarning(
"session",
`pause file cleanup failed: ${err instanceof Error ? err.message : String(err)}`,
`pause file cleanup failed: ${getErrorMessage(err)}`,
{ file: "auto.ts" },
);
}
@ -1665,7 +1665,7 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
// Malformed or missing — proceed with fresh bootstrap
logWarning(
"session",
`paused-session restore failed: ${err instanceof Error ? err.message : String(err)}`,
`paused-session restore failed: ${getErrorMessage(err)}`,
{ file: "auto.ts" },
);
}
@ -1877,7 +1877,7 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
// Best-effort only — sidebar sync must never block autonomous mode startup
logWarning(
"engine",
`cmux sync failed: ${err instanceof Error ? err.message : String(err)}`,
`cmux sync failed: ${getErrorMessage(err)}`,
{ file: "auto.ts" },
);
}
@ -2038,7 +2038,7 @@ export async function dispatchHookUnit(
/* non-fatal */
logWarning(
"dispatch",
`hook model set failed: ${err instanceof Error ? err.message : String(err)}`,
`hook model set failed: ${getErrorMessage(err)}`,
{ file: "auto.ts" },
);
}
@ -2075,7 +2075,7 @@ export async function dispatchHookUnit(
} catch (err) {
logWarning(
"engine",
`chdir failed before hook dispatch: ${err instanceof Error ? err.message : String(err)}`,
`chdir failed before hook dispatch: ${getErrorMessage(err)}`,
{ file: "auto.ts" },
);
}

View file

@ -10,6 +10,7 @@
* retrying. Each retry re-dispatches the unit at full LLM cost, so we bail
* immediately rather than burning budget on guaranteed failures.
*/
import { getErrorMessage } from "../error-utils.js";
export const INFRA_ERROR_CODES = new Set([
"ENOSPC", // disk full
"ENOMEM", // out of memory
@ -35,7 +36,7 @@ export function isInfrastructureError(err) {
const code = err.code;
if (typeof code === "string" && INFRA_ERROR_CODES.has(code)) return code;
}
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
for (const code of INFRA_ERROR_CODES) {
if (msg.includes(code)) return code;
}
@ -65,7 +66,7 @@ export function isTransientCooldownError(err) {
return true;
}
// Fallback: message match for cross-process error propagation
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
return /in a cooldown window/i.test(msg);
}
/**

View file

@ -40,6 +40,7 @@ import {
} from "./phases.js";
import { _clearCurrentResolve } from "./resolve.js";
import { MAX_LOOP_ITERATIONS } from "./types.js";
import { getErrorMessage } from "../error-utils.js";
// ── Stuck detection persistence (#3704) ──────────────────────────────────
// Persist stuck detection state to disk so it survives session restarts.
@ -81,7 +82,7 @@ function loadStuckState(basePath) {
} catch (err) {
debugLog("autoLoop", {
phase: "load-stuck-state-failed",
error: err instanceof Error ? err.message : String(err),
error: getErrorMessage(err),
});
return { recentUnits: [], stuckRecoveryAttempts: 0 };
}
@ -102,7 +103,7 @@ function saveStuckState(basePath, state) {
} catch (err) {
debugLog("autoLoop", {
phase: "save-stuck-state-failed",
error: err instanceof Error ? err.message : String(err),
error: getErrorMessage(err),
});
}
}
@ -142,7 +143,7 @@ function hydrateCustomVerifyRetryCounts(s) {
} catch (err) {
debugLog("autoLoop", {
phase: "load-custom-verify-retries-failed",
error: err instanceof Error ? err.message : String(err),
error: getErrorMessage(err),
});
}
return s.verificationRetryCount;
@ -169,7 +170,7 @@ function saveCustomVerifyRetryCounts(s) {
if (code !== "ENOENT") {
debugLog("autoLoop", {
phase: "save-custom-verify-retries-failed",
error: err instanceof Error ? err.message : String(err),
error: getErrorMessage(err),
});
}
}
@ -417,7 +418,7 @@ async function runExitSolverEval(ctx, s, deps, iteration) {
error: result.error,
});
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
const message = getErrorMessage(err);
ctx.ui.notify(`Autonomous solver eval hook failed: ${message}`, "warning", {
noticeKind: NOTICE_KIND.TOOL_NOTICE,
dedupe_key: `solver-eval-hook-fail:${message.slice(0, 120)}`,
@ -587,7 +588,7 @@ export async function autoLoop(ctx, pi, s, deps) {
} catch (err) {
logWarning(
"dispatch",
`sidecar queue scheduling failed: ${err instanceof Error ? err.message : String(err)}`,
`sidecar queue scheduling failed: ${getErrorMessage(err)}`,
);
}
}

View file

@ -121,6 +121,7 @@ import {
withTimeout,
} from "./finalize-timeout.js";
import { runUnit } from "./run-unit.js";
import { getErrorMessage } from "../error-utils.js";
import {
BUDGET_THRESHOLDS,
MAX_FINALIZE_TIMEOUTS,
@ -657,7 +658,7 @@ export async function runPreDispatch(ic, loopState) {
} catch (err) {
debugLog("autoLoop", {
phase: "slice-parallel-check-error",
error: err instanceof Error ? err.message : String(err),
error: getErrorMessage(err),
});
// Non-fatal — fall through to sequential dispatch
}
@ -696,7 +697,7 @@ export async function runPreDispatch(ic, loopState) {
await generateMilestoneReport(s, ctx, s.currentMilestoneId);
} catch (err) {
ctx.ui.notify(
`Report generation failed: ${err instanceof Error ? err.message : String(err)}`,
`Report generation failed: ${getErrorMessage(err)}`,
"warning",
);
}
@ -1132,7 +1133,7 @@ export async function runDispatch(ic, preData, loopState) {
}
} catch (err) {
logWarning("engine", "UOK diagnostics dispatch gate failed open", {
error: err instanceof Error ? err.message : String(err),
error: getErrorMessage(err),
});
}
deps.emitJournalEvent({
@ -1172,7 +1173,7 @@ export async function runDispatch(ic, preData, loopState) {
}
} catch (err) {
logWarning("engine", "Reasoning assist failed open", {
error: err instanceof Error ? err.message : String(err),
error: getErrorMessage(err),
unitType,
unitId,
});
@ -1720,7 +1721,7 @@ export async function runGuards(ic, mid, unitType, unitId, sliceId) {
}
} 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",
);
}
@ -2662,7 +2663,7 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
? () => {
void resumeAutoAfterProviderDelay(pi, ctx).catch((err) => {
const message =
err instanceof Error ? err.message : String(err);
getErrorMessage(err);
ctx.ui.notify(
`Session timeout recovery failed: ${message}`,
"error",
@ -2856,7 +2857,7 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
/* non-fatal — anchor is advisory */
logWarning(
"engine",
`phase anchor failed: ${err instanceof Error ? err.message : String(err)}`,
`phase anchor failed: ${getErrorMessage(err)}`,
);
}
}

View file

@ -20,6 +20,7 @@ import { dirname, isAbsolute, join, relative, resolve } from "node:path";
import { atomicWriteSync } from "./atomic-write.js";
import { ensureDbOpen } from "./bootstrap/dynamic-tools.js";
import { sfRoot } from "./paths.js";
import { getErrorMessage } from "./error-utils.js";
const DEFAULT_TIMEOUT_MS = 5 * 60_000;
const MAX_OUTPUT_CHARS = 20_000;
@ -46,7 +47,7 @@ function parseJsonLine(line, index, filePath) {
return JSON.parse(line);
} catch (err) {
throw new Error(
`${filePath}:${index + 1}: invalid JSON: ${err instanceof Error ? err.message : String(err)}`,
`${filePath}:${index + 1}: invalid JSON: ${getErrorMessage(err)}`,
);
}
}
@ -480,7 +481,7 @@ export async function runAutomaticAutonomousSolverEval(options) {
});
return { ok: true, report };
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
const message = getErrorMessage(err);
emitJournalEvent({
ts: new Date().toISOString(),
eventType: "solver-eval-auto-failed",
@ -581,7 +582,7 @@ async function recordEvalRunBestEffort(basePath, report) {
} catch (err) {
return {
ok: false,
error: err instanceof Error ? err.message : String(err),
error: getErrorMessage(err),
};
}
}
@ -662,7 +663,7 @@ export async function handleAutonomousSolverEval(
args = parseAutonomousSolverEvalArgs(rawArgs);
} catch (err) {
ctx.ui.notify(
`Usage: /solver-eval [run|history|show <run-id>] [--sample | --cases <jsonl>] [--run-id <id>] [--limit <n>]\n${err instanceof Error ? err.message : String(err)}`,
`Usage: /solver-eval [run|history|show <run-id>] [--sample | --cases <jsonl>] [--run-id <id>] [--limit <n>]\n${getErrorMessage(err)}`,
"warning",
);
return;

View file

@ -13,6 +13,7 @@ import {
import { pauseAutoForProviderError } from "../provider-error-pause.js";
import { logWarning } from "../workflow-logger.js";
import { clearDiscussionFlowState } from "./write-gate.js";
import { getErrorMessage } from "../error-utils.js";
const retryState = createRetryState();
const GEMINI_CAPACITY_COOLDOWN_MS = 2 * 60_000;
@ -145,7 +146,7 @@ export async function handleAgentEnd(pi, event, ctx) {
resetRetryState(retryState);
resolveAgentEnd(event);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
const message = getErrorMessage(err);
ctx.ui.notify(
`Autonomous mode error after empty-content abort: ${message}. Stopping autonomous mode.`,
"error",
@ -228,7 +229,7 @@ export async function handleAgentEnd(pi, event, ctx) {
"warning",
);
} catch (err) {
const m = err instanceof Error ? err.message : String(err);
const m = getErrorMessage(err);
logWarning("bootstrap", `Failed to persist blocked model: ${m}`);
}
}
@ -280,7 +281,7 @@ export async function handleAgentEnd(pi, event, ctx) {
"warning",
);
} catch (err) {
const m = err instanceof Error ? err.message : String(err);
const m = getErrorMessage(err);
logWarning("bootstrap", `Failed to persist model cooldown: ${m}`);
}
}
@ -343,7 +344,7 @@ export async function handleAgentEnd(pi, event, ctx) {
resetRetryState(retryState);
resolveAgentEnd(event);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
const message = getErrorMessage(err);
ctx.ui.notify(
`Autonomous mode error in agent_end handler: ${message}. Stopping autonomous mode.`,
"error",

View file

@ -28,6 +28,7 @@ import {
} from "../tools/workflow-tool-executors.js";
import { logError } from "../workflow-logger.js";
import { ensureDbOpen } from "./dynamic-tools.js";
import { getErrorMessage } from "../error-utils.js";
export function registerDbTools(pi) {
// ─── save_decision ─────────────────────────────────────────────────
const decisionSaveExecute = async (
@ -68,7 +69,7 @@ export function registerDbTools(pi) {
details: { operation: "save_decision", id },
};
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
logError("tool", `save_decision tool failed: ${msg}`, {
tool: "save_decision",
error: String(err),
@ -190,7 +191,7 @@ export function registerDbTools(pi) {
details: { operation: "update_requirement", id: params.id },
};
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
logError("tool", `update_requirement tool failed: ${msg}`, {
tool: "update_requirement",
error: String(err),
@ -307,7 +308,7 @@ export function registerDbTools(pi) {
details: { operation: "save_requirement", id: result.id },
};
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
logError("tool", `save_requirement tool failed: ${msg}`, {
tool: "save_requirement",
error: String(err),
@ -484,7 +485,7 @@ export function registerDbTools(pi) {
},
};
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
return {
content: [
{
@ -613,7 +614,7 @@ export function registerDbTools(pi) {
},
};
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
logError("tool", `report_issue tool failed: ${msg}`, {
tool: "report_issue",
error: String(err),
@ -769,7 +770,7 @@ export function registerDbTools(pi) {
details: { operation: "save_knowledge", id },
};
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
logError("tool", `save_knowledge tool failed: ${msg}`, {
tool: "save_knowledge",
error: String(err),
@ -900,7 +901,7 @@ export function registerDbTools(pi) {
},
};
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
logError("tool", `resolve_issue tool failed: ${msg}`, {
tool: "resolve_issue",
error: String(err),
@ -1010,7 +1011,7 @@ export function registerDbTools(pi) {
},
};
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
logError("tool", `checkpoint tool failed: ${msg}`, {
tool: "checkpoint",
error: String(err),
@ -1745,7 +1746,7 @@ export function registerDbTools(pi) {
},
};
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
logError("tool", `plan_task tool failed: ${msg}`, {
tool: "plan_task",
error: String(err),
@ -2183,7 +2184,7 @@ export function registerDbTools(pi) {
},
};
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
logError("tool", `skip_slice tool failed: ${msg}`, {
tool: "skip_slice",
error: String(err),
@ -2758,7 +2759,7 @@ export function registerDbTools(pi) {
details: { operation: "chapter_open", id },
};
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
return {
content: [{ type: "text", text: `Error opening chapter: ${msg}` }],
details: { operation: "chapter_open", error: msg },
@ -2839,7 +2840,7 @@ export function registerDbTools(pi) {
details: { operation: "chapter_close", id: params.id, closed },
};
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
return {
content: [{ type: "text", text: `Error closing chapter: ${msg}` }],
details: { operation: "chapter_close", error: msg },

View file

@ -17,6 +17,7 @@ import { executeExecSearch } from "../tools/exec-search-tool.js";
import { executeSfExec } from "../tools/exec-tool.js";
import { executeResume } from "../tools/resume-tool.js";
import { logWarning } from "../workflow-logger.js";
import { getErrorMessage } from "../error-utils.js";
export function registerExecTools(pi) {
pi.registerTool({
name: "run_command",
@ -68,7 +69,7 @@ export function registerExecTools(pi) {
} catch (err) {
logWarning(
"tool",
`run_command could not load preferences: ${err instanceof Error ? err.message : String(err)}`,
`run_command could not load preferences: ${getErrorMessage(err)}`,
);
}
onUpdate?.({

View file

@ -1,6 +1,7 @@
import { Type } from "@sinclair/typebox";
import { queryJournal } from "../journal.js";
import { logWarning } from "../workflow-logger.js";
import { getErrorMessage } from "../error-utils.js";
export function registerJournalTools(pi) {
pi.registerTool({
name: "query_journal",
@ -77,7 +78,7 @@ export function registerJournalTools(pi) {
details: { operation: "journal_query", count: limited.length },
};
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
logWarning("tool", `query_journal tool failed: ${msg}`);
return {
content: [{ type: "text", text: `Error querying journal: ${msg}` }],

View file

@ -16,6 +16,7 @@ import { registerQueryTools } from "./query-tools.js";
import { registerHooks } from "./register-hooks.js";
import { registerShortcuts } from "./register-shortcuts.js";
import { registerSessionTodoTool } from "./session-todo-tools.js";
import { getErrorMessage } from "../error-utils.js";
export { writeCrashLog } from "./crash-log.js";
export function handleRecoverableExtensionProcessError(err) {
@ -101,7 +102,7 @@ export function registerSfExtension(pi) {
void loadEcosystemExtensions(pi, ecosystemHandlers).catch((err) => {
logWarning(
"bootstrap",
`Failed to load ecosystem extensions: ${err instanceof Error ? err.message : String(err)}`,
`Failed to load ecosystem extensions: ${getErrorMessage(err)}`,
);
});
},
@ -113,7 +114,7 @@ export function registerSfExtension(pi) {
} catch (err) {
logWarning(
"bootstrap",
`Failed to register ${name}: ${err instanceof Error ? err.message : String(err)}`,
`Failed to register ${name}: ${getErrorMessage(err)}`,
);
}
}

View file

@ -108,6 +108,7 @@ import {
shouldBlockPendingGateBash,
shouldBlockQueueExecution,
} from "./write-gate.js";
import { getErrorMessage } from "../error-utils.js";
// Skip the welcome screen on the very first session_start — cli.ts already
// printed it before the TUI launched. Only re-print on /clear (subsequent sessions).
@ -293,7 +294,7 @@ export function registerHooks(pi, ecosystemHandlers = []) {
const { logWarning } = await import("../workflow-logger.js");
logWarning(
"session-start",
`Failed to initialize metrics-central: ${err instanceof Error ? err.message : String(err)}`,
`Failed to initialize metrics-central: ${getErrorMessage(err)}`,
);
}
// Apply show_token_cost preference (#1515)

View file

@ -14,6 +14,7 @@ import {
openDatabase,
readTransaction,
} from "./sf-db.js";
import { getErrorMessage } from "./error-utils.js";
function milestoneDir(basePath, milestoneId) {
return join(basePath, ".sf", "milestones", milestoneId);
@ -246,7 +247,7 @@ export function getCanonicalMilestonePlan(basePath, milestoneId, options = {}) {
return blockedResult(
"db-unavailable",
milestoneId,
`.sf/sf.db exists but could not be read: ${err instanceof Error ? err.message : String(err)}. Runtime does not fall back to projections when DB is expected; run sf recover.`,
`.sf/sf.db exists but could not be read: ${getErrorMessage(err)}. Runtime does not fall back to projections when DB is expected; run sf recover.`,
paths,
);
}
@ -273,7 +274,7 @@ export function getCanonicalMilestonePlan(basePath, milestoneId, options = {}) {
return blockedResult(
"projection-invalid",
milestoneId,
err instanceof Error ? err.message : String(err),
getErrorMessage(err),
paths,
);
}

View file

@ -8,6 +8,7 @@
* Entry point: handleChangelog() called from commands.ts
*/
// ─── Semver comparison ────────────────────────────────────────────────────────
import { getErrorMessage } from "./error-utils.js";
function compareSemver(a, b) {
const pa = a.split(".").map(Number);
const pb = b.split(".").map(Number);
@ -89,7 +90,7 @@ export async function handleChangelog(args, ctx, pi) {
}
releases = await response.json();
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
const message = getErrorMessage(err);
ctx.ui.notify(`Failed to fetch changelog: ${message}`, "error");
return;
}

View file

@ -16,6 +16,7 @@ import { execFileSync } from "node:child_process";
import { GIT_NO_PROMPT_ENV } from "./git-constants.js";
import { nativeHasChanges } from "./native-git-bridge.js";
import { logWarning } from "./workflow-logger.js";
import { getErrorMessage } from "./error-utils.js";
/**
* Check the working tree for dirty files before a milestone merge.
*
@ -37,7 +38,7 @@ export function preflightCleanRoot(basePath, milestoneId, notify) {
// If the status check itself fails, treat as clean and let the merge decide
logWarning(
"preflight",
`clean-root status check failed: ${err instanceof Error ? err.message : String(err)}`,
`clean-root status check failed: ${getErrorMessage(err)}`,
);
return { stashPushed: false, summary: "" };
}
@ -65,7 +66,7 @@ export function preflightCleanRoot(basePath, milestoneId, notify) {
};
} catch (err) {
// Stash failure is non-fatal — log and let the merge attempt proceed
const msg = `git stash push failed before merge of milestone ${milestoneId}: ${err instanceof Error ? err.message : String(err)}`;
const msg = `git stash push failed before merge of milestone ${milestoneId}: ${getErrorMessage(err)}`;
logWarning("preflight", msg);
notify(
`Auto-stash failed before milestone ${milestoneId} merge — proceeding anyway. ${msg}`,
@ -96,7 +97,7 @@ export function postflightPopStash(basePath, milestoneId, notify) {
} catch (err) {
// Pop conflicts mean the merged code collides with the stashed changes.
// Log a warning — the user needs to resolve manually, but the merge succeeded.
const msg = `git stash pop failed after merge of milestone ${milestoneId}: ${err instanceof Error ? err.message : String(err)}. Run "git stash pop" manually to restore your changes.`;
const msg = `git stash pop failed after merge of milestone ${milestoneId}: ${getErrorMessage(err)}. Run "git stash pop" manually to restore your changes.`;
logWarning("preflight", msg);
notify(msg, "warning");
}

View file

@ -14,6 +14,7 @@ import {
writeFileSync,
} from "node:fs";
import { delimiter, isAbsolute, join, relative, resolve } from "node:path";
import { getErrorMessage } from "./error-utils.js";
const SIFT_BINARY_NAME = process.platform === "win32" ? "sift.exe" : "sift";
const DEFAULT_SIFT_WARMUP_TTL_MS = 6 * 60 * 60 * 1000;
@ -621,7 +622,7 @@ export function ensureSiftIndexWarmup(projectRoot, prefs, options = {}) {
} catch (err) {
return {
status: "error",
reason: err instanceof Error ? err.message : String(err),
reason: getErrorMessage(err),
command,
args,
markerPath,

View file

@ -9,6 +9,7 @@ import { join } from "node:path";
import { resolveSliceFile, sfRoot } from "./paths.js";
import { loadPrompt } from "./prompt-loader.js";
import { deriveState } from "./state.js";
import { getErrorMessage } from "./error-utils.js";
function findLastCompletedSlice(basePath, milestoneId) {
// Scan disk for slices that have a SUMMARY.md (indicating completion)
@ -126,7 +127,7 @@ export async function handleAddTests(args, ctx, pi) {
{ triggerTurn: true },
);
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
ctx.ui.notify(`Failed to dispatch test generation: ${msg}`, "error");
}
}

View file

@ -6,6 +6,7 @@ import {
updateDebugSession,
} from "./debug-session-store.js";
import { loadPrompt } from "./prompt-loader.js";
import { getErrorMessage } from "./error-utils.js";
const SUBCOMMANDS = new Set(["list", "status", "continue", "--diagnose"]);
function isValidSlugCandidate(input) {
@ -127,7 +128,7 @@ export async function handleDebug(args, ctx, pi) {
{ triggerTurn: true },
);
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
ctx.ui.notify(
`Debug dispatch failed: ${msg}\nSession '${s.slug}' is persisted; retry with /debug continue ${s.slug}`,
"warning",
@ -364,7 +365,7 @@ export async function handleDebug(args, ctx, pi) {
{ triggerTurn: true },
);
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
ctx.ui.notify(
`Continue dispatch failed: ${msg}\nSession '${resumed.session.slug}' is persisted; retry with /debug continue ${resumed.session.slug}`,
"warning",
@ -418,7 +419,7 @@ export async function handleDebug(args, ctx, pi) {
{ triggerTurn: true },
);
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
ctx.ui.notify(
`Diagnose dispatch failed: ${msg}\nSession '${s.slug}' is persisted; continue manually with /debug continue ${s.slug}`,
"warning",

View file

@ -44,6 +44,7 @@ import {
resolveSlicePath,
} from "./paths.js";
import { deriveState } from "./state.js";
import { getErrorMessage } from "./error-utils.js";
// ─── Constants ────────────────────────────────────────────────────────────────
/**
* Slice-ID format. Must match the canonical `/^S\d+$/` used elsewhere in the
@ -228,7 +229,7 @@ export async function buildEvalReviewContext(
specTruncated = true;
}
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
spec = bestFitMarker(
remaining,
`[truncated: failed to read AI-SPEC.md (${msg})]`,
@ -553,7 +554,7 @@ export async function handleEvalReview(args, ctx, pi) {
"info",
);
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
ctx.ui.notify(`Failed to read ${action.path}: ${msg}`, "error");
}
return;
@ -581,7 +582,7 @@ export async function handleEvalReview(args, ctx, pi) {
try {
context = await buildEvalReviewContext(detected, milestoneId);
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
ctx.ui.notify(`Failed to build eval-review context: ${msg}`, "error");
return;
}

View file

@ -30,6 +30,7 @@ import { sfRoot } from "./paths.js";
import { sfHome } from "./sf-home.js";
import { loadPrompt } from "./prompt-loader.js";
import { deriveState } from "./state.js";
import { getErrorMessage } from "./error-utils.js";
const UPDATE_REGISTRY_URL =
"https://registry.npmjs.org/singularity-forge/latest";
@ -353,7 +354,7 @@ export async function handleTriage(args, ctx, pi, basePath) {
);
} catch (err) {
ctx.ui.notify(
`TODO triage failed: ${err instanceof Error ? err.message : String(err)}`,
`TODO triage failed: ${getErrorMessage(err)}`,
"warning",
);
}

View file

@ -13,6 +13,7 @@ import {
} from "./native-git-bridge.js";
import { deriveState } from "./state.js";
import { logWarning } from "./workflow-logger.js";
import { getErrorMessage } from "./error-utils.js";
/**
* Clean up merged and stale milestone branches.
*/
@ -625,7 +626,7 @@ export async function handleRecover(ctx, basePath) {
);
ctx.ui.notify(lines.join("\n"), "success");
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
logWarning("command", `recover failed: ${msg}`);
ctx.ui.notify(`sf recover failed: ${msg}`, "error");
}

View file

@ -11,6 +11,7 @@ import {
nativeDetectMainBranch,
nativeGetCurrentBranch,
} from "./native-git-bridge.js";
import { getErrorMessage } from "./error-utils.js";
const EXCLUDED_PATHS = [".sf", ".planning", "PLAN.md"];
function git(basePath, args) {
@ -213,7 +214,7 @@ export async function handlePrBranch(args, ctx) {
gitAllowFail(basePath, ["cherry-pick", "--abort"]);
gitAllowFail(basePath, ["reset", "--hard", "HEAD"]);
gitAllowFail(basePath, ["checkout", currentBranch]);
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
ctx.ui.notify(`Failed to create PR branch: ${msg}`, "error");
}
}

View file

@ -16,6 +16,7 @@
import { existsSync, mkdirSync } from "node:fs";
import { join, relative } from "node:path";
import { loadPrompt } from "./prompt-loader.js";
import { getErrorMessage } from "./error-utils.js";
// ─── Constants ────────────────────────────────────────────────────────────────
export const DEFAULT_FOCUS = "tech+arch";
export const VALID_FOCUS_AREAS = [
@ -97,7 +98,7 @@ export async function handleScan(args, ctx, pi) {
{ triggerTurn: true },
);
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
ctx.ui.notify(`Failed to dispatch scan: ${msg}`, "error");
}
}

View file

@ -25,6 +25,7 @@ import {
resolveSlicePath,
} from "./paths.js";
import { deriveState } from "./state.js";
import { getErrorMessage } from "./error-utils.js";
function git(basePath, args) {
return execFileSync("git", args, { cwd: basePath, encoding: "utf-8" }).trim();
@ -231,7 +232,7 @@ export async function handleShip(args, ctx, _pi) {
}).trim();
ctx.ui.notify(`PR created: ${prUrl}`, "success");
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
ctx.ui.notify(`Failed to create PR: ${msg}`, "error");
}
}

View file

@ -27,6 +27,7 @@ import {
insertTriageSkill,
openDatabase,
} from "./sf-db.js";
import { getErrorMessage } from "./error-utils.js";
const _EMPTY_TODO = "# TODO\n\nDump anything here.\n";
const MAX_DUMP_CHARS = 48_000;
@ -505,7 +506,7 @@ export function validateJsonlFile(path, schemaName) {
} catch (err) {
return {
ok: false,
error: `${schemaName} line ${i + 1}: ${err instanceof Error ? err.message : String(err)}`,
error: `${schemaName} line ${i + 1}: ${getErrorMessage(err)}`,
};
}
}
@ -665,7 +666,7 @@ export async function handleTodo(args, ctx, _pi) {
);
} catch (err) {
ctx.ui.notify(
`TODO triage failed: ${err instanceof Error ? err.message : String(err)}`,
`TODO triage failed: ${getErrorMessage(err)}`,
"warning",
);
}

View file

@ -14,6 +14,7 @@ import {
nativeHasChanges,
} from "./native-git-bridge.js";
import { autoCommitCurrentBranch } from "./worktree.js";
import { getErrorMessage } from "./error-utils.js";
import {
diffWorktreeAll,
diffWorktreeNumstat,
@ -146,7 +147,7 @@ async function handleMerge(args, ctx) {
removeWorktree(basePath, target, { deleteBranch: true });
ctx.ui.notify(`Removed empty worktree ${target}.`, "info");
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
ctx.ui.notify(
`Worktree partially removed: ${msg}\n\nRun 'git worktree prune' to clean up any dangling registrations.`,
"error",
@ -158,7 +159,7 @@ async function handleMerge(args, ctx) {
try {
autoCommitCurrentBranch(wt.path, "worktree-merge", target);
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
ctx.ui.notify(
[
`Auto-commit before merge failed: ${msg}`,
@ -176,7 +177,7 @@ async function handleMerge(args, ctx) {
try {
mergeWorktreeToMain(basePath, target, commitMessage);
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
if (err instanceof SFError && err.code === SF_GIT_ERROR) {
ctx.ui.notify(
`Merge requires the main branch to be checked out: ${msg}\n\nSwitch to ${mainBranch} (e.g. 'git checkout ${mainBranch}'), then re-run /worktree merge ${target}.`,
@ -199,7 +200,7 @@ async function handleMerge(args, ctx) {
removeWorktree(basePath, target, { deleteBranch: true });
ctx.ui.notify(successLines.join("\n"), "info");
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
const cleanupLines = [
...successLines,
"",
@ -228,7 +229,7 @@ async function handleClean(ctx) {
removeWorktree(basePath, wt.name, { deleteBranch: true });
removed.push(wt.name);
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
kept.push(`${wt.name} (failed: ${msg})`);
}
} else {
@ -286,7 +287,7 @@ async function handleRemove(args, ctx) {
removeWorktree(basePath, name, { deleteBranch: true });
ctx.ui.notify(`Removed worktree ${name}.`, "info");
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
ctx.ui.notify(
`Worktree partially removed: ${msg}\n\nRun 'git worktree prune' to clean up any dangling registrations.`,
"error",
@ -348,7 +349,7 @@ export async function handleWorktree(args, ctx) {
"warning",
);
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
ctx.ui.notify(`Worktree command failed: ${msg}`, "error");
}
}

View file

@ -32,6 +32,7 @@ import { handleQuick } from "../../quick.js";
import { createRun, listRuns } from "../../run-manager.js";
import { deriveState } from "../../state.js";
import { projectRoot } from "../context.js";
import { getErrorMessage } from "../../error-utils.js";
// ─── Custom Workflow Subcommands ─────────────────────────────────────────
const WORKFLOW_USAGE = [
@ -136,7 +137,7 @@ async function handleCustomWorkflow(sub, ctx, pi) {
// Clean up engine state so a failed workflow run doesn't pollute the next /autonomous
setActiveEngineId(null);
setActiveRunDir(null);
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
ctx.ui.notify(`Failed to run workflow "${defName}": ${msg}`, "error");
}
return true;
@ -184,7 +185,7 @@ async function handleCustomWorkflow(sub, ctx, pi) {
);
}
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
ctx.ui.notify(`Failed to validate "${defName}": ${msg}`, "error");
}
return true;

View file

@ -7,6 +7,7 @@
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { parse } from "yaml";
import { getErrorMessage } from "./error-utils.js";
/** Read and parse the frozen DEFINITION.yaml from a run directory. */
export function readFrozenDefinition(runDir) {
const defPath = join(runDir, "DEFINITION.yaml");
@ -14,7 +15,7 @@ export function readFrozenDefinition(runDir) {
const raw = readFileSync(defPath, "utf-8");
return parse(raw, { schema: "core" });
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
const message = getErrorMessage(err);
const wrapped = new Error(
`Failed to read/parse DEFINITION.yaml at ${defPath}: ${message}`,
{ cause: err },

View file

@ -17,6 +17,7 @@ import {
getWorktreeMode,
loadEffectiveSFPreferences,
} from "./preferences.js";
import { getErrorMessage } from "./error-utils.js";
/**
* Check that all Tier 1.4 config keys are well-formed and within expected ranges.
@ -253,7 +254,7 @@ export async function checkConfigHealth(issues, fixesApplied, shouldFix) {
code: "config_check_error",
scope: "project",
unitId: "config",
message: `Config health check failed: ${err instanceof Error ? err.message : String(err)}`,
message: `Config health check failed: ${getErrorMessage(err)}`,
file: ".sf/preferences.yaml",
fixable: false,
});
@ -360,7 +361,7 @@ export function checkVaultHealth(issues, _shouldFix) {
code: "vault_unreachable",
scope: "project",
unitId: "vault",
message: `Vault at ${vaultAddr} is unreachable (${err instanceof Error ? err.message : String(err)}). This is OK if vault is not yet set up.`,
message: `Vault at ${vaultAddr} is unreachable (${getErrorMessage(err)}). This is OK if vault is not yet set up.`,
file: "vault server",
fixable: false,
});
@ -372,7 +373,7 @@ export function checkVaultHealth(issues, _shouldFix) {
code: "vault_check_error",
scope: "project",
unitId: "vault",
message: `Vault health check failed: ${err instanceof Error ? err.message : String(err)}`,
message: `Vault health check failed: ${getErrorMessage(err)}`,
file: ".sf/preferences.yaml",
fixable: false,
});

View file

@ -26,6 +26,7 @@ import {
import { extractVerdict } from "./verdict-parser.js";
import { readEvents } from "./workflow-events.js";
import { renderAllProjections } from "./workflow-projections.js";
import { getErrorMessage } from "./error-utils.js";
const LEGACY_MILESTONE_DIR_RE = /^(M\d+)-.+$/;
const LEGACY_SLICE_DIR_RE = /^(S\d+)-.+$/;
@ -118,7 +119,7 @@ function projectionDriftIssues(basePath, milestoneId) {
code: "db_projection_roadmap_invalid",
scope: "milestone",
unitId: milestoneId,
message: `${milestoneId}-ROADMAP.json could not be parsed: ${err instanceof Error ? err.message : String(err)}. Projection is not executable runtime state.`,
message: `${milestoneId}-ROADMAP.json could not be parsed: ${getErrorMessage(err)}. Projection is not executable runtime state.`,
file: roadmapJsonPath,
fixable: false,
});

View file

@ -57,6 +57,7 @@ import { getMilestoneSlices, getSliceTasks, isDbAvailable } from "./sf-db.js";
import { deriveState, isMilestoneComplete } from "./state.js";
import { isClosedStatus } from "./status-guards.js";
import { parseUnitId } from "./unit-id.js";
import { getErrorMessage } from "./error-utils.js";
// ─── Flow Audit Implementation ────────────────────────────────────────────
const DEFAULT_STALE_PROGRESS_MS = 20 * 60 * 1000;
@ -814,7 +815,7 @@ export async function runFlowAudit(basePath, options = {}) {
else process.kill(row.pid, "SIGTERM");
killed = true;
} catch (err) {
killError = err instanceof Error ? err.message : String(err);
killError = getErrorMessage(err);
warnings.push(
`Failed to kill over-budget ${classification} child pid ${row.pid}: ${killError}`,
);

View file

@ -8,6 +8,7 @@ import { pathToFileURL } from "node:url";
import { getAgentDir } from "@singularity-forge/coding-agent";
import { logWarning } from "../workflow-logger.js";
import { createSFExtensionAPI } from "./sf-extension-api.js";
import { getErrorMessage } from "../error-utils.js";
// ─── Trust check (inlined; pi does not export isProjectTrusted from its
// package root, and constraint forbids modifying packages/coding-agent/) ─
@ -78,7 +79,7 @@ async function _loadEcosystemExtensionsImpl(pi, sharedHandlers, cwd) {
} catch (err) {
logWarning(
"ecosystem",
`failed to resolve extensions dir: ${err instanceof Error ? err.message : String(err)}`,
`failed to resolve extensions dir: ${getErrorMessage(err)}`,
);
return;
}
@ -91,7 +92,7 @@ async function _loadEcosystemExtensionsImpl(pi, sharedHandlers, cwd) {
} catch (err) {
logWarning(
"ecosystem",
`failed to read extensions dir: ${err instanceof Error ? err.message : String(err)}`,
`failed to read extensions dir: ${getErrorMessage(err)}`,
);
return;
}
@ -111,7 +112,7 @@ async function _loadOne(extDir, realExtDir, entry, api) {
} catch (err) {
logWarning(
"ecosystem",
`failed to resolve ${entry}: ${err instanceof Error ? err.message : String(err)}`,
`failed to resolve ${entry}: ${getErrorMessage(err)}`,
);
return;
}
@ -149,7 +150,7 @@ async function _loadOne(extDir, realExtDir, entry, api) {
} catch (err) {
logWarning(
"ecosystem",
`failed to import ${entry}: ${err instanceof Error ? err.message : String(err)}`,
`failed to import ${entry}: ${getErrorMessage(err)}`,
);
return;
}
@ -163,7 +164,7 @@ async function _loadOne(extDir, realExtDir, entry, api) {
} catch (err) {
logWarning(
"ecosystem",
`factory threw for ${entry}: ${err instanceof Error ? err.message : String(err)}`,
`factory threw for ${entry}: ${getErrorMessage(err)}`,
);
}
}

View file

@ -20,6 +20,7 @@
import { Type } from "@sinclair/typebox";
import { Value } from "@sinclair/typebox/value";
import { parse as parseYaml } from "yaml";
import { getErrorMessage } from "./error-utils.js";
// ─── Constants ────────────────────────────────────────────────────────────────
/** Schema version literal embedded in every EVAL-REVIEW.md frontmatter. */
export const EVAL_REVIEW_SCHEMA_VERSION = "eval-review/v1";
@ -143,7 +144,7 @@ export function parseEvalReviewFrontmatter(raw) {
try {
parsed = parseYaml(fm.yaml, { schema: "core" });
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
return { ok: false, error: `YAML parse error: ${msg}`, pointer: "/" };
}
const schema = EvalReviewFrontmatter;

View file

@ -8,6 +8,7 @@ import { spawn } from "node:child_process";
import { randomUUID } from "node:crypto";
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
import { resolve } from "node:path";
import { getErrorMessage } from "./error-utils.js";
const ALWAYS_FORWARD_ENV = ["PATH", "HOME"];
export const EXEC_DEFAULTS = {
@ -96,7 +97,7 @@ export function runExecSandbox(request, opts) {
});
} catch (err) {
const duration = Date.now() - started;
const message = err instanceof Error ? err.message : String(err);
const message = getErrorMessage(err);
writeFileSync(stdoutPath, "");
writeFileSync(stderrPath, `spawn error: ${message}\n`);
const result = {
@ -217,7 +218,7 @@ export function runExecSandbox(request, opts) {
resolveP(result);
};
child.on("error", (err) => {
const message = err instanceof Error ? err.message : String(err);
const message = getErrorMessage(err);
const line = `child error: ${message}\n`;
const remaining = opts.stderr_cap_bytes - stderrBytes;
if (remaining > 0) {

View file

@ -7,6 +7,7 @@ import { appendEvent } from "./workflow-events.js";
import { logWarning } from "./workflow-logger.js";
import { writeManifest } from "./workflow-manifest.js";
import { renderAllProjections } from "./workflow-projections.js";
import { getErrorMessage } from "./error-utils.js";
const REPO_INSTRUCTION_FILES = [
"AGENTS.md",
@ -116,7 +117,7 @@ export async function skipExecuteTaskForInstructionConflict(
} catch (err) {
logWarning(
"dispatch",
`instruction-conflict skip post-mutation hook warning: ${err instanceof Error ? err.message : String(err)}`,
`instruction-conflict skip post-mutation hook warning: ${getErrorMessage(err)}`,
);
}
invalidateStateCache();

View file

@ -8,6 +8,7 @@
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { logWarning } from "./workflow-logger.js";
import { getErrorMessage } from "./error-utils.js";
let cachedGraphApi = null;
let resolvedGraphApi = false;
@ -170,7 +171,7 @@ export async function inlineGraphSubgraph(projectDir, term, opts) {
} catch (err) {
logWarning(
"prompt",
`inlineGraphSubgraph failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`,
`inlineGraphSubgraph failed (non-fatal): ${getErrorMessage(err)}`,
);
return null;
}

View file

@ -8,6 +8,7 @@
// missing `pi` (e.g. in standalone unit tests) logs a warning so callers know
// hooks won't fire, but never throws.
import { logWarning } from "./workflow-logger.js";
import { getErrorMessage } from "./error-utils.js";
let _pi;
let _missingPiWarningLogged = false;
@ -40,7 +41,7 @@ async function emitEvent(event) {
} catch (err) {
logWarning(
"hook",
`emitExtensionEvent failed for ${event.type}: ${err instanceof Error ? err.message : String(err)}`,
`emitExtensionEvent failed for ${event.type}: ${getErrorMessage(err)}`,
);
return undefined;
}

View file

@ -1,3 +1,4 @@
import { getErrorMessage } from "./error-utils.js";
export {
clearPendingGate,
getPendingGate,
@ -37,7 +38,7 @@ export default async function registerExtension(pi) {
const { logWarning } = await import("./workflow-logger.js");
logWarning(
"bootstrap",
`Extension setup partially failed — SF commands are available but shortcuts/tools may be missing: ${err instanceof Error ? err.message : String(err)}`,
`Extension setup partially failed — SF commands are available but shortcuts/tools may be missing: ${getErrorMessage(err)}`,
);
}
@ -52,7 +53,7 @@ export default async function registerExtension(pi) {
const { logWarning } = await import("./workflow-logger.js");
logWarning(
"subagent",
`SF subagent setup failed — delegation tools may be missing: ${err instanceof Error ? err.message : String(err)}`,
`SF subagent setup failed — delegation tools may be missing: ${getErrorMessage(err)}`,
);
}
@ -66,7 +67,7 @@ export default async function registerExtension(pi) {
const { logWarning } = await import("./workflow-logger.js");
logWarning(
"ui",
`SF TUI setup failed — running with default header/footer: ${err instanceof Error ? err.message : String(err)}`,
`SF TUI setup failed — running with default header/footer: ${getErrorMessage(err)}`,
);
}
@ -78,7 +79,7 @@ export default async function registerExtension(pi) {
const { logWarning } = await import("./workflow-logger.js");
logWarning(
"usage-bar",
`SF usage bar setup failed: ${err instanceof Error ? err.message : String(err)}`,
`SF usage bar setup failed: ${getErrorMessage(err)}`,
);
}
@ -92,7 +93,7 @@ export default async function registerExtension(pi) {
const { logWarning } = await import("./workflow-logger.js");
logWarning(
"notifications",
`SF notifications setup failed: ${err instanceof Error ? err.message : String(err)}`,
`SF notifications setup failed: ${getErrorMessage(err)}`,
);
}
@ -106,7 +107,7 @@ export default async function registerExtension(pi) {
const { logWarning } = await import("./workflow-logger.js");
logWarning(
"inturn-guard",
`SF in-turn guard setup failed: ${err instanceof Error ? err.message : String(err)}`,
`SF in-turn guard setup failed: ${getErrorMessage(err)}`,
);
}
@ -120,7 +121,7 @@ export default async function registerExtension(pi) {
const { logWarning } = await import("./workflow-logger.js");
logWarning(
"permissions",
`SF permissions setup failed: ${err instanceof Error ? err.message : String(err)}`,
`SF permissions setup failed: ${getErrorMessage(err)}`,
);
}
@ -134,7 +135,7 @@ export default async function registerExtension(pi) {
const { logWarning } = await import("./workflow-logger.js");
logWarning(
"legacy-commands",
`SF legacy commands setup failed: ${err instanceof Error ? err.message : String(err)}`,
`SF legacy commands setup failed: ${getErrorMessage(err)}`,
);
}
}

View file

@ -3,6 +3,7 @@ import { logWarning } from "../workflow-logger.js";
import { createBeforeModelSelectHandler } from "./hook-handler.mjs";
import { loadCapabilityOverrides } from "./loadCapabilityOverrides.mjs";
import { validateOutcome } from "./outcome-recorder.mjs";
import { getErrorMessage } from "../error-utils.js";
const DEFAULT_N_PRIOR = 10;
const DEFAULT_ROLLING_DAYS = 30;
@ -45,7 +46,7 @@ async function ensureLearningReady() {
cachedDb = null;
logWarning(
"dispatch",
`failed to initialize learned routing: ${err instanceof Error ? err.message : String(err)}`,
`failed to initialize learned routing: ${getErrorMessage(err)}`,
);
} finally {
initPromise = null;
@ -75,7 +76,7 @@ export function recordLearnedOutcome(input) {
} catch (err) {
logWarning(
"db",
`failed to record learned routing outcome: ${err instanceof Error ? err.message : String(err)}`,
`failed to record learned routing outcome: ${getErrorMessage(err)}`,
);
return false;
}

View file

@ -8,6 +8,7 @@
*/
import { flushSyncQueue } from "./sync-scheduler.js";
import { getErrorMessage } from "./error-utils.js";
/**
* Flush SM sync queue for a project before unit completes.
@ -36,7 +37,7 @@ export async function flushProjectMemorySync(projectId) {
failed: 0,
elapsed,
status: "error",
reason: err instanceof Error ? err.message : String(err),
reason: getErrorMessage(err),
};
}
}

View file

@ -14,6 +14,7 @@ import {
import { dirname, join } from "node:path";
import { logWarning } from "./workflow-logger.js";
import { sfHome } from "./sf-home.js";
import { getErrorMessage } from "./error-utils.js";
/**
* Bump `FLOW_VERSION` whenever a new required step is added to ONBOARDING_STEPS.
* Records with an older flowVersion are treated as "needs partial re-onboarding"
@ -94,7 +95,7 @@ export function writeOnboardingRecord(patch) {
} catch (err) {
logWarning(
"state",
`Failed to persist onboarding record: ${err instanceof Error ? err.message : String(err)}`,
`Failed to persist onboarding record: ${getErrorMessage(err)}`,
{
file: FILE,
},

View file

@ -21,6 +21,7 @@ import {
worktreePath,
worktreesDir,
} from "./worktree-manager.js";
import { getErrorMessage } from "./error-utils.js";
// ─── Internal Helpers ─────────────────────────────────────────────────────────
/**
@ -95,7 +96,7 @@ export function sweepOrphanWorktrees(basePath) {
} catch (err) {
result.errors.push({
id: "<worktrees-dir>",
reason: `readdirSync failed: ${err instanceof Error ? err.message : String(err)}`,
reason: `readdirSync failed: ${getErrorMessage(err)}`,
});
return result;
}
@ -156,7 +157,7 @@ export function sweepOrphanWorktrees(basePath) {
} catch (err) {
result.errors.push({
id,
reason: err instanceof Error ? err.message : String(err),
reason: getErrorMessage(err),
});
}
}

View file

@ -8,6 +8,7 @@ import { dirname, join } from "node:path";
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
import { sfRoot } from "./paths.js";
import { logWarning } from "./workflow-logger.js";
import { getErrorMessage } from "./error-utils.js";
/**
* Resolve the path to the project-level preferences file.
@ -75,7 +76,7 @@ function readProjectPreferencesParts(path) {
} catch (err) {
logWarning(
"guided",
`preferences.yaml has invalid YAML — rewriting: ${err instanceof Error ? err.message : String(err)}`,
`preferences.yaml has invalid YAML — rewriting: ${getErrorMessage(err)}`,
);
}
}

View file

@ -22,6 +22,7 @@ import { nativeHasStagedChanges } from "./native-git-bridge.js";
import { sfRoot } from "./paths.js";
import { loadEffectiveSFPreferences } from "./preferences.js";
import { loadPrompt } from "./prompt-loader.js";
import { getErrorMessage } from "./error-utils.js";
let pendingQuickReturn = null;
// ─── Quick Task Helpers ───────────────────────────────────────────────────────
@ -191,7 +192,7 @@ export async function handleQuick(args, ctx, pi) {
}
} 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

@ -28,6 +28,7 @@ import { dirname, join } from "node:path";
import { SCAFFOLD_FILES } from "./agentic-docs-scaffold.js";
import { detectScaffoldDrift } from "./scaffold-drift.js";
import { logWarning } from "./workflow-logger.js";
import { getErrorMessage } from "./error-utils.js";
/**
* Build the .proposed body shipped by the Phase-D stub: the current scaffold
@ -135,7 +136,7 @@ export function dispatchScaffoldKeeperFireAndForget(basePath, ctx) {
queueMicrotask(() => {
dispatchScaffoldKeeperIfNeeded(basePath, ctx).catch((err) => {
logWarning("scaffold", "scaffold-keeper dispatch threw", {
error: err instanceof Error ? err.message : String(err),
error: getErrorMessage(err),
});
});
});

View file

@ -41,6 +41,7 @@ import {
} from "../task-frontmatter.js";
import { readTraceEvents } from "../uok/trace-writer.js";
import { logError, logWarning } from "../workflow-logger.js";
import { getErrorMessage } from "../error-utils.js";
let loadAttempted = false;
function loadProvider() {
@ -207,7 +208,7 @@ function createDatabaseSnapshot(rawDb, path) {
} catch (err) {
logWarning(
"sf-db",
`database snapshot failed: ${err instanceof Error ? err.message : String(err)}`,
`database snapshot failed: ${getErrorMessage(err)}`,
);
}
}
@ -242,7 +243,7 @@ function performDatabaseMaintenance(rawDb, path) {
} catch (err) {
logWarning(
"sf-db",
`database maintenance failed: ${err instanceof Error ? err.message : String(err)}`,
`database maintenance failed: ${getErrorMessage(err)}`,
);
}
}

View file

@ -38,6 +38,7 @@ import {
emitMilestoneResquash,
emitSliceMerged,
} from "./worktree-telemetry.js";
import { getErrorMessage } from "./error-utils.js";
/**
* Auto-worktree milestone branch name. Must match autoWorktreeBranch() in
@ -56,7 +57,7 @@ function cleanupMergeArtifacts(projectRoot) {
} catch (err) {
logWarning(
"worktree",
`merge artifact cleanup failed: ${err instanceof Error ? err.message : String(err)}`,
`merge artifact cleanup failed: ${getErrorMessage(err)}`,
);
}
}

View file

@ -20,6 +20,7 @@
* Tries to connect to SM at SINGULARITY_MEMORY_ADDR (default: http://localhost:8080).
* SM is opt-in: set SM_ENABLED=true to activate. Disabled by default.
*/
import { getErrorMessage } from "./error-utils.js";
export async function initializeSmClient() {
// SM is opt-in — disabled unless explicitly enabled
if (process.env.SM_ENABLED !== "true") {
@ -56,7 +57,7 @@ export async function initializeSmClient() {
return {
connected: false,
version: null,
reason: `SM unreachable at ${addr}: ${err instanceof Error ? err.message : String(err)}`,
reason: `SM unreachable at ${addr}: ${getErrorMessage(err)}`,
};
}
}
@ -111,7 +112,7 @@ export async function syncMemoryToSm(memory, opts = {}) {
}
} catch (err) {
console.warn(
`[sm-client] Error syncing memory ${memory.id}: ${err instanceof Error ? err.message : String(err)}`,
`[sm-client] Error syncing memory ${memory.id}: ${getErrorMessage(err)}`,
);
}
});

View file

@ -11,6 +11,7 @@
import { existsSync } from "node:fs";
import { join } from "node:path";
import { DatabaseSync } from "node:sqlite";
import { getErrorMessage } from "./error-utils.js";
const SPEC_GENERATOR_VERSION = "1";
@ -51,7 +52,7 @@ function readDbSummary(basePath) {
`- DB spec rows: milestone_specs=${milestoneSpecs}, slice_specs=${sliceSpecs}, task_specs=${taskSpecs}`,
].join("\n");
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
const message = getErrorMessage(err);
return `- Database state: unreadable (${message})`;
} finally {
db?.close();

View file

@ -1,4 +1,5 @@
import { randomUUID } from "node:crypto";
import { getErrorMessage } from "../error-utils.js";
export class SubagentBackgroundJobManager {
jobs = new Map();
evictionTimers = new Map();
@ -60,7 +61,7 @@ export class SubagentBackgroundJobManager {
return;
}
job.status = "failed";
job.errorText = err instanceof Error ? err.message : String(err);
job.errorText = getErrorMessage(err);
this.scheduleEviction(job.id);
this.deliverResult(job);
});

View file

@ -10,6 +10,7 @@ import * as fs from "node:fs";
import * as path from "node:path";
import { promisify } from "node:util";
import { sfHome } from "../sf-home.js";
import { getErrorMessage } from "../error-utils.js";
const execFile = promisify(execFileCb);
// ============================================================================
@ -356,7 +357,7 @@ export async function mergeDeltaPatches(repoRoot, patches) {
success: false,
appliedPatches,
failedPatches,
error: `Patch conflict: ${err instanceof Error ? err.message : String(err)}`,
error: `Patch conflict: ${getErrorMessage(err)}`,
};
}
// Apply for real

View file

@ -16,6 +16,7 @@
import { delay } from "./atomic-write.js";
import { syncMemoryToSm } from "./sm-client.js";
import { getErrorMessage } from "./error-utils.js";
/**
* Global sync scheduler state.
@ -201,7 +202,7 @@ async function trySyncWithRetry(item, attempt = 0) {
if (typeof process !== "undefined" && process.env.DEBUG_SM_SYNC) {
console.warn(
`[SM sync] failed after ${MAX_RETRIES} retries: ${item.id}`,
err instanceof Error ? err.message : String(err),
getErrorMessage(err),
);
}

View file

@ -6,6 +6,7 @@
import { EXEC_DEFAULTS, runExecSandbox } from "../exec-sandbox.js";
import { isContextModeEnabled } from "../preferences-types.js";
import { sanitizeExternalContent } from "../safety/sanitize-external-content.js";
import { getErrorMessage } from "../error-utils.js";
export function buildExecOptions(baseDir, cfg, extras) {
const allowlist = Array.isArray(cfg?.exec_env_allowlist)
? cfg.exec_env_allowlist
@ -111,7 +112,7 @@ export async function executeSfExec(params, deps) {
);
return formatResult(result);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
const message = getErrorMessage(err);
return {
content: [
{ type: "text", text: `Error: run_command failed — ${message}` },

View file

@ -18,6 +18,7 @@ import {
resolveSiftSearchScope,
} from "../code-intelligence.js";
import { recordRetrievalEvidence } from "../retrieval-evidence.js";
import { getErrorMessage } from "../error-utils.js";
const _KNOWN_STRATEGIES = [
"hybrid",
@ -344,7 +345,7 @@ export function registerSiftSearchTool(pi) {
};
} catch (err) {
const elapsedMs = Date.now() - startedAt;
const message = err instanceof Error ? err.message : String(err);
const message = getErrorMessage(err);
await recordRetrievalEvidence(projectRoot, {
backend: "sift",
sourceKind: "code",

View file

@ -26,6 +26,7 @@ import { handlePlanSlice } from "./plan-slice.js";
import { handleReassessRoadmap } from "./reassess-roadmap.js";
import { handleReplanSlice } from "./replan-slice.js";
import { handleValidateMilestone } from "./validate-milestone.js";
import { getErrorMessage } from "../error-utils.js";
export const SUPPORTED_SUMMARY_ARTIFACT_TYPES = [
"SUMMARY",
"RESEARCH",
@ -180,7 +181,7 @@ export async function executeSummarySave(params, basePath = process.cwd()) {
},
};
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
logError("tool", `save_summary tool failed: ${msg}`, {
tool: "save_summary",
error: String(err),
@ -269,7 +270,7 @@ export async function executeTaskComplete(params, basePath = process.cwd()) {
} catch (err) {
// Escalation is additive — never block task completion if it fails,
// but DO tell the agent so they don't think the issue was recorded.
escalationError = err instanceof Error ? err.message : String(err);
escalationError = getErrorMessage(err);
logError(
"tool",
`complete_task escalation write failed: ${escalationError}`,
@ -303,7 +304,7 @@ export async function executeTaskComplete(params, basePath = process.cwd()) {
},
};
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
logError("tool", `complete_task tool failed: ${msg}`, {
tool: "complete_task",
error: String(err),
@ -401,7 +402,7 @@ export async function executeSliceComplete(params, basePath = process.cwd()) {
},
};
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
logError("tool", `complete_slice tool failed: ${msg}`, {
tool: "complete_slice",
error: String(err),
@ -456,7 +457,7 @@ export async function executeCompleteMilestone(
},
};
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
logError("tool", `complete_milestone tool failed: ${msg}`, {
tool: "complete_milestone",
error: String(err),
@ -511,7 +512,7 @@ export async function executeValidateMilestone(
},
};
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
logError("tool", `validate_milestone tool failed: ${msg}`, {
tool: "validate_milestone",
error: String(err),
@ -564,7 +565,7 @@ export async function executeReassessRoadmap(params, basePath = process.cwd()) {
},
};
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
logError("tool", `reassess_roadmap tool failed: ${msg}`, {
tool: "reassess_roadmap",
error: String(err),
@ -654,7 +655,7 @@ export async function executeSaveGateResult(params, basePath = process.cwd()) {
},
};
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
logError("tool", `record_gate failed: ${msg}`, {
tool: "record_gate",
error: String(err),
@ -709,7 +710,7 @@ export async function executePlanMilestone(params, basePath = process.cwd()) {
},
};
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
logError("tool", `plan_milestone tool failed: ${msg}`, {
tool: "plan_milestone",
error: String(err),
@ -762,7 +763,7 @@ export async function executePlanSlice(params, basePath = process.cwd()) {
},
};
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
logError("tool", `plan_slice tool failed: ${msg}`, {
tool: "plan_slice",
error: String(err),
@ -815,7 +816,7 @@ export async function executeReplanSlice(params, basePath = process.cwd()) {
},
};
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
logError("tool", `replan_slice tool failed: ${msg}`, {
tool: "replan_slice",
error: String(err),
@ -881,7 +882,7 @@ export async function executeMilestoneStatus(params, basePath = process.cwd()) {
};
});
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
logWarning("tool", `milestone_status tool failed: ${msg}`);
return {
content: [

View file

@ -28,6 +28,7 @@ import {
pushPromptHistory,
readPromptHistory,
} from "./prompt-history.js";
import { getErrorMessage } from "../error-utils.js";
const WORK_MODE_CYCLE = [
"chat",
@ -487,7 +488,7 @@ export default function sfTui(pi) {
return { output: response };
} catch (err) {
return {
output: `Agent dispatch failed: ${err instanceof Error ? err.message : String(err)}`,
output: `Agent dispatch failed: ${getErrorMessage(err)}`,
};
}
},

View file

@ -26,6 +26,7 @@ import {
} from "@a2a-js/sdk/server/express";
import express from "express";
import { buildAgentCard } from "./a2a-transport.js";
import { getErrorMessage } from "../error-utils.js";
const agentName = process.env.SF_A2A_AGENT_NAME;
const agentRole = process.env.SF_A2A_AGENT_ROLE ?? "worker";
@ -103,7 +104,7 @@ class SwarmAgentExecutor {
resultBody = await this._handleEnvelope(envelope);
} catch (err) {
resultBody = {
error: err instanceof Error ? err.message : String(err),
error: getErrorMessage(err),
agentName: this._name,
role: this._role,
unitId: envelope?.unitId,

View file

@ -184,7 +184,7 @@ function hasPriorParallelResearchFailure(basePath, mid) {
} catch (err) {
logWarning(
"dispatch",
`Ignoring unreadable parallel-research runtime state for ${mid}: ${err instanceof Error ? err.message : String(err)}`,
`Ignoring unreadable parallel-research runtime state for ${mid}: ${getErrorMessage(err)}`,
);
return false;
}
@ -1576,7 +1576,7 @@ export const DISPATCH_RULES = [
} catch (err) {
logWarning(
"dispatch",
`failed to persist validation attention marker: ${err instanceof Error ? err.message : String(err)}`,
`failed to persist validation attention marker: ${getErrorMessage(err)}`,
);
}
return {
@ -1696,7 +1696,7 @@ export const DISPATCH_RULES = [
/* fall through — don't block on DB errors */
logWarning(
"dispatch",
`verification class check failed: ${err instanceof Error ? err.message : String(err)}`,
`verification class check failed: ${getErrorMessage(err)}`,
);
}
// P5-A: Advisory check for deferred requirements targeting this milestone
@ -1915,6 +1915,7 @@ export const DISPATCH_RULES = [
];
import { getRegistry, hasRegistry } from "../rule-registry.js";
import { getErrorMessage } from "../error-utils.js";
// ─── Dispatch Envelope Emission ───────────────────────────────────────────
/**
@ -2027,7 +2028,7 @@ export async function resolveDispatch(ctx) {
// surface real bugs, then fall back.
logWarning(
"dispatch",
`registry dispatch failed, falling back to inline rules: ${err instanceof Error ? err.message : String(err)}`,
`registry dispatch failed, falling back to inline rules: ${getErrorMessage(err)}`,
);
}
}

View file

@ -8,6 +8,7 @@ import { snapshotUnitMetrics } from "../metrics.js";
import { updateSubscriptionTokensUsed } from "../preferences-models.js";
import { logWarning } from "../workflow-logger.js";
import { writeTurnGitTransaction } from "./gitops.js";
import { getErrorMessage } from "../error-utils.js";
/**
* Snapshot metrics, save activity log, and fire-and-forget memory extraction
* for a completed unit. Returns the activity log file path (if any).
@ -60,7 +61,7 @@ export async function closeoutUnit(
/* non-fatal */
logWarning(
"engine",
`operation failed: ${err instanceof Error ? err.message : String(err)}`,
`operation failed: ${getErrorMessage(err)}`,
);
}
}

View file

@ -45,6 +45,7 @@ import {
formatExecuteTaskRecoveryStatus,
inspectExecuteTaskDurability,
} from "./unit-runtime.js";
import { getErrorMessage } from "../error-utils.js";
function computeTokenCountFromSession(ctx) {
const entries = ctx.sessionManager?.getEntries?.() ?? [];
@ -353,7 +354,7 @@ export async function runPostUnitVerification(vctx, pauseAuto) {
.catch((err) => ({
outcome: "fail",
failureClass: "unknown",
rationale: `Gate ${id} threw: ${err instanceof Error ? err.message : String(err)}`,
rationale: `Gate ${id} threw: ${getErrorMessage(err)}`,
})),
),
);

View file

@ -12,6 +12,7 @@ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { delay } from "../atomic-write.js";
import { getErrorMessage } from "../error-utils.js";
const DEFAULT_LATENCY_PROBABILITY = 0.05;
const DEFAULT_PARTIAL_FAILURE_PROBABILITY = 0.03;
@ -39,7 +40,7 @@ export class ChaosMonkeyGate {
return {
outcome: "fail",
failureClass: "execution",
rationale: `Chaos monkey injected fault: ${err instanceof Error ? err.message : String(err)}`,
rationale: `Chaos monkey injected fault: ${getErrorMessage(err)}`,
findings: `Injected during verification phase (attempt ${attempt})`,
};
}

View file

@ -9,6 +9,7 @@ import { logWarning } from "../workflow-logger.js";
import { buildAuditEnvelope, emitUokAuditEvent } from "./audit.js";
import { validateGate } from "./contracts.js";
import { appendTraceEvent } from "./trace-writer.js";
import { getErrorMessage } from "../error-utils.js";
const RETRY_MATRIX = {
none: 0,
@ -112,7 +113,7 @@ export async function enrichGateResultWithMemory(gateResult, gateId) {
// Degrade gracefully - memory enrichment never changes gate result
logWarning(
"gate-runner",
`Memory enrichment failed for gate ${gateId}: ${err instanceof Error ? err.message : String(err)}`,
`Memory enrichment failed for gate ${gateId}: ${getErrorMessage(err)}`,
);
}
@ -392,7 +393,7 @@ export class UokGateRunner {
try {
result = await gate.execute(ctx, attempt);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
const message = getErrorMessage(err);
result = {
outcome: "fail",
failureClass: "unknown",

View file

@ -21,6 +21,7 @@
import { existsSync, readFileSync } from "node:fs";
import { homedir } from "node:os";
import { logWarning } from "./workflow-logger.js";
import { getErrorMessage } from "./error-utils.js";
/**
* In-memory cache for resolved vault secrets.
@ -67,7 +68,7 @@ export function parseVaultUri(uri) {
return { path, field, vaultAddr, token };
} catch (err) {
return {
error: `Failed to parse vault URI: ${err instanceof Error ? err.message : String(err)}`,
error: `Failed to parse vault URI: ${getErrorMessage(err)}`,
};
}
}
@ -140,7 +141,7 @@ async function fetchVaultSecret(path, vaultAddr, token) {
// Log error but don't throw — fail open
logWarning(
"vault-resolver",
`Vault fetch failed for ${path}: ${err instanceof Error ? err.message : String(err)}`,
`Vault fetch failed for ${path}: ${getErrorMessage(err)}`,
);
return null;
}

View file

@ -27,6 +27,7 @@ import { sfHome } from './sf-home.js';
import { extname, join, sep as pathSep, resolve } from "node:path";
import { parse as parseYaml } from "yaml";
import { validateDefinition } from "./definition-loader.js";
import { getErrorMessage } from "./error-utils.js";
// ─── Constants ───────────────────────────────────────────────────────────
const MAX_RESPONSE_BYTES = 256 * 1024;
@ -212,7 +213,7 @@ export function validateFetchedContent(fetched) {
parsed = parseYaml(fetched.content);
} catch (err) {
throw new Error(
`Installed YAML failed to parse: ${err instanceof Error ? err.message : String(err)}`,
`Installed YAML failed to parse: ${getErrorMessage(err)}`,
);
}
const result = validateDefinition(parsed);

View file

@ -17,6 +17,7 @@ import { sfHome } from './sf-home.js';
import { basename, extname, join } from "node:path";
import { parse as parseYaml } from "yaml";
import { loadRegistry } from "./workflow-templates.js";
import { getErrorMessage } from "./error-utils.js";
// ─── Path resolution ─────────────────────────────────────────────────────
function resolveBundledDir() {
@ -143,7 +144,7 @@ function loadYamlPlugin(filePath, source) {
format: "yaml",
source,
meta: { displayName: name, mode: "yaml-step" },
error: `YAML parse error: ${err instanceof Error ? err.message : String(err)}`,
error: `YAML parse error: ${getErrorMessage(err)}`,
};
}
if (parsed == null || typeof parsed !== "object") {

View file

@ -58,6 +58,7 @@ import {
emitCanonicalRootRedirect,
emitWorktreeDivergenceWarning,
} from "./worktree-telemetry.js";
import { getErrorMessage } from "./error-utils.js";
/** Commits-behind threshold above which a divergence warning is emitted. */
export const WORKTREE_DIVERGENCE_CAP = 50;
// ─── Path Helpers ──────────────────────────────────────────────────────────
@ -147,7 +148,7 @@ export function resolveCanonicalMilestoneRoot(basePath, milestoneId) {
} catch (err) {
logWarning(
"worktree",
`canonical-root-redirect telemetry failed: ${err instanceof Error ? err.message : String(err)}`,
`canonical-root-redirect telemetry failed: ${getErrorMessage(err)}`,
);
}
return wtPath;

View file

@ -25,6 +25,7 @@ import {
resquashMilestoneOnMain,
} from "./slice-cadence.js";
import { resolveGitDir } from "./worktree-manager.js";
import { getErrorMessage } from "./error-utils.js";
import {
emitWorktreeCreated,
emitWorktreeMerged,
@ -165,7 +166,7 @@ export class WorktreeResolver {
});
}
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
debugLog("WorktreeResolver", {
action: "enterMilestone",
milestoneId,
@ -219,7 +220,7 @@ export class WorktreeResolver {
action: "exitMilestone",
milestoneId,
phase: "auto-commit-failed",
error: err instanceof Error ? err.message : String(err),
error: getErrorMessage(err),
});
}
try {
@ -231,7 +232,7 @@ export class WorktreeResolver {
action: "exitMilestone",
milestoneId,
phase: "teardown-failed",
error: err instanceof Error ? err.message : String(err),
error: getErrorMessage(err),
});
}
this.restoreToProjectRoot();
@ -355,7 +356,7 @@ export class WorktreeResolver {
action: "mergeAndExit",
milestoneId,
phase: "resquash",
error: err instanceof Error ? err.message : String(err),
error: getErrorMessage(err),
});
}
// #4764 — record merge completion. Only reaches here when an actual
@ -481,7 +482,7 @@ export class WorktreeResolver {
);
}
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
debugLog("WorktreeResolver", {
action: "mergeAndExit",
milestoneId,
@ -604,7 +605,7 @@ export class WorktreeResolver {
});
return true;
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const msg = getErrorMessage(err);
debugLog("WorktreeResolver", {
action: "mergeAndExit",
milestoneId,