Severity: MEDIUM
Description
The scheduler initialization in src/scheduler.ts:89-95 and src/server.ts:177 has a potential race condition where cron tasks may fire before the database and tools are fully initialized.
Location
File: src/server.ts
Lines: 175-178
Problematic Code
export async function startServer(): Promise<void> {
const config = loadConfig();
getDb(); // initialize database on startup
await initTools();
initScheduler(); // <-- Could fire tasks immediately
const server = serve(...);
}
File: src/scheduler.ts
Lines: 52-66
const task = cron.schedule(expr, async () => {
try {
console.log(`Cron fired for agent "${agentName}" (cron: ${expr})`);
const { createRun } = await import("./traces.ts");
const { runAgent } = await import("./runner.ts");
const runId = createRun(agentName);
await runAgent(agentName, runId, `Scheduled run (cron: ${expr})`);
} catch (err: unknown) {
// ...
}
});
Issues
- Immediate Execution Risk: If a cron expression is "* * * * *" (every minute) and we start at :59 seconds, the task fires within 1 second
- Tools Not Ready: MCP servers may still be connecting when first cron fires
- Dynamic Imports: Using
import() inside the handler is slower and could fail
- No Startup Delay: No grace period after server start before scheduled tasks run
Impact
- First scheduled run could fail with "tools not initialized"
- Race between
initTools() promise and cron task execution
- Confusing error messages on startup
- Flaky behavior that only appears with specific cron timings
Recommendation
Option 1: Add startup delay to scheduler:
export function initScheduler(delayMs: number = 5000): void {
const agents = listAgents();
// Schedule with delay to allow initialization to complete
setTimeout(() => {
for (const agent of agents) {
const exprs = parseCronTriggers(agent.triggers);
if (exprs.length > 0) {
scheduleAgent(agent.name, agent.triggers);
}
}
const count = scheduled.size;
if (count > 0) {
console.log(`Scheduler: ${count} agent(s) with cron triggers`);
}
}, delayMs);
}
Option 2: Make initScheduler async and await tools:
export async function startServer(): Promise<void> {
const config = loadConfig();
getDb();
await initTools();
await new Promise(resolve => setTimeout(resolve, 1000)); // grace period
initScheduler();
const server = serve(...);
}
Option 3: Check readiness in task handler:
const task = cron.schedule(expr, async () => {
// Wait for tools to be ready
const tools = listTools();
if (tools.length === 0) {
console.warn(`Cron task for ${agentName} skipped: tools not initialized yet`);
return;
}
// ... rest of handler
});
Related Issues
- No check for duplicate agent runs (could schedule same agent twice)
updateNextRun called in finally might race with next cron fire
Created by security audit
Severity: MEDIUM
Description
The scheduler initialization in
src/scheduler.ts:89-95andsrc/server.ts:177has a potential race condition where cron tasks may fire before the database and tools are fully initialized.Location
File:
src/server.tsLines: 175-178
Problematic Code
File:
src/scheduler.tsLines: 52-66
Issues
import()inside the handler is slower and could failImpact
initTools()promise and cron task executionRecommendation
Option 1: Add startup delay to scheduler:
Option 2: Make initScheduler async and await tools:
Option 3: Check readiness in task handler:
Related Issues
updateNextRuncalled infinallymight race with next cron fireCreated by security audit