This site documents a collection of planning-module failures we reproduced and debugged on Baidu Apollo, an open-source Automated Driving System (ADS). Each case below includes a video of the failure, the scenario configuration needed to reproduce it, our root-cause analysis of the underlying code, and a link to the corresponding GitHub issue we filed against the Apollo repository.
These failures were originally reported by SafePlanner as end-to-end test cases that cause the ego vehicle to behave incorrectly, with only a shallow analysis of the underlying cause. Our goal here is to go one step further: for each case, we dug into the Apollo source code, identified the specific faulty logic responsible for the failure, and, where possible, confirmed it by modifying the code and re-running the scenario to verify the fix. This site exists to make that analysis accessible, both for other researchers working on ADS testing and debugging, and for the Apollo maintainers reviewing the linked issues.
AD system side
OS: Ubuntu 20.04.1 LTS
GPU: NVIDIA GeForce RTX 2080 Ti
Apollo version: Baidu Apollo r7.0.0
Enabled modules: Localization, Perception, Transform, Routing, Prediction, Planning, Traffic Light, Control, Recorder
Prediction module modification: Perception input replaced with 3D ground truth (gt_perception)
Simulator side
OS: Ubuntu 22.04.5 LTS
GPU: NVIDA GeForce RTX 3090
Simulator: LGSVL Simulator 2021.3, integrated via SORA-SVL
Failure #1
Failure Oracle: Immobility
Triggering Condition
Ego's path passes through a junction.
A stationary NPC is present in the same lane before the junction.
Distance from the NPC to the junction < 20.0 m (15.0 m for bare intersection)
Description
Ego permanently stops in front of the NPC.
A stop decision due to the obstacle occurs persistently.
Cause Analysis
This code checks the distance from the blocking obstacle to the nearest signal, stop sign, or junction (overlap.second.start_s - blocking_obstacle_s), and if that distance is below a threshold (kIntersectionClearanceDist for signals/stop signs, kJunctionClearanceDist for other overlaps), it disables SIDE_PASS and returns false.
This is a reasonable safety policy on its own, since attempting to borrow a lane right before an intersection is risky. However, the check only looks at distance, never at how long the obstacle has been stationary. As long as the obstacle sits within this distance, a stop decision is generated every frame, and the Ego waits indefinitely, whether the obstacle is briefly stopped or permanently stuck.
Possible fix direction
Keep the existing distance-based restriction near intersections, but additionally track how long the obstacle has remained stationary, and allow a fallback (e.g., a limited SIDE_PASS or rerouting) once that duration exceeds a threshold.
Failure #2
Failure Oracle: Immobility
Triggering Condition
Ego's path involves a right turn.
A right turn at a specific intersection. ex) MapIntersection_junction_86_0's RL light.
Description
Ego permanently stops during a right turn.
Ego fails path optimization and stops via a fallback trajectory.
Cause Analysis
In piecewise_jerk_path_optimizer.cc, a feasible path is generated from the given path boundary information using OSQP-based optimization: if the solver succeeds, the resulting candidate is added to a list of feasible paths; if that list ends up empty, the if statement at line 195 reports optimization failure. We observed that this failure occurs repeatedly when the Ego attempts a right turn at certain intersections. Since Apollo typically plans trajectories with a few seconds of lookahead, a single failed optimization does not immediately stop the vehicle; the Ego briefly continues using the previously generated path. However, once optimization keeps failing across consecutive frames, the Ego eventually falls back to a fallback trajectory and stops in the middle of the intersection.
Line 195 is the direct signal marking this optimization failure. We also found several other GitHub issues reporting similar optimization failures during sharp turns, which suggests that the underlying cause may extend beyond this specific intersection, possibly reflecting a more general limitation of the piecewise-jerk optimization formulation (or the OSQP solver) under sharp curvature changes.
Possible fix direction
Given the recurrence of similar failures across sharp-turn scenarios reported elsewhere, we suspect the optimizer's handling of sharp curvature may be worth investigating further, potentially as a broader issue beyond this specific case.
Failure #3
Failure Oracle: Immobility
Triggering Condition
Ego needs to change lanes to make a left/right turn.
Ego's lane change fails due to an NPC in the target lane or other reasons.
No STOP_SIGN_UNPROTECTED scenario.
Description
Ego permanently stops during a right turn.
Ego fails path optimization and stops via a fallback trajectory.
Ego experiences multiple reroutings near the intersection.
Cause Analysis
In modules/planning/traffic_rules/rerouting.cc, once a rerouting is triggered, subsequent reroutings are suppressed for a 3-second cooldown, checked via last_rerouting_time(). However, in modules/planning/on_lane_planning.cc, whenever the routing changes, the previous planning context is cleared entirely, including last_rerouting_time(). This resets the cooldown check, allowing a new rerouting to fire immediately.
In this case, this causes rerouting to trigger while the Ego is already mid-execution of the turn, reverting the active scenario back to the default LANE_FOLLOW. With the vehicle now misaligned relative to this new stage's expected state, path optimization fails, and the Ego falls back to a stop inside the intersection.
Possible fix direction
Preserving last_rerouting_time() independently across planning-context resets (rather than clearing it along with the rest of the context) would restore the intended cooldown behavior. That said, we suspect the more fundamental issue is that rerouting is triggered too late relative to the vehicle's committed maneuver, so a narrower fix around the cooldown handling may address the symptom without fully resolving the underlying timing issue.
Failure #4
Failure Oracle: Immobility
Triggering Condition
Ego needs to change lanes to make a left/right turn.
Ego's lane change fails due to an NPC in the target lane or other reasons.
STOP_SIGN_UNPROTECTED scenario.
Description
Ego experiences multiple reroutings near the intersection.
Ego repeats the PRE_STOP to CREEP stages several times.
Ego moves forward slightly, then the scenario resets to LANE_FOLLOW and it stops.
Cause Analysis
In modules/planning/scenarios/scenario_manager.cc, the STOP_SIGN_UNPROTECTED scenario is only selected when the distance from the Ego to the stop sign, computed using the front edge of the vehicle (adc_front_edge_s), is strictly positive. When the lane change fails and rerouting occurs, the scenario manager re-evaluates which scenario to select. On the first rerouting, the Ego is still behind the stop sign, so STOP_SIGN_UNPROTECTED is correctly re-selected. However, by the time a later rerouting occurs, the Ego has already crept slightly forward during the CREEP stage, pushing the front edge past the stop sign. This makes the distance negative, so the scenario falls back to the default LANE_FOLLOW instead.
Separately, in modules/planning/traffic_rules/stop_sign.cc, the stop decision created for the stop sign is only maintained through the STOP and CREEP stages, and is not carried over once the scenario falls back to LANE_FOLLOW. Since LANE_FOLLOW has no mechanism to clear or supersede this now-orphaned stop decision, the Ego remains permanently stopped.
Possible fix direction
In our tests, using the rear edge of the vehicle (adc_back_edge_s) instead of the front edge when computing the distance to the stop sign resolved this specific case. That said, we suspect the more fundamental issue is that scenario-relevant context is not preserved across rerouting, and a more complete fix would involve maintaining the stop-sign scenario context through a rerouting event rather than relying purely on a distance-based re-entry condition.
Failure #5
Failure Oracle: Mission Failure
Triggering Condition
EMERGENCY_PULL_OVER scenario.
No specific triggering condition has been identified except the scenario type.
Presumed to be related to the Ego's position.
Description
Ego re-executes the EMERGENCY_PULL_OVER even though it has already completed it.
Cause Analysis
In path_bounds_decider.cc, an already-found pull-over position is re-validated against the current path boundary every cycle via IsPointWithinPathBound. We found a boundary-index bug in this function: when the position's s-coordinate falls at or before the boundary's first element, it computes idx_before = -1, an out-of-bounds index. The resulting bogus bound values cause the check to incorrectly report the position as outside the boundary, so the Ego clears its valid pull-over position and searches for a new one, restarting the maneuver.
Possible fix direction
IsPointWithinPathBound should explicitly handle the case where the computed index falls at or before the first boundary element, rather than falling through to an out-of-bounds idx_before. Guarding this edge case directly at the index computation would prevent a valid pull-over position from being misclassified as out of bounds.
Failure #6
Failure Oracle: Mission Failure
Triggering Condition
Ego's path passes through a junction.
EMERGENCY_PULL_OVER scenario is triggered near the intersection.
Description
Ego generates the pull-over destination (blue rectangle) inside the intersection.
Ego finally stops in the intersection.
Cause Analysis
In path_bounds_decider.cc, when searching for a pull-over position, the code checks whether a candidate point falls inside a junction by calling GetJunctions with a hardcoded search radius of 1 meter. This radius is far too small to reliably detect that a candidate point is inside an intersection; in our tests, a radius on the order of ten-plus meters was needed to consistently recognize the point as being inside the junction. With the current 1m radius, a point clearly inside the intersection can still fail to match any nearby junction, so the check passes and the point is accepted as a valid pull-over location.
Possible fix direction
Increasing this search radius to a more appropriate value is a straightforward starting point, though the right radius may need to be tuned depending on junction size and shape rather than treated as a single fixed constant.