Anonymous View
Skip to content

Bug: agent cancel button leaves tool_use blocks without matching tool_result #214

@jakobklippel

Description

@jakobklippel

Summary

The Agent module ships a "Cancel pending tools" button (agent.ui.yaml / chat-agent.ui.yaml) that is currently disabled in code. When enabled, clicking it cancels the running child sub-workflows but does NOT persist tool_result blocks for the cancelled tool_use ids. The next llmTurn then sends Claude an assistant message containing tool_use blocks with no matching user tool_result blocks, and the API returns 400 ("tool_use without matching tool_result"). The widget: line in agent.workflow.ts is commented out as a stop-gap.

Steps to reproduce

  1. Re-enable the widget in agent-module/src/workflows/agent.workflow.ts (uncomment the widget: line around L57) or run the chat-agent variant which still has its widget enabled.
  2. Start an agent run with at least one tool call that delegates to a sub-workflow so the agent enters awaiting_tools.
  3. While in awaiting_tools, click the "Cancel pending tools" button.
  4. Wait for the next llmTurn to fire.

Expected behaviour

Cancel ends the in-flight tool calls cleanly, synthetic tool_result blocks (marked as errors / cancellation) are written to the message log for every pending tool_use id, and the next LLM turn proceeds normally (or the agent terminates) without an API error.

Actual behaviour

cancelPendingTools (agent.workflow.ts:144, chat-agent.workflow.ts:165) cancels children and transitions awaiting_tools → ready directly, skipping the toolsComplete step that writes tool_result blocks to LlmMessageDocument. The next llmTurn ships a history with assistant tool_use blocks but no matching user tool_result message → Anthropic returns 400 "tool_use without matching tool_result".

Contributing factors observed during investigation:

  • The cancelled-children pipeline (WorkflowOrchestrationService.cancelChildrencancelcompleteresume(parent, ..., 'toolResultReceived')) does fire callbacks, but toolResultReceived is declared from: 'awaiting_tools'. By the time those callbacks arrive, the cancel transition has already moved the parent to ready, so the callbacks no longer match and are dropped.
  • Even if they did fire, only toolsComplete flushes tool_result blocks to the document store; the cancel path bypasses it.
  • LlmDelegateResult only retains completed toolResults and a pendingCount — it does not carry the list of pending tool_use ids. The ids only live on state.llmResult.message, which the cancel path does not consume.

Affected area

  • Package(s): @loopstack/agent-module, @loopstack/llm-provider-module, @loopstack/core
  • File(s):
    • loopstack/registry/features/agent-module/src/workflows/agent.workflow.ts (cancelPendingTools at L144, disabled widget comment at L54-57)
    • loopstack/registry/features/agent-module/src/workflows/chat-agent.workflow.ts (cancelPendingTools at L165)
    • loopstack/registry/features/agent-module/src/workflows/agent.ui.yaml
    • loopstack/registry/features/llm-provider-module/src/services/llm-delegate.service.ts (updateToolResult, handleToolCompletion)
    • loopstack/registry/features/llm-provider-module/src/types/llm.types.ts (LlmDelegateResult shape)
    • loopstack/packages/core/src/workflow-processor/services/workflow-orchestration.service.ts (cancelChildren, cancel, complete)

Environment

  • Branch / commit: main
  • Node / npm / OS: TBD

Additional context

The disabled widget comment in agent.workflow.ts:54-56 already documents the user-visible symptom. Re-enabling the button without addressing the persistence gap will reproduce the 400 every time. A fix needs to (a) decide where the synthetic tool_result blocks are constructed (cancel path or a still-running toolsComplete), (b) surface the pending tool_use ids to that code (e.g. via LlmDelegateResult or by reading llmResult.message), and (c) make sure the cancel transition and the cancelled-children callbacks do not race over delegateResult.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingpriority:p2Medium priority

    Type

    No fields configured for Bug.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions