refactor(errors): surface 57 deferred catch-and-swallow sites via logger

The earlier sweep cleaned the hot path (worker/runtime/automation/api-
server/ipc core — 59 sites). This pass finishes the deferred long tail:
main/* services, database/* CRUD modules, api-server/* routes, ipc-*
handlers, thread-integration, automation helpers, browser, skills.

Default strategy: catch (error) { logger.warn("[module] context:", ...) }
— preserve current behavior, make the failure visible instead of
silently hidden. Widened 3 logger interface signatures to include warn
where callers only passed info/error previously (thread-helpers,
host-environment-service, ipc-domain-common).

Legitimate boundaries left intact: JSON.parse on untrusted input, DNS
resolver no-records paths, explicit safe* helpers (safeJson, parseJson,
parseTriggerDetail), URL-parse predicates, and `<unserialisable>`
fallbacks inside the prompt-builder summarisers. Each kept catch now
either has a reason in context or is a documented safe* helper.

Tests adjusted only where test-constructed no-op loggers needed a warn
stub (message-dispatch, company-mode-switch, ipc-agent-graph-handlers).

With the earlier pass, AgentCompany no longer has silently-swallowing
catch blocks in main-process or renderer business code.
This commit is contained in:
ZacharyZhang-NY committed 2026-04-17 15:25:13 -04:00
1 parent ecd1bc2e33
commit 40040b3dd2
46 files changed
+209 -113

No files matched your search

+2 -1
View File
@@ -266,7 +266,8 @@ export class RuntimeBridge {
let task: { title: string } | null = null;
try {
task = this.deps.db.getTask(envelope.taskId);
} catch {
} catch (err) {
this.deps.logger.warn(`[runtime-bridge] getTask failed for ${envelope.taskId} in recordThreadEvent:`, err instanceof Error ? err.message : String(err));
return;
}
if (!task) return;
+4 -2
View File
@@ -13,7 +13,8 @@ export function registerAgentControlRoutes(server: AgentApiServer) {
const targetAgent = (() => {
try {
return db.getAgent(targetAgentId);
} catch {
} catch (err) {
log.warn(`[api/agent-control] getAgent failed for ${targetAgentId}:`, err instanceof Error ? err.message : String(err));
return null;
}
})();
@@ -52,7 +53,8 @@ export function registerAgentControlRoutes(server: AgentApiServer) {
let targetAgent;
try {
targetAgent = db.getAgent(targetAgentId);
} catch {
} catch (err) {
log.warn(`[api/agent-control] getAgent failed for heartbeat ${targetAgentId}:`, err instanceof Error ? err.message : String(err));
targetAgent = null;
}
if (!targetAgent || targetAgent.companyId !== token.companyId) {
+6 -3
View File
@@ -156,7 +156,8 @@ export function registerAgentRoutes(server: AgentApiServer) {
let agent;
try {
agent = db.getAgent(requireParam(params, "agentId"));
} catch {
} catch (err) {
log.warn(`[api/agents] getAgent failed:`, err instanceof Error ? err.message : String(err));
server.json(res, 404, { error: "Agent not found" });
return;
}
@@ -262,7 +263,8 @@ export function registerAgentRoutes(server: AgentApiServer) {
const connectorRecord = (() => {
try {
return db.getConnector(input.connectorId);
} catch {
} catch (err) {
log.warn(`[api/agents] getConnector failed for ${input.connectorId}:`, err instanceof Error ? err.message : String(err));
return null;
}
})();
@@ -305,7 +307,8 @@ export function registerAgentRoutes(server: AgentApiServer) {
? (() => {
try {
return db.getAgent(resolvedReportsTo).name;
} catch {
} catch (err) {
log.warn(`[api/agents] getAgent failed for manager ${resolvedReportsTo}:`, err instanceof Error ? err.message : String(err));
return resolvedReportsTo;
}
})()
+4 -2
View File
@@ -38,7 +38,8 @@ export function registerMeetingRoutes(server: AgentApiServer) {
return;
}
}
} catch {
} catch (err) {
log.warn(`[api/meetings] participantAgentIds JSON parse failed:`, err instanceof Error ? err.message : String(err));
server.json(res, 400, { error: "participantAgentIds must be a JSON array of agent ids" });
return;
}
@@ -95,7 +96,8 @@ export function registerMeetingRoutes(server: AgentApiServer) {
if (data.actionItemsJson) {
try {
actionItems = JSON.parse(data.actionItemsJson) as Array<{ title?: string; assigneeAgentId?: string; description?: string }>;
} catch {
} catch (err) {
log.warn(`[api/meetings] actionItemsJson parse failed:`, err instanceof Error ? err.message : String(err));
server.json(res, 400, { error: "actionItemsJson must be valid JSON" });
return;
}
+5 -2
View File
@@ -1,3 +1,4 @@
import log from "electron-log/main.js";
import { agentMessageInputSchema } from "@shared/contracts";
import { dispatchAgentMessage } from "../message-dispatch";
import { requireParam, type AgentApiServer } from "./server-core";
@@ -59,7 +60,8 @@ export function registerMessageRoutes(server: AgentApiServer) {
}
server.json(res, 201, comment);
} catch {
} catch (err) {
log.warn(`[api/messages] addComment failed:`, err instanceof Error ? err.message : String(err));
server.json(res, 404, { error: "Task not found" });
}
});
@@ -71,7 +73,8 @@ export function registerMessageRoutes(server: AgentApiServer) {
server.json(res, 404, { error: "Task not found" });
return;
}
} catch {
} catch (err) {
log.warn(`[api/messages] getTask failed for comments list:`, err instanceof Error ? err.message : String(err));
server.json(res, 404, { error: "Task not found" });
return;
}
+4 -2
View File
@@ -54,7 +54,8 @@ export function registerTaskRoutes(server: AgentApiServer) {
return;
}
server.json(res, 200, task);
} catch {
} catch (err) {
log.warn(`[api/tasks] getTask failed:`, err instanceof Error ? err.message : String(err));
server.json(res, 404, { error: "Task not found" });
}
});
@@ -73,7 +74,8 @@ export function registerTaskRoutes(server: AgentApiServer) {
let task;
try {
task = db.getTask(requireParam(params, "issueId"));
} catch {
} catch (err) {
log.warn(`[api/tasks] getTask failed for checkout:`, err instanceof Error ? err.message : String(err));
server.json(res, 404, { error: "Task not found" });
return;
}
+7 -3
View File
@@ -1,3 +1,4 @@
import log from "electron-log/main.js";
import { z } from "zod";
import { goalInputSchema, projectInputSchema } from "@shared/contracts";
import { canManageGoal, canManageProject } from "../agent-permissions";
@@ -102,7 +103,8 @@ export function registerWorkGraphRoutes(server: AgentApiServer) {
});
onDomainChanged();
server.json(res, 200, { ok: true });
} catch {
} catch (err) {
log.warn(`[api/work-graph] Failed to update project:`, err instanceof Error ? err.message : String(err));
server.json(res, 500, { error: "Failed to update project" });
}
});
@@ -187,7 +189,8 @@ export function registerWorkGraphRoutes(server: AgentApiServer) {
});
onDomainChanged();
server.json(res, 200, { ok: true });
} catch {
} catch (err) {
log.warn(`[api/work-graph] Failed to update goal:`, err instanceof Error ? err.message : String(err));
server.json(res, 500, { error: "Failed to update goal" });
}
});
@@ -241,7 +244,8 @@ export function registerWorkGraphRoutes(server: AgentApiServer) {
});
onDomainChanged();
server.json(res, 201, { id });
} catch {
} catch (err) {
log.warn(`[api/work-graph] Invalid sprint data:`, err instanceof Error ? err.message : String(err));
server.json(res, 400, { error: "Invalid sprint data" });
}
});
+8 -6
View File
@@ -151,7 +151,8 @@ export function executeAutomationAction(
try {
const sender = db.getAgent(fromAgentId);
channelTargetId = sender.department ?? null;
} catch {
} catch (err) {
logger.warn(`[automation] getAgent failed for sender ${fromAgentId}; dropping department target:`, err instanceof Error ? err.message : String(err));
channelTargetId = null;
}
}
@@ -221,21 +222,22 @@ export function executeAutomationAction(
let agentName = "an agent";
try {
agentName = db.getAgent(targetAgentId).name;
} catch {
// Fall back to the generic label when the agent was deleted mid-flight.
} catch (err) {
logger.warn(`[automation] getAgent failed for escalation target ${targetAgentId}; using generic label:`, err instanceof Error ? err.message : String(err));
}
let taskTitle = ctx.taskId ?? "unknown";
try {
if (ctx.taskId) taskTitle = db.getTask(ctx.taskId).title;
} catch {
// Keep the fallback title when the task no longer exists.
} catch (err) {
logger.warn(`[automation] getTask failed for escalation task ${ctx.taskId}; using id as title:`, err instanceof Error ? err.message : String(err));
}
// Heartbeat tasks are internal system tasks — skip escalation messages to avoid flooding channels.
let isHeartbeatTask = false;
if (ctx.taskId) {
try {
isHeartbeatTask = db.getTask(ctx.taskId).isHeartbeat === true;
} catch {
} catch (err) {
logger.warn(`[automation] getTask failed for heartbeat check on ${ctx.taskId}; treating as non-heartbeat:`, err instanceof Error ? err.message : String(err));
isHeartbeatTask = false;
}
}
+11 -5
View File
@@ -1,11 +1,16 @@
import type { AppDatabase } from "../database";
import type { AutomationEventContext } from "./types";
type ConditionLogger = {
warn: (...args: unknown[]) => void;
};
export function matchesEventConfig(
db: AppDatabase,
configJson: string,
sourceDepartment: string | null,
ctx: AutomationEventContext,
logger?: ConditionLogger,
): boolean {
if (sourceDepartment && ctx.department && sourceDepartment !== ctx.department) {
return false;
@@ -21,8 +26,8 @@ export function matchesEventConfig(
const task = ctx.taskId ? db.getTask(ctx.taskId) : null;
if (task && task.priority !== conditions.priority) return false;
// If task was deleted, skip this condition instead of rejecting the rule.
} catch {
// Entity missing — treat as condition not applicable, continue matching.
} catch (err) {
logger?.warn(`[automation-conditions] getTask failed for ${ctx.taskId}; treating priority condition as not applicable:`, err instanceof Error ? err.message : String(err));
}
}
if (typeof conditions.budgetThresholdPct === "number" && ctx.agentId) {
@@ -33,8 +38,8 @@ export function matchesEventConfig(
if (utilization < (conditions.budgetThresholdPct as number) / 100) return false;
}
// If budget is 0 (unlimited), skip budget condition instead of rejecting.
} catch {
// Agent missing — treat as condition not applicable, continue matching.
} catch (err) {
logger?.warn(`[automation-conditions] getAgent failed for ${ctx.agentId}; treating budget condition as not applicable:`, err instanceof Error ? err.message : String(err));
}
}
if (conditions.taskId && ctx.taskId && conditions.taskId !== ctx.taskId) return false;
@@ -47,6 +52,7 @@ export function matchesConditions(
db: AppDatabase,
rule: { conditionsJson: string; sourceDepartment: string | null },
ctx: AutomationEventContext,
logger?: ConditionLogger,
): boolean {
return matchesEventConfig(db, rule.conditionsJson, rule.sourceDepartment, ctx);
return matchesEventConfig(db, rule.conditionsJson, rule.sourceDepartment, ctx, logger);
}
+2 -2
View File
@@ -102,8 +102,8 @@ export function handleTaskStatusChange(
}
}
}
} catch {
// Parent task may have been deleted or reassigned during completion cascade evaluation.
} catch (err) {
deps.logger.warn(`[automation] Parent task ${task.parentId} lookup failed during completion cascade:`, err instanceof Error ? err.message : String(err));
}
}
}
@@ -274,8 +274,8 @@ export function processRunCompletion(
try {
const agent = db.getAgent(run.agentId);
automationContext.department = agent.department ?? undefined;
} catch {
// Agent can disappear after a run completes; keep the context without department.
} catch (err) {
deps.logger.warn(`[automation] getAgent failed for ${run.agentId} during run completion; dropping department from context:`, err instanceof Error ? err.message : String(err));
}
if (isSuccess) {
@@ -295,8 +295,8 @@ export function processRunCompletion(
}
}
}
} catch {
// Parent task may have been deleted or reassigned during cascade evaluation.
} catch (err) {
deps.logger.warn(`[automation] Parent task ${task.parentId} cascade failed during run completion:`, err instanceof Error ? err.message : String(err));
}
}
}
@@ -326,8 +326,8 @@ export function processRunCompletion(
notify,
(en, zh) => localizeNotification(budgetLocale, en, zh),
);
} catch {
// Budget follow-up is best-effort; do not fail run completion if the agent disappeared.
} catch (err) {
deps.logger.warn(`[automation] Budget follow-up failed for agent ${run.agentId}:`, err instanceof Error ? err.message : String(err));
}
}
}
+2 -2
View File
@@ -109,7 +109,7 @@ export function createAutomationService({
try {
const rules = db.listActiveAutomationRules(ctx.companyId, trigger);
for (const rule of rules) {
if (!matchesConditions(db, rule, ctx)) continue;
if (!matchesConditions(db, rule, ctx, logger)) continue;
const executionKey = JSON.stringify({
ruleId: rule.id,
trigger,
@@ -166,7 +166,7 @@ export function createAutomationService({
try {
const workflows = db.listTriggerableWorkflows(ctx.companyId, trigger);
for (const workflow of workflows) {
if (!matchesEventConfig(db, workflow.triggerConfigJson, null, ctx)) continue;
if (!matchesEventConfig(db, workflow.triggerConfigJson, null, ctx, logger)) continue;
const executionKey = JSON.stringify({
workflowId: workflow.id,
trigger,
+3 -1
View File
@@ -1,4 +1,5 @@
import { join } from "node:path";
import log from "electron-log/main.js";
import type { AppDatabase } from "./database";
import { BrowserManager } from "./browser-manager";
@@ -28,7 +29,8 @@ export function createBrowserActionService({
function getApprovalOrNull(approvalId: string): ReturnType<typeof db.getApprovalRecord> | null {
try {
return db.getApprovalRecord(approvalId);
} catch {
} catch (err) {
log.warn(`[browser-action] getApprovalRecord failed for ${approvalId}:`, err instanceof Error ? err.message : String(err));
return null;
}
}
+4 -2
View File
@@ -236,7 +236,8 @@ export class BrowserManager {
rules.cookieOrigins.map(async (origin) => {
try {
return await ses.cookies.get({ url: origin });
} catch {
} catch (err) {
log.warn(`[browser-manager] cookies.get failed for origin ${origin}:`, err instanceof Error ? err.message : String(err));
return [];
}
}),
@@ -293,7 +294,8 @@ export class BrowserManager {
})()
`);
return result && (result.accountName || result.displayName) ? result : { accountName: "", displayName: "" };
} catch {
} catch (err) {
log.warn(`[browser-manager] extractProfileFromSession evaluation failed:`, err instanceof Error ? err.message : String(err));
return { accountName: "", displayName: "" };
} finally {
if (!win.isDestroyed()) win.close();
+10 -4
View File
@@ -24,12 +24,17 @@ export interface BudgetGateResult {
* Returns a human-readable label for a budget policy scope.
* Looks up entity names from the database for agent and project scopes.
*/
function getScopeLabel(db: AppDatabase, policy: BudgetPolicyRecord): string {
function getScopeLabel(
db: AppDatabase,
policy: BudgetPolicyRecord,
logger: { warn: (...args: unknown[]) => void },
): string {
if (policy.scopeType === "agent") {
try {
const agent = db.getAgent(policy.scopeId);
return `Agent "${agent.name}"`;
} catch {
} catch (err) {
logger.warn(`[budget] getAgent failed for scope ${policy.scopeId}:`, err instanceof Error ? err.message : String(err));
return `Agent ${policy.scopeId.slice(0, 8)}`;
}
}
@@ -37,7 +42,8 @@ function getScopeLabel(db: AppDatabase, policy: BudgetPolicyRecord): string {
try {
const row = db.queryAll<{ name: string }>("select name from projects where id = ?", policy.scopeId);
return row.length > 0 ? `Project "${row[0].name}"` : `Project ${policy.scopeId.slice(0, 8)}`;
} catch {
} catch (err) {
logger.warn(`[budget] project name lookup failed for scope ${policy.scopeId}:`, err instanceof Error ? err.message : String(err));
return `Project ${policy.scopeId.slice(0, 8)}`;
}
}
@@ -106,7 +112,7 @@ export function createBudgetEnforcementService({
const spent = db.getSpentInWindow(policy);
const utilization = spent / policy.amount;
const warnThreshold = policy.warnPercent / 100;
const scopeLabel = getScopeLabel(db, policy);
const scopeLabel = getScopeLabel(db, policy, logger);
if (utilization >= warnThreshold) {
db.upsertBudgetIncident(policy, "warn_threshold", spent);
+3 -2
View File
@@ -4,6 +4,7 @@ import { homedir, tmpdir } from "node:os";
import { join } from "node:path";
import { randomBytes } from "node:crypto";
import { spawnSync } from "node:child_process";
import log from "electron-log/main.js";
import type { ConnectorId } from "@shared/types";
function quoteShell(value: string) {
@@ -79,8 +80,8 @@ function spawnTerminalWithScript(script: string, _title: string, _cwd: string):
});
child.unref();
return true;
} catch {
// Try next terminal candidate.
} catch (err) {
log.warn(`[connector-auth] Failed to spawn terminal ${binary}; trying next candidate:`, err instanceof Error ? err.message : String(err));
}
}
+3 -2
View File
@@ -1,4 +1,5 @@
import type { DatabaseSync, SQLInputValue } from "node:sqlite";
import log from "electron-log/main.js";
import type { AgentRecord, AgentRoleLevel, ConnectorId } from "@shared/types";
import {
type ReadAll,
@@ -125,8 +126,8 @@ export function buildAgentCrudMethods(
configBefore: beforeConfig,
configAfter: afterConfig,
});
} catch {
/* Non-fatal: revision recording should never block agent save */
} catch (err) {
log.warn(`[database/agents] recordConfigRevision failed for agent ${id}:`, err instanceof Error ? err.message : String(err));
}
}
}
+3 -2
View File
@@ -2,6 +2,7 @@ import { chmodSync, existsSync, mkdirSync } from "node:fs";
import { copyFile, cp, rm } from "node:fs/promises";
import { join } from "node:path";
import { DatabaseSync, backup as backupDatabase } from "node:sqlite";
import log from "electron-log/main.js";
async function copyIfExists(source: string, destination: string) {
if (!existsSync(source)) {
@@ -107,8 +108,8 @@ export function buildBackupMethods(
try {
deps.setDb(deps.openConnection());
deps.init();
} catch {
// Reopen failed. Caller sees original + recovery messages.
} catch (reopenError) {
log.error(`[database/backup] Reopen failed after restore/recovery failure:`, reopenError instanceof Error ? reopenError.message : String(reopenError));
}
throw new Error(
`Restore failed: ${message}. Recovery also failed: ${recoveryMessage}. Previous profile is at ${recoveryDir}.`,
+5 -2
View File
@@ -1,4 +1,5 @@
import type { DatabaseSync } from "node:sqlite";
import log from "electron-log/main.js";
import type { ConnectorId, ConnectorRecord } from "@shared/types";
import type { ReadAll } from "./helpers";
import { nowIso } from "./helpers";
@@ -30,7 +31,8 @@ export function buildConnectorMethods(
let definition;
try {
definition = getConnectorDefinition(row.id as ConnectorId);
} catch {
} catch (err) {
log.warn(`[database/connectors] Unknown connector id ${row.id}; skipping row:`, err instanceof Error ? err.message : String(err));
continue;
}
result.push({
@@ -110,7 +112,8 @@ export function buildConnectorMethods(
let definition;
try {
definition = getConnectorDefinition(row.id as ConnectorId);
} catch {
} catch (err) {
log.warn(`[database/connectors] Unknown connector id ${row.id} in findConnector:`, err instanceof Error ? err.message : String(err));
return null;
}
return {
+5 -2
View File
@@ -1,5 +1,6 @@
import { randomUUID } from "node:crypto";
import type { DatabaseSync, SQLInputValue } from "node:sqlite";
import log from "electron-log/main.js";
import type { AgentMessageRecord, AgentRecord } from "@shared/types";
import {
BOARD_READER_ID,
@@ -139,7 +140,8 @@ export function buildMessageMethods(
if (agent.companyId === companyId) {
department = agent.department;
}
} catch {
} catch (err) {
log.warn(`[database/messages] getAgent failed for ${opts.agentId}; department filter disabled:`, err instanceof Error ? err.message : String(err));
department = null;
}
@@ -277,7 +279,8 @@ export function buildMessageMethods(
if (agent.companyId === companyId) {
department = agent.department;
}
} catch {
} catch (err) {
log.warn(`[database/messages] getAgent failed for ${agentId} in listAgentInboxUnread; department filter disabled:`, err instanceof Error ? err.message : String(err));
department = null;
}
+3 -1
View File
@@ -3,6 +3,7 @@ import { writeFileSync, readFileSync, existsSync, statSync, openSync, readSync,
import { appendFile } from "node:fs/promises";
import { join } from "node:path";
import type { DatabaseSync, SQLInputValue } from "node:sqlite";
import log from "electron-log/main.js";
import type { ConnectorId, RunLogChunk, RunRecord } from "@shared/types";
import { type ReadAll, type SqliteRow, type Transaction, nowIso, toBoolean } from "./helpers";
@@ -90,7 +91,8 @@ export function buildRunMethods(
if (!run?.logPath) return "";
try {
return readFileSync(run.logPath, "utf8");
} catch {
} catch (err) {
log.warn(`[database/runs] readFileSync failed for run ${runId} log ${run.logPath}:`, err instanceof Error ? err.message : String(err));
return "";
}
},
+5 -4
View File
@@ -12,6 +12,7 @@ import {
session,
shell,
} from "electron";
import log from "electron-log/main.js";
import { attachWindowStatePersistence, loadWindowState } from "./window-state";
import type { NotificationOptions } from "./notification-service";
@@ -96,8 +97,8 @@ export function createDesktopShellWindow(deps: DesktopShellWindowDependencies) {
let activeRunCount = 0;
try {
activeRunCount = getActiveRunCount();
} catch {
// Cannot determine active runs — proceed with immediate quit as fail-safe
} catch (err) {
log.warn(`[desktop-shell-window] getActiveRunCount failed; quitting as fail-safe:`, err instanceof Error ? err.message : String(err));
onRequestQuit();
app.quit();
return;
@@ -150,8 +151,8 @@ export function createDesktopShellWindow(deps: DesktopShellWindowDependencies) {
}
}
// Cancel: do nothing
} catch {
// Dialog failed (e.g. window destroyed mid-flight) — quit as fail-safe
} catch (err) {
log.warn(`[desktop-shell-window] Quit confirmation dialog failed; quitting as fail-safe:`, err instanceof Error ? err.message : String(err));
onRequestQuit();
app.quit();
} finally {
+7 -3
View File
@@ -1,3 +1,4 @@
import log from "electron-log/main.js";
import { resolveApprovalDecision } from "./approval-policy";
import type { AppDatabase } from "./database";
@@ -35,7 +36,8 @@ function collectImpactedAgentIdsFromWaitStates(db: AppDatabase, companyId: strin
if (task.assigneeAgentId) {
agentIds.add(task.assigneeAgentId);
}
} catch {
} catch (err) {
log.warn(`[execution-mode-reconciler] getTask failed for ${thread.relatedEntityId}:`, err instanceof Error ? err.message : String(err));
continue;
}
}
@@ -71,7 +73,8 @@ function collectImpactedAgentIdsFromReviewState(db: AppDatabase, companyId: stri
if (task.assigneeAgentId) {
agentIds.add(task.assigneeAgentId);
}
} catch {
} catch (err) {
log.warn(`[execution-mode-reconciler] getTask failed for approval ${approval.relatedTaskId}:`, err instanceof Error ? err.message : String(err));
continue;
}
}
@@ -87,7 +90,8 @@ function resumeAgents(db: AppDatabase, companyId: string, agentIds: Iterable<str
if (!agent.heartbeatEnabled) {
db.setHeartbeat(agent.id, true, agent.heartbeatIntervalSec || 120);
}
} catch {
} catch (err) {
log.warn(`[execution-mode-reconciler] resumeAgents failed for ${agentId}:`, err instanceof Error ? err.message : String(err));
continue;
}
}
+5 -3
View File
@@ -7,6 +7,7 @@ import { app } from "electron";
interface HostEnvironmentDependencies {
logger: {
info: (...args: unknown[]) => void;
warn: (...args: unknown[]) => void;
};
}
@@ -29,7 +30,8 @@ function enrichWindowsPath(logger: HostEnvironmentDependencies["logger"]) {
return readdirSync(pythonBase)
.filter((name) => /^Python\d+$/.test(name))
.map((name) => join(pythonBase, name, "Scripts"));
} catch {
} catch (err) {
logger.warn(`[path] readdirSync failed for ${pythonBase}:`, err instanceof Error ? err.message : String(err));
return [];
}
})();
@@ -94,8 +96,8 @@ export async function prepareHostEnvironment({
logger.info(`[path] Added ${toAdd.length} shell PATH entries.`);
}
}
} catch {
// Non-fatal environment enrichment.
} catch (err) {
logger.warn(`[path] Shell PATH probe failed:`, err instanceof Error ? err.message : String(err));
}
} else {
logger.info("[path] Skipping interactive shell PATH probe; using deterministic PATH setup only.");
+2 -2
View File
@@ -107,8 +107,8 @@ export function registerBrowserHandlers({
const profile = await browserActions.extractProfileFromSession(input.socialAccountId, account.platform);
detectedName = profile.accountName;
detectedDisplay = profile.displayName;
} catch {
// Profile extraction is best-effort; proceed with login status update
} catch (err) {
logger.warn(`[ipc/browser] extractProfileFromSession failed for social account ${input.socialAccountId}:`, err instanceof Error ? err.message : String(err));
}
db.saveSocialAccount({
id: latestAccount.id,
+4 -3
View File
@@ -13,7 +13,7 @@ import type { AppDatabase } from "./database";
interface BudgetHandlerDependencies extends Pick<
RegisterDomainHandlersDependencies,
"registerHandle" | "ensureCompanyExists" | "publishDomainChanged"
"registerHandle" | "ensureCompanyExists" | "publishDomainChanged" | "logger"
> {
budgetService: ReturnType<typeof createBudgetEnforcementService> | null;
db: AppDatabase;
@@ -25,6 +25,7 @@ export function registerBudgetHandlers({
publishDomainChanged,
budgetService,
db,
logger,
}: BudgetHandlerDependencies) {
// ---- Save (create/update) a budget policy ----
registerHandle(IPC_CHANNELS.saveBudgetPolicy, async (_event, payload) => {
@@ -36,8 +37,8 @@ export function registerBudgetHandlers({
if (budgetService) {
try {
budgetService.checkAutoResolve(parsed.companyId, id);
} catch {
// Best effort -- service may not be ready
} catch (err) {
logger.warn(`[ipc/budget] checkAutoResolve failed for policy ${id} in company ${parsed.companyId}:`, err instanceof Error ? err.message : String(err));
}
}
publishDomainChanged();
+6 -2
View File
@@ -32,6 +32,7 @@ export function registerContentOperationsHandlers({
publishDomainChanged,
wakeAgentIfPossible,
dispatchAutomation,
logger,
}: Pick<
RegisterDomainHandlersDependencies,
| "registerHandle"
@@ -41,6 +42,7 @@ export function registerContentOperationsHandlers({
| "publishDomainChanged"
| "wakeAgentIfPossible"
| "dispatchAutomation"
| "logger"
>) {
registerHandle(IPC_CHANNELS.saveMeeting, async (_event, payload) => {
const parsed = meetingInputSchema.parse(payload);
@@ -52,7 +54,8 @@ export function registerContentOperationsHandlers({
participantAgentIds = Array.isArray(parsedParticipantIds)
? parsedParticipantIds.filter((value): value is string => typeof value === "string")
: [];
} catch {
} catch (err) {
logger.warn(`[ipc/content] Meeting participants JSON parse failed:`, err instanceof Error ? err.message : String(err));
return fail("INVALID_PARTICIPANTS", "Meeting participants must be valid JSON.");
}
const invalidReference = findInvalidReference(parsed.companyId, [
@@ -163,7 +166,8 @@ export function registerContentOperationsHandlers({
if (parsed.id) {
try {
previousSprintStatus = db.listSnapshot().sprints.find((s: { id: string }) => s.id === parsed.id)?.status ?? null;
} catch {
} catch (err) {
logger.warn(`[ipc/content] listSnapshot failed while resolving sprint ${parsed.id} previous status:`, err instanceof Error ? err.message : String(err));
previousSprintStatus = null;
}
}
+4 -1
View File
@@ -18,6 +18,7 @@ export function registerCoreAgentGraphHandlers({
findInvalidReference,
publishDomainChanged,
handleApprovalResolved,
logger,
}: Pick<
RegisterCoreHandlersDependencies,
| "registerHandle"
@@ -26,6 +27,7 @@ export function registerCoreAgentGraphHandlers({
| "findInvalidReference"
| "publishDomainChanged"
| "handleApprovalResolved"
| "logger"
>) {
registerHandle(IPC_CHANNELS.saveWorkspace, async (_event, payload) => {
const parsed = workspaceInputSchema.parse(payload);
@@ -182,7 +184,8 @@ export function registerCoreAgentGraphHandlers({
? (() => {
try {
return db.getAgent(resolvedReportsTo).name;
} catch {
} catch (err) {
logger.warn(`[ipc/agent-graph] getAgent failed for manager ${resolvedReportsTo}:`, err instanceof Error ? err.message : String(err));
return resolvedReportsTo;
}
})()
+4 -2
View File
@@ -32,6 +32,7 @@ export function registerCoreOperationHandlers({
queueTaskRun,
getWorkerProcess,
notify,
logger,
}: Pick<
RegisterCoreHandlersDependencies,
| "registerHandle"
@@ -46,6 +47,7 @@ export function registerCoreOperationHandlers({
| "queueTaskRun"
| "getWorkerProcess"
| "notify"
| "logger"
>) {
registerHandle(IPC_CHANNELS.requestApproval, async (_event, payload) => {
const parsed = approvalInputSchema.parse(payload);
@@ -201,8 +203,8 @@ export function registerCoreOperationHandlers({
taskId: parsed.taskId,
agentId: parsed.authorAgentId ?? undefined,
});
} catch {
// Task may have been deleted between comment creation and wake evaluation.
} catch (err) {
logger.warn(`[ipc/core-operations] Post-comment wake/dispatch failed for task ${parsed.taskId}:`, err instanceof Error ? err.message : String(err));
}
return ok(comment.id);
});
+14 -6
View File
@@ -20,6 +20,7 @@ export function registerCoreReadHandlers({
resetAfterRestore,
getApiPort,
listConnectorModels,
logger,
}: Pick<
RegisterCoreHandlersDependencies,
| "registerHandle"
@@ -30,6 +31,7 @@ export function registerCoreReadHandlers({
| "resetAfterRestore"
| "getApiPort"
| "listConnectorModels"
| "logger"
>) {
registerHandle(IPC_CHANNELS.loadSnapshot, () => ok(db.listRendererSnapshot()));
@@ -109,7 +111,8 @@ export function registerCoreReadHandlers({
try {
const parsed = connectorInputSchema.shape.id.parse(connectorId);
return ok(await listConnectorModels(parsed));
} catch {
} catch (err) {
logger.warn(`[ipc] listConnectorModels failed for connector ${String(connectorId)}:`, err instanceof Error ? err.message : String(err));
return ok([]);
}
});
@@ -118,7 +121,8 @@ export function registerCoreReadHandlers({
try {
const parsed = connectorInputSchema.shape.id.parse(connectorId);
return ok(db.getDisabledModels(parsed));
} catch {
} catch (err) {
logger.warn(`[ipc] getConnectorDisabledModels failed for connector ${String(connectorId)}:`, err instanceof Error ? err.message : String(err));
return ok([]);
}
});
@@ -180,7 +184,8 @@ export function registerCoreReadHandlers({
const task = db.getTask(parsed.id);
if (task.companyId !== parsed.companyId) return fail("TASK_NOT_FOUND", "Task not found in current company.");
return ok(task);
} catch {
} catch (err) {
logger.warn(`[ipc] getTask failed for id ${parsed.id}:`, err instanceof Error ? err.message : String(err));
return fail("TASK_NOT_FOUND", "Task not found.");
}
});
@@ -193,7 +198,8 @@ export function registerCoreReadHandlers({
const agent = db.getAgent(parsed.id);
if (agent.companyId !== parsed.companyId) return fail("AGENT_NOT_FOUND", "Agent not found in current company.");
return ok(agent);
} catch {
} catch (err) {
logger.warn(`[ipc] getAgent failed for id ${parsed.id}:`, err instanceof Error ? err.message : String(err));
return fail("AGENT_NOT_FOUND", "Agent not found.");
}
});
@@ -206,7 +212,8 @@ export function registerCoreReadHandlers({
const run = db.getRun(parsed.id);
if (run.companyId !== parsed.companyId) return fail("RUN_NOT_FOUND", "Run not found in current company.");
return ok(run);
} catch {
} catch (err) {
logger.warn(`[ipc] getRun failed for id ${parsed.id}:`, err instanceof Error ? err.message : String(err));
return fail("RUN_NOT_FOUND", "Run not found.");
}
});
@@ -219,7 +226,8 @@ export function registerCoreReadHandlers({
const approval = db.getApprovalRecord(parsed.id);
if (approval.companyId !== parsed.companyId) return fail("APPROVAL_NOT_FOUND", "Approval not found in current company.");
return ok(approval);
} catch {
} catch (err) {
logger.warn(`[ipc] getApprovalRecord failed for id ${parsed.id}:`, err instanceof Error ? err.message : String(err));
return fail("APPROVAL_NOT_FOUND", "Approval not found.");
}
});
+2 -1
View File
@@ -118,7 +118,8 @@ export function registerCoreTaskGraphHandlers({
const existingTask = db.getTask(taskId);
previousStatus = existingTask.status;
previousAssigneeAgentId = existingTask.assigneeAgentId;
} catch {
} catch (err) {
logger.warn(`[ipc/task-graph] getTask failed for ${taskId}; treating as new task:`, err instanceof Error ? err.message : String(err));
previousStatus = null;
previousAssigneeAgentId = null;
}
+1
View File
@@ -25,6 +25,7 @@ export function registerCoreWorkGraphHandlers(
findInvalidReference: deps.findInvalidReference,
publishDomainChanged: deps.publishDomainChanged,
handleApprovalResolved: deps.handleApprovalResolved,
logger: deps.logger,
});
registerCoreTaskGraphHandlers({
registerHandle: deps.registerHandle,
+1
View File
@@ -30,6 +30,7 @@ export interface RegisterDomainHandlersDependencies {
) => void;
db: AppDatabase;
logger: {
warn: (...args: unknown[]) => void;
error: (...args: unknown[]) => void;
};
ensureCompanyExists: (companyId: string) => DesktopResult<never> | null;
+2
View File
@@ -21,6 +21,7 @@ export function registerDomainHandlers(dependencies: RegisterDomainHandlersDepen
publishDomainChanged: dependencies.publishDomainChanged,
skillService: dependencies.skillService,
db: dependencies.db,
logger: dependencies.logger,
});
registerBudgetHandlers({
registerHandle: dependencies.registerHandle,
@@ -28,5 +29,6 @@ export function registerDomainHandlers(dependencies: RegisterDomainHandlersDepen
publishDomainChanged: dependencies.publishDomainChanged,
budgetService: dependencies.budgetService,
db: dependencies.db,
logger: dependencies.logger,
});
}
+4 -2
View File
@@ -19,6 +19,7 @@ export function registerRecoveryHandlers({
ensureCompanyExists,
publishDomainChanged,
wakeAgentIfPossible,
logger,
}: Pick<
RegisterDomainHandlersDependencies,
| "registerHandle"
@@ -26,6 +27,7 @@ export function registerRecoveryHandlers({
| "ensureCompanyExists"
| "publishDomainChanged"
| "wakeAgentIfPossible"
| "logger"
>) {
registerHandle(IPC_CHANNELS.recoveryRetry, async (_event, payload) => {
const parsed = recoveryRetrySchema.parse(payload);
@@ -37,8 +39,8 @@ export function registerRecoveryHandlers({
db.retryInterruptedTask(parsed.taskId, parsed.agentId, parsed.companyId);
try {
wakeAgentIfPossible(parsed.agentId, parsed.companyId, "retry_interrupted");
} catch {
// Best effort -- agent may have been deleted
} catch (err) {
logger.warn(`[ipc/recovery] wakeAgentIfPossible failed for agent ${parsed.agentId}:`, err instanceof Error ? err.message : String(err));
}
publishDomainChanged();
return ok(null);
+4 -3
View File
@@ -16,7 +16,7 @@ import type { AppDatabase } from "./database";
interface SkillHandlerDependencies extends Pick<
RegisterDomainHandlersDependencies,
"registerHandle" | "ensureCompanyExists" | "publishDomainChanged"
"registerHandle" | "ensureCompanyExists" | "publishDomainChanged" | "logger"
> {
skillService: ReturnType<typeof createSkillService> | null;
db: AppDatabase;
@@ -28,6 +28,7 @@ export function registerSkillHandlers({
publishDomainChanged,
skillService,
db,
logger,
}: SkillHandlerDependencies) {
function requireSkillService():
| { service: NonNullable<typeof skillService>; error?: undefined }
@@ -64,8 +65,8 @@ export function registerSkillHandlers({
// Scan workspace .agents/skills/ directories for new skills before listing
try {
await svc.scanWorkspaceSkills(parsed.companyId);
} catch {
// Non-fatal: scan failure should not block listing existing skills
} catch (err) {
logger.warn(`[ipc/skills] scanWorkspaceSkills failed for company ${parsed.companyId}:`, err instanceof Error ? err.message : String(err));
}
return ok(svc.listForCompany(parsed.companyId));
});
+5 -2
View File
@@ -8,6 +8,7 @@
* other provider we delegate to pi-ai's `getModel` / `getModels`.
*/
import log from "electron-log/main.js";
import { getModel, getModels, type Api, type Model } from "@mariozechner/pi-ai";
const FIREWORKS_BASE_URL = "https://api.fireworks.ai/inference";
@@ -54,7 +55,8 @@ export function resolveModel(provider: string, modelId: string): Model<Api> | nu
}
try {
return getModel(provider as never, modelId as never) ?? null;
} catch {
} catch (err) {
log.warn(`[model-resolver] getModel failed for ${provider}/${modelId}:`, err instanceof Error ? err.message : String(err));
return null;
}
}
@@ -69,7 +71,8 @@ export function resolveModels(provider: string): Array<{ id: string; name: strin
try {
const models = getModels(provider as never) ?? [];
return models.map((model: { id: string; name?: string }) => ({ id: model.id, name: model.name ?? model.id }));
} catch {
} catch (err) {
log.warn(`[model-resolver] getModels failed for ${provider}:`, err instanceof Error ? err.message : String(err));
return [];
}
}
+2 -1
View File
@@ -72,7 +72,8 @@ export function createLifecycle(deps: LifecycleDeps) {
return db.listSnapshot().runs
.filter((r) => r.status === "running" || r.status === "queued")
.map((r) => r.id);
} catch {
} catch (err) {
logger.warn("[lifecycle] listActiveRunIds failed:", err instanceof Error ? err.message : String(err));
return [];
}
}
+2 -1
View File
@@ -68,7 +68,8 @@ export function createSessionManagementService({ db, logger }: SessionManagement
let task: { title: string; description: string } | null = null;
try {
task = db.getTask(taskId);
} catch {
} catch (err) {
logger.warn(`[session-management] getTask failed for ${taskId} during handoff generation:`, err instanceof Error ? err.message : String(err));
task = null;
}
+10 -6
View File
@@ -272,8 +272,8 @@ export function createSkillImporter({ db, profileDir: _profileDir, logger, skill
const pathParts = urlObj.pathname.split("/").filter(Boolean);
const lastPart = pathParts[pathParts.length - 1] ?? "";
urlSlug = normalizeSkillSlug(lastPart.replace(/\.md$/i, ""));
} catch {
// ignore URL parsing errors
} catch (err) {
logger.warn(`[skill-import] Unable to parse skill URL for slug derivation (${url}):`, err instanceof Error ? err.message : String(err));
}
const rawSlug =
normalizeSkillSlug(parsed.frontmatter.slug as string | undefined) ??
@@ -340,7 +340,8 @@ export function createSkillImporter({ db, profileDir: _profileDir, logger, skill
let realDir: string;
try {
realDir = realpathSync(dir);
} catch {
} catch (err) {
logger.warn(`[skill-import] realpathSync failed for ${dir}:`, err instanceof Error ? err.message : String(err));
return;
}
if (visitedRealPaths.has(realDir)) {
@@ -351,7 +352,8 @@ export function createSkillImporter({ db, profileDir: _profileDir, logger, skill
let entries: Dirent[];
try {
entries = readdirSync(dir, { withFileTypes: true });
} catch {
} catch (err) {
logger.warn(`[skill-import] readdirSync failed for ${dir}:`, err instanceof Error ? err.message : String(err));
return;
}
for (const entry of entries) {
@@ -395,7 +397,8 @@ export function createSkillImporter({ db, profileDir: _profileDir, logger, skill
let realDir: string;
try {
realDir = realpathSync(dir);
} catch {
} catch (err) {
logger.warn(`[skill-import] realpathSync failed for ${dir}:`, err instanceof Error ? err.message : String(err));
return;
}
if (visitedRealPaths.has(realDir)) {
@@ -406,7 +409,8 @@ export function createSkillImporter({ db, profileDir: _profileDir, logger, skill
let entries: Dirent[];
try {
entries = readdirSync(dir, { withFileTypes: true });
} catch {
} catch (err) {
logger.warn(`[skill-import] readdirSync failed for ${dir}:`, err instanceof Error ? err.message : String(err));
return;
}
for (const entry of entries) {
+5 -4
View File
@@ -5,6 +5,7 @@
* minimum description quality, acceptance criteria, and capability matching.
*/
import log from "electron-log/main.js";
import type { DesktopEvent } from "@shared/types";
import type { AppDatabase } from "@main/database";
import type { NotificationOptions } from "@main/notification-service";
@@ -279,8 +280,8 @@ export function sweepStaleReviews(
detail: "Auto-approved by review timeout escalation",
actor: "system",
});
} catch {
// Already decided (race condition with manual approval) -- skip silently (Pitfall 3)
} catch (err) {
log.warn(`[task-quality-gates] resolveApprovalDecision failed for approval ${pendingApproval.id} (likely already decided):`, err instanceof Error ? err.message : String(err));
}
}
@@ -288,8 +289,8 @@ export function sweepStaleReviews(
try {
deps.db.advanceTaskStatus(task.id, "done", "Auto-advanced: review timeout exceeded 30 minutes.");
deps.handleTaskStatusChange(task.id, companyId, "in_review", "done");
} catch {
// Task may have been updated by manual action
} catch (err) {
log.warn(`[task-quality-gates] advanceTaskStatus failed for task ${task.id}:`, err instanceof Error ? err.message : String(err));
}
escalatedCount++;
+9 -5
View File
@@ -139,7 +139,7 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh
});
notifyThreadSubscribers(db, thread.id, thread.title, approvalReason, notify);
}
announceApprovalToBoard(db, approval, { emitEvent, publishDomainChanged });
announceApprovalToBoard(db, approval, { emitEvent, publishDomainChanged, logger });
} catch (err) {
logger.error(`[thread-integration] onApprovalCreated failed: ${err instanceof Error ? err.message : String(err)}`);
}
@@ -305,7 +305,8 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh
try {
const task = db.getTask(thread.relatedEntityId);
if (task.assigneeAgentId !== toAgentId) continue;
} catch {
} catch (err) {
logger.warn(`[thread-integration] getTask failed for thread ${ws.threadId}:`, err instanceof Error ? err.message : String(err));
continue;
}
}
@@ -321,7 +322,8 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh
try {
const waitStates = db.listThreadWaitStates(companyId);
return waitStates.length > 0;
} catch {
} catch (err) {
logger.warn(`[thread-integration] hasUnresolvedWaitStates query failed for company ${companyId}:`, err instanceof Error ? err.message : String(err));
return false;
}
},
@@ -343,7 +345,8 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh
companyId, agentId,
);
return rows.length > 0;
} catch {
} catch (err) {
logger.warn(`[thread-integration] hasUnresolvedWaitStatesForAgent query failed (company=${companyId} agent=${agentId}):`, err instanceof Error ? err.message : String(err));
return false;
}
},
@@ -355,7 +358,8 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh
countUnresolvedWaitStates(companyId: string): number {
try {
return db.listThreadWaitStates(companyId).length;
} catch {
} catch (err) {
logger.warn(`[thread-integration] countUnresolvedWaitStates failed for company ${companyId}:`, err instanceof Error ? err.message : String(err));
return 0;
}
},
@@ -5,7 +5,7 @@ import type { ThreadType } from "@shared/types";
export interface ThreadIntegrationDeps {
db: AppDatabase;
logger: { info: (...args: unknown[]) => void; error: (...args: unknown[]) => void };
logger: { info: (...args: unknown[]) => void; warn: (...args: unknown[]) => void; error: (...args: unknown[]) => void };
emitEvent?: (event: import("@shared/types").DesktopEvent) => void;
publishDomainChanged?: () => void;
notify?: (options: { title: string; body: string }) => void;
@@ -64,6 +64,7 @@ function resolveApprovalAnnouncementChannel(
companyId: string,
taskId: string | null,
requestedByAgentId: string | null,
logger: ThreadIntegrationDeps["logger"],
): { channel: "department" | "project" | "company"; channelTargetId: string | null } | null {
if (requestedByAgentId) {
try {
@@ -71,8 +72,8 @@ function resolveApprovalAnnouncementChannel(
if (requester.companyId === companyId && requester.department) {
return { channel: "department", channelTargetId: requester.department };
}
} catch {
// Fall through to task/project/company targeting.
} catch (err) {
logger.warn(`[thread-helpers] getAgent failed for requester ${requestedByAgentId}; falling through to task targeting:`, err instanceof Error ? err.message : String(err));
}
}
@@ -82,8 +83,8 @@ function resolveApprovalAnnouncementChannel(
if (task.companyId === companyId && task.projectId) {
return { channel: "project", channelTargetId: task.projectId };
}
} catch {
// Fall through to company-wide routing.
} catch (err) {
logger.warn(`[thread-helpers] getTask failed for task ${taskId}; falling through to company routing:`, err instanceof Error ? err.message : String(err));
}
}
@@ -100,7 +101,7 @@ export function announceApprovalToBoard(
payloadSummary: string;
impactSummary: string;
},
deps: Pick<ThreadIntegrationDeps, "emitEvent" | "publishDomainChanged">,
deps: Pick<ThreadIntegrationDeps, "emitEvent" | "publishDomainChanged" | "logger">,
): void {
if (!approval.requestedByAgentId || !deps.emitEvent || !deps.publishDomainChanged) return;
@@ -109,6 +110,7 @@ export function announceApprovalToBoard(
approval.companyId,
approval.relatedTaskId,
approval.requestedByAgentId,
deps.logger,
);
if (!target) return;
+1 -1
View File
@@ -95,7 +95,7 @@ describe("company mode switching", () => {
const threadIntegration = createThreadIntegration({
db,
logger: { info: () => {}, error: () => {} },
logger: { info: () => {}, warn: () => {}, error: () => {} },
});
threadIntegration.onTaskCreated(taskId, companyId);
@@ -77,6 +77,7 @@ describe("ipc-core-agent-graph-handlers model enforcement", () => {
findInvalidReference: () => null,
publishDomainChanged: vi.fn(),
handleApprovalResolved: vi.fn(),
logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() },
});
const saveAgent = handlers.get(IPC_CHANNELS.saveAgent);
+1 -1
View File
@@ -174,7 +174,7 @@ describe("message dispatch", () => {
const { companyId, ctoId, taskId } = createScaffold(db);
const threadIntegration = createThreadIntegration({
db,
logger: { info: () => {}, error: () => {} },
logger: { info: () => {}, warn: () => {}, error: () => {} },
});
threadIntegration.onTaskCreated(taskId, companyId);