Quick answer: SetDestination returns false when the agent is not placed on a valid NavMesh surface, the NavMesh has not been baked, or the agent is disabled. Check agent.isOnNavMesh before calling SetDestination and ensure the NavMesh is baked for the correct agent type.

Here is how to fix Unity NavMesh agent not moving. You call NavMeshAgent.SetDestination with a valid-looking position, and the agent just stands there. No errors in the console. No movement. The path seems like it should work, the NavMesh is baked, and the destination is reachable. This is one of the most common and frustrating issues in Unity AI navigation, and it almost always comes down to one of a handful of configuration mismatches.

The Symptom

The agent does not move after calling SetDestination. Sometimes it starts moving then stops. Sometimes it rotates in place. Sometimes it works in one scene but not another. The agent's pathStatus may show PathInvalid or PathPartial, or the path may appear valid but the agent's velocity stays at zero.

There is no single cause for this. The NavMesh system has multiple layers of configuration that must all agree, and a mismatch at any layer produces the same visible result: the agent refuses to move.

Check If the Agent Is on the NavMesh

The first thing to verify is whether the agent is actually placed on a valid NavMesh surface. If the agent spawns slightly above or below the baked mesh, it will not register as being on the NavMesh, and all pathfinding calls will silently fail.

using UnityEngine;
using UnityEngine.AI;

public class NavAgentDebug : MonoBehaviour
{
    private NavMeshAgent _agent;

    void Start()
    {
        _agent = GetComponent<NavMeshAgent>();

        if (!_agent.isOnNavMesh)
        {
            Debug.LogWarning($"{name} is NOT on NavMesh at {transform.position}");

            // Try to place the agent on the nearest valid point
            if (NavMesh.SamplePosition(transform.position, out NavMeshHit hit, 5f, NavMesh.AllAreas))
            {
                transform.position = hit.position;
                Debug.Log($"Warped {name} to NavMesh at {hit.position}");
            }
            else
            {
                Debug.LogError($"No NavMesh found within 5 units of {name}");
            }
        }
    }
}

This check should be your first debugging step every time. Call it before any SetDestination. If isOnNavMesh is false, no amount of pathfinding configuration will help.

Bake Settings and Agent Radius Mismatch

The NavMesh bake process uses an agent radius and height to determine which areas are walkable. The NavMeshAgent component on your GameObject has its own radius and height. If these do not match, the agent may consider paths impassable that the baked mesh thinks are open, or vice versa.

Agent radius too large. If your NavMeshAgent radius is larger than the bake radius, the agent will get stuck in corridors and doorways that the NavMesh considers walkable. The mesh was baked for a thinner agent.

Agent height mismatch. A tall agent cannot walk under low ceilings that a short bake height would allow. The NavMesh was baked assuming a shorter agent and does not account for the taller one.

Step height too small. If the baked step height is smaller than actual terrain variations, the NavMesh will have gaps at small ledges that break path connectivity.

// Log agent configuration for debugging
void LogAgentConfig(NavMeshAgent agent)
{
    Debug.Log($"Agent radius: {agent.radius}");
    Debug.Log($"Agent height: {agent.height}");
    Debug.Log($"Agent base offset: {agent.baseOffset}");
    Debug.Log($"Agent on NavMesh: {agent.isOnNavMesh}");
    Debug.Log($"Agent has path: {agent.hasPath}");
    Debug.Log($"Path status: {agent.pathStatus}");
    Debug.Log($"Remaining distance: {agent.remainingDistance}");
}

Open the Navigation window (Window > AI > Navigation) and compare the bake settings with your agent's component values. They do not need to be identical, but the bake radius should be less than or equal to your smallest agent radius.

Validating the Destination with SamplePosition

A destination that is even slightly off the NavMesh surface will cause SetDestination to fail or produce a partial path. This is extremely common when the destination comes from a mouse click raycast, a random point, or another object's position.

public class NavAgentMover : MonoBehaviour
{
    private NavMeshAgent _agent;

    void Awake()
    {
        _agent = GetComponent<NavMeshAgent>();
    }

    public bool MoveTo(Vector3 worldPosition)
    {
        if (!_agent.isOnNavMesh)
        {
            Debug.LogWarning("Agent is not on NavMesh");
            return false;
        }

        // Snap the destination to the nearest NavMesh point
        if (NavMesh.SamplePosition(worldPosition, out NavMeshHit hit, 2f, NavMesh.AllAreas))
        {
            bool success = _agent.SetDestination(hit.position);

            if (!success)
                Debug.LogWarning($"SetDestination failed for {hit.position}");

            return success;
        }

        Debug.LogWarning($"No NavMesh near {worldPosition}");
        return false;
    }
}

The SamplePosition call searches within a radius for the nearest point on the NavMesh. This handles minor position discrepancies from raycasts hitting slightly off-mesh geometry, spawn points placed at approximate locations, or animation root motion drifting the character off the mesh edge.

Always check the return value of SetDestination. It returns false when the agent is not on the NavMesh, the NavMesh is not baked, or the destination cannot be mapped to any NavMesh polygon. Most developers ignore this return value and then wonder why the agent does nothing.

NavMeshObstacle Carving Issues

Dynamic obstacles with NavMeshObstacle components can carve holes in the NavMesh at runtime. If an obstacle is carving a hole right where the agent is standing or right in the only path to the destination, the agent will stop moving.

Obstacle on the agent itself. Some developers add both a NavMeshAgent and a NavMeshObstacle to the same GameObject. The obstacle carves the mesh under the agent's feet, removing the surface it is standing on. The agent detects it is no longer on the NavMesh and stops. Never put both components on the same object.

Carving blocking the only path. If obstacles block all possible routes, the agent gets a partial or invalid path. Check agent.pathStatus to detect this situation.

void Update()
{
    if (_agent.hasPath)
    {
        if (_agent.pathStatus == NavMeshPathStatus.PathPartial)
            Debug.LogWarning("Path is partial - cannot reach destination");
        else if (_agent.pathStatus == NavMeshPathStatus.PathInvalid)
            Debug.LogError("Path is invalid - destination unreachable");
    }

    // Detect agent stuck with valid path but zero velocity
    if (_agent.hasPath && _agent.remainingDistance > _agent.stoppingDistance
        && _agent.velocity.sqrMagnitude < 0.01f)
    {
        Debug.LogWarning("Agent has path but zero velocity - likely stuck");
    }
}

For dynamic environments, consider using NavMeshObstacle.carving with a carve delay. Setting carvingMoveThreshold and carvingTimeToStationary prevents rapid recarving when obstacles are moving, which can cause NavMesh flickering and path recalculation storms.

Path Visualization for Debugging

When the agent appears stuck, the most valuable debugging tool is visualizing the calculated path. Draw the path corners in the Scene view to see exactly where the agent is trying to go and where it gets blocked.

void OnDrawGizmos()
{
    if (_agent == null || !_agent.hasPath)
        return;

    var path = _agent.path;
    Gizmos.color = Color.yellow;

    for (int i = 0; i < path.corners.Length - 1; i++)
    {
        Gizmos.DrawLine(path.corners[i], path.corners[i + 1]);
        Gizmos.DrawSphere(path.corners[i], 0.15f);
    }

    // Draw destination
    Gizmos.color = Color.red;
    Gizmos.DrawSphere(_agent.destination, 0.25f);

    // Draw agent position on NavMesh
    Gizmos.color = _agent.isOnNavMesh ? Color.green : Color.red;
    Gizmos.DrawWireSphere(transform.position, _agent.radius);
}

This visualization immediately shows you whether the path exists, where it goes, and whether the agent is actually on the mesh. A green wire sphere means the agent is on the NavMesh. A red sphere means it has fallen off. Yellow lines show the planned route. If there are no yellow lines, there is no valid path.

Related Issues

If the agent moves but the camera following it jitters, see Cinemachine camera jittering. If the agent is part of a multiplayer game and other clients do not see it move, check NetworkObject not spawning. For agents driven by animation that ignore the NavMesh, see animation not playing in Animator.

If SetDestination returns true but the agent still does not move, check agent.isStopped. Something in your code may have set it to true.