Field notes
Cron & Ignore System Audit
Full audit of CronScheduler and ignore-gate MCP tools before refactor.
Cron Execution — Silent Failure#
Root cause chain:
CronScheduler.#execute()creates synthetic session:team_id="cron",channel_id="cron:${job.id}".src/gateway/slack/cron-scheduler.ts:48-52- No route registered in adapter's
routesMap.src/gateway/slack/adapter.ts:119 - Agent event handler:
routes.get(e.sessionId)returns undefined → early return.adapter.ts:158-159 - All agent events (
done,error,toolCall) silently dropped for cron sessions. - MCP resolver returns undefined for no-route → cron session has zero Slack tools.
adapter.ts:142-154 - Agent can't call
mcp__slaude_slack__reply→ response vanishes. postResult()dead code — zero call sites.cron-scheduler.ts:71next_runupdates with "dispatched" status before agent finishes.cron-scheduler.ts:61
Impact: Cron jobs fire, agent runs, but zero Slack output. DB looks healthy (next_run advances) but user sees nothing.
Cron Session Reuse & Race Condition#
ensureSessionuses deterministic thread key → same session row across all runs.src/agent/manager.ts:112-126- Working dir shared across runs → files from run 1 persist for run 2.
#runningSet cleared infinallyblock aftersendMessageresolves, not after agent completes.cron-scheduler.ts:46-66- If agent turn duration > cron interval, overlapping execution possible. Second run flushes prior live turn or starts overlapping session.
remove_cron_job MCP Tool Bugs#
- Description lies: says "8-char prefix" accepted, but
findById()does exact UUID match. Prefix → "Job not found" error.db/cron-jobs.ts:34 - Soft delete only:
deactivate()setsactive = 0. Row stays forever. Tool says "deactivated" not "deleted".
ignore_thread / unignore_thread MCP Tool Bugs#
- No auth check:
ignore_thread/unignore_threadhave zero permission validation. Any agent can ignore any thread. Compare: slash command/ignorerequires manager/approver.mcp-tools.ts:499-538vsadapter.ts:372-376 - Duration parsing bugs:
- Invalid suffix silently accepted:
"5x"→ parsed as 5 minutes. - Decimal silently truncated:
"1.5h"→ parsed as 1 hour. - No max duration limit.
- Invalid suffix silently accepted:
- DM broken:
threadTs = event.tsfor regular DM messages (noevent.thread_ts) → unique per message.ignore_threadonly ignores that exact message; next DM flows through.adapter.ts:261 - Silent no-op:
unignore_threadreturns "removed" even if no active ignore existed. - createdBy hardcoded:
"agent"instead of actual user ID.mcp-tools.ts:526 - Missing tools: No
ignore_user/unignore_userMCP tools. DB supports user-level ignore; only slash commands can create them.
Design Decision: Cron as Regular Thread Session#
Current model: Synthetic isolated session. Cron and human conversation are separate worlds.
New model: One thread = one session. Cron uses real Slack thread key.
/cron-addin thread #123 → job stores real(team_id, channel_id, thread_ts)- All cron runs resume same session, post to same thread
- Humans reply in same thread → same session, shared history
- Cron skips if session live (humans get priority, no interruption)
Benefits:
- Natural human engagement — people can refine, query, adjust cron tasks
- No synthetic session complexity
- Single conversation history
Tradeoff: Cron and human are mutually exclusive on same thread. If humans actively chatting, cron waits next interval.
Migration:
cron_jobstable: store realslack_team_id,slack_channel_id,slack_thread_ts- Remove synthetic key usage from
CronScheduler postResult()becomes unnecessary — agent uses normal MCP reply- Adapter registers route for cron sessions same as human messages
Files to Touch#
src/db/schema.ts— add real Slack keys tocron_jobssrc/db/cron-jobs.ts— read/write real keys, prefix lookup for removesrc/gateway/slack/cron-scheduler.ts— use real keys, skip-if-live, remove postResultsrc/gateway/slack/adapter.ts— wire cron routes into normal flowsrc/gateway/slack/mcp-tools.ts— auth checks, duration validation, ignore_user toolssrc/gateway/slack/ignore-gate.ts— DM handling (if fixing)