# RuntimeError: Cannot cancel goal that is already being cancelled

- **ID:** `ros2/nav2-cancel-goal-multiple-times`
- **Domain:** ros2
- **Category:** runtime_error
- **Verification:** ai_generated
- **Fix Rate:** 85%

## Root Cause

Nav2 action server does not support concurrent cancel requests on the same goal; calling cancel multiple times triggers a race condition in the action server's internal state machine.

## Version Compatibility

| Version | Status | Introduced | Deprecated |
|---------|--------|------------|------------|
| ros2-humble | active | — | — |
| ros2-iron | active | — | — |
| ros2-rolling | active | — | — |

## Workarounds

1. **Check if the goal is already in a terminal state before calling cancel: if (goal_handle->get_status() == action_msgs::msg::GoalStatus::STATUS_ACCEPTED || goal_handle->get_status() == action_msgs::msg::GoalStatus::STATUS_EXECUTING) { goal_handle->cancel(); }** (90% success)
   ```
   Check if the goal is already in a terminal state before calling cancel: if (goal_handle->get_status() == action_msgs::msg::GoalStatus::STATUS_ACCEPTED || goal_handle->get_status() == action_msgs::msg::GoalStatus::STATUS_EXECUTING) { goal_handle->cancel(); }
   ```
2. **Use a mutex to serialize cancel calls: std::lock_guard<std::mutex> lock(cancel_mutex_); if (!cancel_in_progress_) { cancel_in_progress_ = true; goal_handle->cancel(); cancel_in_progress_ = false; }** (85% success)
   ```
   Use a mutex to serialize cancel calls: std::lock_guard<std::mutex> lock(cancel_mutex_); if (!cancel_in_progress_) { cancel_in_progress_ = true; goal_handle->cancel(); cancel_in_progress_ = false; }
   ```

## Dead Ends

- **** — Race still occurs if two cancel calls happen within the sleep window; not a deterministic fix. (70% fail)
- **** — Error is suppressed but the action server state may remain inconsistent, leading to future crashes. (60% fail)
