Skip to content

Triage-escalation Slack replies are dropped after a daemon restart (watcher never rearmed) #227

Description

@kjgbot

Pre-existing on main — not introduced by #225. Verified this function and every function it calls in this report already exist on main; #225 does not touch any of them. Filed separately because it surfaced while auditing #226 (the GitHub-fallback eligibility survey), but it is an independent defect with its own trigger, unrelated to fallback eligibility.

Summary

A human's Slack reply to a triage-escalation question is silently dropped after a daemon restart, because the Slack watcher that would receive it is never rearmed. This is not about GitHub API fallback eligibility — a triage escalation resolved entirely through the normal Relayfile projection has the same problem.

The watcher rearm function only covers two record shapes

#rearmSlackReplyWatchers runs once at startup and re-establishes Slack reply watchers for issues that had one before the process stopped. It iterates three sources, and a pre-dispatch triage escalation is absent from all three:

  1. batch.inFlight — the in-memory dispatch tracker. A triage escalation exists specifically before dispatch (the routing decision needed human input before the issue was ever dispatched), so it was never inserted here.
  2. #state.listConversationSessions, filtered to provider === 'slack' — this branch looks promising (a triage-escalation thread does create a conversation session), but it gates the actual rearm on const record = batch.getIssue(session.issue) and only proceeds if (record && ...). Since the escalation is not in-flight, record is undefined and the rearm is skipped.
  3. #state.listWaitingClarifications — a different durable concept (an agent mid-implementation blocked on a question), not a pre-dispatch triage escalation.

The escalation's decision is durable — persisted via #state.setGithubIssueCommentWatch (kind: 'triage') when the escalation is first posted — but nothing in the rearm function reads that store.

Consequence

  1. Factory dispatches, triage escalates on ambiguous routing, an escalation question is posted and mirrored to Slack.
  2. The daemon restarts before a human replies (deploy, crash, planned restart — any restart).
  3. A human replies on the Slack thread.
  4. No watcher is listening for that thread anymore. The reply is not processed. Nothing surfaces the drop to the operator or the stakeholder — there is no error, just silence.

Suggested direction

Add a fourth branch to #rearmSlackReplyWatchers that iterates the durable comment-watch store, filters to kind === 'triage' entries with a decision and a known Slack thread, and rearms using the same escalationWatchRecord-style construction already used elsewhere for this exact decision shape. Worth checking whether the GitHub-side equivalent (comment polling) has a matching gap or whether it's naturally covered by durable polling instead of an in-memory watcher — the two providers may not have the same defect shape.

Scope note

This was found while doing a systematic call-site audit for #226 (GitHub API fallback eligibility). It is unrelated to that mechanism — the failure here is "no listener is rearmed at all," not "the listener reads the wrong thing." Please don't conflate fixes for the two.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions