Quick answer
ERR_NOT_IN_RANGE (-9) belongs to the action you called. If creep.harvest(source) returns it, the Creep is not close enough to harvest. Call moveTo() as a separate operation, check that return code separately, and retry harvest() on a later tick when the Creep is actually within range 1.
const result = creep.harvest(source);
if (result === ERR_NOT_IN_RANGE) {
const moveResult = creep.moveTo(source, {
range: 1,
reusePath: 5
});
if (moveResult !== OK && moveResult !== ERR_TIRED) {
console.log('moveTo() returned ' + moveResult);
}
}
This pattern keeps two questions separate: “Why did the action fail now?” and “Was a movement request accepted?”
Use this guide when
Use it when a Creep action such as harvest(), transfer(), withdraw(), build(), repair(), or upgradeController() returns ERR_NOT_IN_RANGE. If moveTo() itself returns ERR_NO_PATH, use the ERR_NO_PATH guide. If moveTo() returns OK but the position does not progress on later ticks, use the accepted-movement debugging guide.
Separate the action result from movement
A common debugging mistake is to overwrite the original action result:
let result = creep.harvest(source);
if (result === ERR_NOT_IN_RANGE) {
result = creep.moveTo(source);
}
console.log(result);
Now the log no longer tells you whether harvest() returned -9; it only shows the later movement call. Keep both values:
const actionResult = creep.harvest(source);
let moveResult = null;
if (actionResult === ERR_NOT_IN_RANGE) {
moveResult = creep.moveTo(source, { range: 1 });
}
console.log(JSON.stringify({
tick: Game.time,
actionResult,
moveResult
}));
An OK movement result is not proof that the original action happened. The action already returned for the current call.
Check the action's real range
Do not copy one movement range into every role. Different actions have different range requirements.
| Action | Required range | Typical movement target |
|---|---|---|
harvest(source) | 1 | { range: 1 } |
transfer(target, resource) | 1 | { range: 1 } |
withdraw(target, resource) | 1 | { range: 1 } |
build(site) | 3 | { range: 3 } |
repair(structure) | 3 | { range: 3 } |
upgradeController(controller) | 3 | { range: 3 } |
Use creep.pos.getRangeTo(target) when you need a read-only check. The RoomPosition distance guide covers range semantics and cross-room boundaries.
Minimal safe fix
For a Harvester, keep the action branch small:
function harvestOrMove(creep, source) {
if (!creep || !source) {
return { status: 'missing-object' };
}
const harvestResult = creep.harvest(source);
if (harvestResult !== ERR_NOT_IN_RANGE) {
return {
status: 'harvest-result',
harvestResult
};
}
const moveResult = creep.moveTo(source, {
range: 1,
reusePath: 5
});
return {
status: 'moving-into-range',
harvestResult,
moveResult
};
}
The function does not convert movement acceptance into harvest success. That distinction makes logs and later decisions trustworthy.
Why the action retries on a later tick
Your loop reads the current game state, then submits actions and movement intents. If harvest() returns ERR_NOT_IN_RANGE, calling moveTo() does not rewrite creep.pos for the remainder of that JavaScript execution. The movement is resolved as part of the tick.
tick 1200
harvest() -> ERR_NOT_IN_RANGE
moveTo() -> OK
later tick
read the new Creep position
harvest() is attempted again
This is a timing model, not a recorded live trace from this editorial pass. Live multi-tick verification remains pending.
Read moveTo() return codes separately
| moveTo() result | Meaning for this branch |
|---|---|
OK | The movement request was accepted; verify position progress on later ticks. |
ERR_NO_PATH | No path was found under the current movement options. |
ERR_BUSY | The Creep cannot perform the movement request in its current state, such as while spawning. |
ERR_INVALID_TARGET | The target is not a valid movement target. |
ERR_TIRED | Fatigue prevents movement this tick. |
ERR_NO_BODYPART | No active MOVE body part remains. |
Do not retry pathfinding indefinitely after a hard call failure. Branch on the result that actually occurred.
Check body, resource, and ownership prerequisites
Getting into range does not guarantee the original action will succeed. A Harvester needs an active WORK part and free capacity for harvested Energy. A transfer needs the resource being transferred. Controller upgrading needs carried Energy and an active WORK part. Ownership and target validity also differ by method.
The sequence is therefore:
- Call the intended action and keep its return code.
- If and only if it returns
ERR_NOT_IN_RANGE, request movement with the action's real range. - On later ticks, call the action again and diagnose the new return code rather than assuming movement solved every prerequisite.
Safer diagnostic version
function describeRangeFailure(creep, target, actionName, actionResult) {
const range = creep && target
? creep.pos.getRangeTo(target)
: null;
return {
tick: Game.time,
creep: creep?.name ?? null,
action: actionName,
actionResult,
targetId: target?.id ?? null,
range,
fatigue: creep?.fatigue ?? null,
activeMove: creep
? creep.getActiveBodyparts(MOVE)
: null
};
}
Log this only when a failure changes state or at a bounded interval. Permanent per-tick logging can make a simple range problem harder to read.
Common mistakes
- Replacing the action return code with the later
moveTo()result. - Using range 1 for actions that work at range 3, causing unnecessary traffic.
- Calling the action again in the same execution and expecting
creep.posto have changed. - Treating
moveTo() === OKas proof that the action succeeded. - Ignoring
ERR_NO_BODYPARTafter all activeMOVEparts are gone. - Diagnosing range before checking whether the target object exists.
When this guide does not apply
This guide does not solve path search failure, traffic deadlocks, fatigue body design, invisible-room targets, or a Creep whose accepted movement does not produce later-tick progress. Follow the return code or observable symptom into the specific Movement guide instead of adding more retries here.
Frequently asked questions
What does ERR_NOT_IN_RANGE mean?
The original method requires the acting object to be closer to its target. It is not a generic “movement failed” code.
Should I retry the action immediately after moveTo()?
Do not assume the Creep moved during the same JavaScript execution. Retry on a later tick after reading the new position.
Does moveTo() return ERR_NO_BODYPART?
Yes. The current official return table includes ERR_NO_BODYPART (-12) when there is no active MOVE body part.
Official documentation
- API Reference: Creep.moveTo()
- API Reference: Creep.harvest()
- API Reference: Creep.upgradeController()
- Simultaneous actions and movement priority
Choose another guide when
If the original action is already in range but moveTo() returns ERR_NO_PATH, use the ERR_NO_PATH guide. If moveTo() returns OK but the Creep position does not progress on later ticks, use the accepted-movement guide. Do not turn either case into another blind range retry.
const harvestResult = creep.harvest(source);
if (harvestResult === ERR_NOT_IN_RANGE) {
const moveResult = creep.moveTo(source, {
range: 1,
reusePath: 5
});
// Keep harvestResult and moveResult separate.
}