Skip to content

[BUG] [v0.1.0] handle_reject() in actions.rs never sends rejection result back to LLM, stalling the agentic loop #164

@climax-dev-1

Description

@climax-dev-1

Bug Description

When a user rejects a tool execution via the Reject keybinding (handle_reject() in actions.rs), the rejection result is never fed back to the LLM as a pending tool result. This means the agentic loop stalls after a rejection — the LLM never learns the tool was rejected and cannot adapt or try an alternative approach.

Location

src/cortex-tui/src/runner/event_loop/actions.rs, lines 218–245

Root Cause

handle_reject() calls self.app_state.update_tool_result() to update the UI status, but it never calls self.app_state.add_pending_tool_result() to queue the rejection for the next LLM turn. Compare with every other tool completion path in the codebase:

  • handle_tool_completed in tools.rs:407-408 → calls add_pending_tool_result
  • handle_tool_failed in tools.rs:528-529 → calls add_pending_tool_result
  • cancel_question_prompt in mouse.rs:542-547 → calls add_pending_tool_result
  • submit_question_answers in mouse.rs:517-518 → calls add_pending_tool_result
  • All subagent completion/failure paths → call add_pending_tool_result

But handle_reject() only does:

async fn handle_reject(&mut self) -> Result<()> {
    if let Some(approval) = self.app_state.reject() {
        self.app_state.update_tool_result(
            &approval.tool_call_id,
            "Tool execution rejected by user".to_string(),
            false,
            "Rejected".to_string(),
        );
        // Legacy bridge handling...
        // MISSING: self.app_state.add_pending_tool_result(...)
        // MISSING: continuation check (continue_with_tool_results)
    }
    Ok(())
}

Reproduction Steps

  1. Start a conversation that triggers a tool requiring approval (e.g., Edit, Write, Execute)
  2. When the approval prompt appears, press the Reject key
  3. Observe that the conversation stalls — the LLM never receives the rejection and cannot continue

Expected Behavior

After rejecting a tool, the rejection message should be sent back to the LLM as a tool result (with success: false), allowing the agentic loop to continue. The LLM can then acknowledge the rejection and try an alternative approach.

Fix

Add add_pending_tool_result and a continuation check after the rejection:

async fn handle_reject(&mut self) -> Result<()> {
    if let Some(approval) = self.app_state.reject() {
        self.app_state.update_tool_result(
            &approval.tool_call_id,
            "Tool execution rejected by user".to_string(),
            false,
            "Rejected".to_string(),
        );

        // Feed rejection back to LLM for agentic loop continuation
        self.app_state.add_pending_tool_result(
            approval.tool_call_id.clone(),
            approval.tool_name.clone(),
            "Tool execution rejected by user".to_string(),
            false,
        );

        // Continue agentic loop if no other tools are running
        if self.running_tool_tasks.is_empty() && self.running_subagents.is_empty() {
            if self.app_state.has_pending_tool_results() {
                let _ = self.continue_with_tool_results().await;
            }
        }

        // Legacy bridge handling...
    }
    Ok(())
}

Impact

Any time a user rejects a tool during an agentic conversation, the entire conversation becomes stuck. The user must manually send a new message to resume interaction. This defeats the purpose of the approval/rejection flow in the agentic loop.

Version

v0.1.0

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