Skip to content

Resume posts internal error after earlier reply #1114

Description

@dcramer

Summary

A resumed Slack turn can still post the generic internal-error fallback after an earlier accepted assistant reply in the same turn. The symptom is real. The original root-cause framing and suggested !assistantMessageDelivered gate are not.

Verified behavior

  • /api/internal/agent/continue still reaches runPausedTurnresumeSlackTurn in packages/junior/src/chat/runtime/slack-resume.ts.
  • On a completed failed result, resume always delivers the sanitized fallback:
if (reply.diagnostics.outcome !== "success") {
  await deliverAssistantMessage(reply.text);
}
  • assistantMessageDelivered is seeded from turnHasReply(...), but only affects lifecycle success vs no_reply. It does not gate fallback delivery.
  • Function naming in the original report is stale (resumeAwaitingSlackContinuation is now resumeSlackTurn); the path is still live.

Current completion contract

This is not just a missed boolean check.

  • Every completed tool-free assistant message is a normal reply. Status UI owns progress; there is no progress-vs-final reply type.
  • Success requires a terminal answer or explicit no-reply, not “any earlier text.”
  • buildTurnResult() scores only the current slice’s newMessages, and only the terminal assistant tail after the last non-assistant message.
  • PR feat(agent): Deliver completed assistant messages #969 and current tests intentionally keep failure-after-progress visible:
    • PR body: failed runs append the safe failure response even when an earlier progress message was already visible
    • packages/junior/tests/integration/slack/finalized-reply-behavior.test.ts: posts a fallback after progress when the run fails
    • packages/junior/tests/unit/turn-result.test.ts: progress + tools with no terminal answer ⇒ execution_failure

So this production shape is consistent with current rules:

  1. earlier slice delivers a detailed answer
  2. continue slice runs tools successfully with 0 assistant messages
  3. diagnostics become execution_failure / empty assistant turn
  4. resume posts “I ran into an internal error…”

Why the original suggested fix is wrong

Gating fallback with:

outcome !== "success" && !assistantMessageDelivered

would suppress the intentional failure-after-progress case:

  1. model posts “Checking that…”
  2. assistantMessageDelivered = true
  3. run ends empty/failed
  4. fallback would be suppressed

It would also turn incomplete work after an intermediate reply into silent operator-only failure.

Policy split

Path If turn already has a reply User-facing failure
Stranded running recovery (paused-turn.ts) turnHasReply → complete as success no
Failed completed resume/result (slack-resume / reply-executor) ignore prior reply yes, post fallback
Intentional progress-then-fail (tests + #969) prior reply is progress yes, post fallback

The system is not uniformly “always show internal error after any prior reply,” and not uniformly “never show it.”

Unknowns

  • Without the original continue event payload, medium confidence whether the production “detailed answer” was terminal final content or intermediate text under today’s rules.
  • Whether stranded turnHasReply → success and failed-result fallback delivery should share one policy.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions