Quick answer
ERR_NOT_IN_RANGE is -9. For a Creep action, it means the target exists but the Creep is outside that method's required distance. Save the action result, call moveTo() only when the result is exactly ERR_NOT_IN_RANGE, use the action's real range, save the movement result separately, return from the branch, and retry the original action on a later tick.
Identify which method returned -9
Do not log only a bare number:
const harvestResult = creep.harvest(source);
console.log(JSON.stringify({
method: 'harvest',
creepName: creep.name,
targetId: source.id,
harvestResult
}));
The same constant can be returned by many Creep actions, but the valid distance and other prerequisites differ. A structure method that returns a range-related error cannot solve it by calling moveTo() on the structure.
Common Creep action ranges
| Action | Usable distance | Suggested movement range |
|---|---|---|
harvest() | Adjacent | { range: 1 } |
withdraw() | Adjacent | { range: 1 } |
transfer() | Adjacent | { range: 1 } |
pickup() | Same tile or adjacent | { range: 1 } |
attack() and heal() | Range 1 | { range: 1 } |
build(), repair(), and upgradeController() | Range 3 | { range: 3 } |
rangedAttack() and rangedHeal() | Range 3 | { range: 3 } |
The range option tells pathfinding where it may stop. It does not change the original action's rules. Using range 1 for an upgrader can work, but it sends the Creep closer than necessary and may crowd the Controller area.
Move now and retry on the next tick
Current tick:
call action
→ ERR_NOT_IN_RANGE
→ submit moveTo()
→ return
Later tick:
read the new position
→ call the action again
Screeps scripts read the game state for the current tick. Calling moveTo() does not change creep.pos during the same JavaScript execution:
creep.moveTo(source);
creep.harvest(source);
The second line still checks the old position. A clear loop attempts the action first, submits movement only for ERR_NOT_IN_RANGE, and lets the next tick retry.
Complete range-1 example
State impact: this script may submit one harvest action or one movement order for Harvester1. It does not write Memory.
function moveForAction(
creep,
target,
desiredRange,
label
) {
if (creep.getActiveBodyparts(MOVE) <= 0) {
return {
status: 'no-active-move-part'
};
}
const moveResult = creep.moveTo(target, {
range: desiredRange,
reusePath: 10
});
if (
moveResult !== OK
&& moveResult !== ERR_TIRED
) {
console.log(JSON.stringify({
type: 'movement-failed',
creepName: creep.name,
label,
moveResult
}));
}
return {
status: moveResult === OK
? 'move-submitted'
: 'move-not-submitted',
moveResult
};
}
function runHarvester(creep) {
if (!creep || creep.spawning) {
return {
status: 'creep-unavailable'
};
}
const source = creep.pos.findClosestByPath(
FIND_SOURCES_ACTIVE
);
if (!source) {
return {
status: 'active-source-not-found'
};
}
const harvestResult = creep.harvest(source);
if (harvestResult === ERR_NOT_IN_RANGE) {
return {
harvestResult,
...moveForAction(
creep,
source,
1,
'Source'
)
};
}
if (harvestResult !== OK) {
console.log(JSON.stringify({
type: 'harvest-failed',
creepName: creep.name,
sourceId: source.id,
harvestResult
}));
}
return {
status: harvestResult === OK
? 'harvest-submitted'
: 'harvest-failed',
harvestResult
};
}
module.exports.loop = function () {
runHarvester(Game.creeps.Harvester1);
};
The action and movement results remain separate. A pathfinding problem is not misreported as a harvesting problem.
Complete range-3 example
function runUpgrader(creep) {
if (!creep || creep.spawning) {
return {
status: 'creep-unavailable'
};
}
const controller = creep.room.controller;
if (!controller || controller.my !== true) {
return {
status: 'owned-controller-not-found'
};
}
const upgradeResult =
creep.upgradeController(controller);
if (upgradeResult === ERR_NOT_IN_RANGE) {
const moveResult = creep.moveTo(controller, {
range: 3,
reusePath: 10
});
return {
status: moveResult === OK
? 'move-submitted'
: 'move-not-submitted',
upgradeResult,
moveResult
};
}
return {
status: upgradeResult === OK
? 'upgrade-submitted'
: 'upgrade-failed',
upgradeResult
};
}
The same movement range applies to normal building and repair actions. Keep their resource, ownership, and body checks separate.
Read the movement result separately
moveTo() result | Meaning | Next step |
|---|---|---|
OK | The movement order was scheduled. | Read position on a later tick. |
ERR_TIRED | The Creep has fatigue. | Wait and inspect load, terrain, and MOVE ratio. |
ERR_NO_PATH | No route to the requested range was found. | Inspect range, obstacles, callbacks, and route limits. |
ERR_NOT_FOUND | noPathFinding was used without a reusable cached path. | Allow a fresh path search. |
ERR_INVALID_TARGET | The movement target is invalid. | Validate the target and its position. |
ERR_BUSY | The Creep is still spawning. | Wait until spawning finishes. |
ERR_NOT_OWNER | The Creep is not yours. | Validate the selected object. |
OK does not prove that the Creep arrived or even changed position after tick processing. Continue with the moveTo() OK diagnostic guide when several later ticks show no progress.
If the Creep is already in range
const currentRange = creep.pos.getRangeTo(target);
const adjacent = creep.pos.isNearTo(target);
const withinRangeThree = creep.pos.inRangeTo(
target,
3
);
When the required range is already satisfied but the action still fails, more movement is not the solution. Inspect active body parts, carried resources, target capacity, target type, ownership, spawning state, or the method's other documented return codes.
Current moveTo() return-code correction
The Chinese source listed ERR_NO_BODYPART as a possible moveTo() result. The current official Creep.moveTo() return table does not list that code. This English article still checks creep.getActiveBodyparts(MOVE) because a Creep without an active MOVE part cannot make normal movement progress, but it does not attribute an undocumented return value to moveTo().
Debugging checklist
- Record the exact action method and its return value.
- Validate the Creep and target before calling either method.
- Use range 1 or range 3 according to the real action.
- Enter the movement branch only for
ERR_NOT_IN_RANGE. - Save
actionResultandmoveResultseparately. - Return after scheduling movement.
- Retry the original action on a later tick.
- Precheck active MOVE parts without inventing a moveTo() return code.
- Do not keep moving when another prerequisite is failing.
- Use the English error-code reference for shared constants.
Scope and next steps
This guide does not design CostMatrix rules, cross-room routing, traffic coordination, path caches, or a global movement scheduler. Continue with diagnosing a movement order that returns OK but shows no progress.
Frequently asked questions
Does -9 mean the target is invalid?
No. It means the Creep is outside the action's required distance.
Should every action stop at range 1?
No. Building, repair, Controller upgrading, ranged attacks, and ranged healing work at range 3.
Can movement and the action finish in one script execution?
The later action still reads the current tick's old position. Retry on a later tick.
Does moveTo() return ERR_NO_BODYPART?
The current official return table does not list it. Check active MOVE parts separately.