Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/fix-silent-catch-blocks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@colony/hooks": patch
"@colony/foraging": patch
---

Surface silent `catch {}` failures to stderr (rule #9).

Every empty catch in `session-start`, `scanner`, and the foraging MCP tool now either logs a `[colony] <site>: <message>` line to stderr or carries a one-line comment explaining why silence is intentional (fs-stat races, missing-directory guards, best-effort cleanup). Previously a whole session's 43/43 MCP call failures could vanish with no trace.
9 changes: 8 additions & 1 deletion apps/mcp-server/src/tools/foraging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,14 @@ function enrichForagingHits(
const rows = store.storage.getObservations(hits.map((h) => h.id));
const metadataById = new Map<number, Record<string, unknown>>();
for (const row of rows) {
metadataById.set(row.id, parseMeta(row.metadata));
if (!row.metadata) continue;
try {
metadataById.set(row.id, JSON.parse(row.metadata) as Record<string, unknown>);
} catch (err) {
process.stderr.write(
`[colony] enrichForagingHits: observation ${row.id} metadata parse failed: ${(err as Error)?.message ?? err}\n`,
);
}
}
return hits.map((h) => {
const md = metadataById.get(h.id);
Expand Down
18 changes: 14 additions & 4 deletions packages/foraging/src/scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,9 @@ export function scanExamplesFs(opts: ScanFsOptions): ScanFsResult {
let names: string[];
try {
names = readdirSync(examplesDir);
} catch {
} catch (err) {
// examples/ directory may not exist on repos that haven't set up foraging yet.
process.stderr.write(`[colony] scanExamplesFs: ${(err as Error)?.message ?? err}\n`);
return { scanned: [] };
}
names.sort();
Expand All @@ -307,7 +309,9 @@ export function scanExamplesFs(opts: ScanFsOptions): ScanFsResult {
let isDir = false;
try {
isDir = statSync(abs_path).isDirectory();
} catch {}
} catch {
// Tolerant: entry disappeared between readdir and stat (race or dangling symlink); skip it.
}
if (!isDir) continue;

if (example_name === COCOINDEX_EXAMPLE_NAME && isLargeCocoindexExample(abs_path)) {
Expand Down Expand Up @@ -441,7 +445,9 @@ function compactExistingPaths(abs_path: string, paths: readonly string[]): strin
try {
const st = statSync(abs);
out.add(st.isDirectory() ? `${p.replace(/\/$/, '')}/` : p);
} catch {}
} catch {
// Tolerant: path listed in spec does not exist in this repo's working copy; omit it.
}
}
return Array.from(out).sort();
}
Expand All @@ -460,7 +466,9 @@ function computeRufloContentHash(
let st: Stats | null = null;
try {
st = statSync(abs);
} catch {}
} catch {
// Tolerant: filetree path may not exist in all repo variants; skip for hash contribution.
}
if (!st) continue;
hash.update(`${rel}\t${st.size}\n`);
if (st.isFile()) {
Expand All @@ -476,6 +484,7 @@ function fileExists(abs: string): boolean {
try {
return statSync(abs).isFile();
} catch {
// Tolerant: stat failure (ENOENT, EACCES) means file is absent from this working copy.
return false;
}
}
Expand All @@ -484,6 +493,7 @@ function directoryExists(abs: string): boolean {
try {
return statSync(abs).isDirectory();
} catch {
// Tolerant: stat failure (ENOENT, EACCES) means directory is absent from this working copy.
return false;
}
}
Expand Down
36 changes: 26 additions & 10 deletions packages/hooks/src/handlers/session-start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,10 @@ function kickForagingScan(store: MemoryStore, input: HookInput): void {
if (!claimForagingSessionStartScan(settings, cwd)) return;
try {
spawnNodeScript(cli, ['foraging', 'scan', '--cwd', cwd]);
} catch {
} catch (err) {
releaseForagingSessionStartScan(settings, cwd);
// Best-effort. Foraging is not load-bearing for the hook's primary job.
console.error(`[colony] kickForagingScan: ${(err as Error)?.message ?? err}`);
}
}

Expand Down Expand Up @@ -180,7 +181,9 @@ function releaseForagingSessionStartScan(settings: Settings, cwd: string): void
const markerPath = foragingSessionStartMarkerPath(settings, cwd);
try {
if (existsSync(markerPath)) unlinkSync(markerPath);
} catch {}
} catch {
// Tolerant: marker cleanup is best-effort; a stale marker just delays the next scan.
}
}

function foragingSessionStartMarkerPath(settings: Settings, cwd: string): string {
Expand All @@ -194,6 +197,7 @@ function readForagingSessionStartMarker(markerPath: string): number | null {
const value = parsed.last_started_at;
return typeof value === 'number' && Number.isFinite(value) ? value : null;
} catch {
// Tolerant: missing or malformed marker is treated as no prior scan; scan will proceed.
return null;
}
}
Expand Down Expand Up @@ -237,7 +241,8 @@ export function buildReadyClaimNudgePreface(
let plans: ReturnType<typeof listPlans>;
try {
plans = listPlans(store, { repo_root: detected.repo_root, limit: 50 });
} catch {
} catch (err) {
console.error(`[colony] buildReadyClaimNudgePreface: ${(err as Error)?.message ?? err}`);
return '';
}
let unclaimed = 0;
Expand Down Expand Up @@ -276,7 +281,8 @@ export function buildAttentionBudgetSection(
agent,
repo_root: detected.repo_root,
});
} catch {
} catch (err) {
console.error(`[colony] buildAttentionBudgetSection: ${(err as Error)?.message ?? err}`);
return '';
}
const budget = applyAttentionBudget(inbox);
Expand Down Expand Up @@ -378,7 +384,8 @@ export function buildTaskPreface(
repo_root: detected.repo_root,
include_stalled_lanes: false,
}).unread_messages;
} catch {
} catch (err) {
console.error(`[colony] buildTaskPreface unreadMessages: ${(err as Error)?.message ?? err}`);
unreadMessages = [];
}
const others = thread.participants().filter((p) => p.session_id !== input.session_id);
Expand Down Expand Up @@ -506,7 +513,8 @@ export async function buildSuggestionPreface(
let queryEmbedding: Float32Array;
try {
queryEmbedding = await embedder.embed(suggestionQuery(input, detected.branch));
} catch {
} catch (err) {
console.error(`[colony] buildSuggestionPreface embed: ${(err as Error)?.message ?? err}`);
return '';
}

Expand All @@ -518,7 +526,10 @@ export async function buildSuggestionPreface(
exclude_task_ids: [thread.task_id],
min_similarity: thresholds.SIMILARITY_FLOOR,
});
} catch {
} catch (err) {
console.error(
`[colony] buildSuggestionPreface findSimilarTasks: ${(err as Error)?.message ?? err}`,
);
return '';
}
const top = similarTasks[0];
Expand All @@ -527,7 +538,10 @@ export async function buildSuggestionPreface(
let payload: unknown;
try {
payload = core.buildSuggestionPayload(store, similarTasks);
} catch {
} catch (err) {
console.error(
`[colony] buildSuggestionPreface buildSuggestionPayload: ${(err as Error)?.message ?? err}`,
);
return '';
}
if (!isSuggestionPayload(payload)) return '';
Expand Down Expand Up @@ -668,7 +682,8 @@ async function resolveSuggestionEmbedder(store: MemoryStore): Promise<Embedder |
cachedSuggestionEmbedder = await embeddingModule.createEmbedder(store.settings, {
log: () => {},
});
} catch {
} catch (err) {
console.error(`[colony] resolveSuggestionEmbedder: ${(err as Error)?.message ?? err}`);
cachedSuggestionEmbedder = null;
}
return cachedSuggestionEmbedder;
Expand All @@ -689,7 +704,8 @@ async function loadSuggestionCore(): Promise<SuggestionCore | null> {
findSimilarTasks: core.findSimilarTasks,
buildSuggestionPayload: core.buildSuggestionPayload,
};
} catch {
} catch (err) {
console.error(`[colony] loadSuggestionCore: ${(err as Error)?.message ?? err}`);
return null;
}
}
Expand Down
Loading