MOVEMENT · ACTION RANGE DEBUGGING

How to Fix ERR_NOT_IN_RANGE in Screeps Without Hiding the Real Failure

CURATED PATH

These links are maintained for a concrete prerequisite, failure mode, debugging step, or next lesson instead of generic topic similarity.

Movement

Recommended next steps

Confirm the action's real rangeRange checkmoveTo returns OK but the Creep stays putNext debugging stepWhen path search fails insteadFailure branch
Verification statusOfficial documentation: Checked August 12, 2026 — Creep action ranges, Creep.moveTo(), ERR_NOT_IN_RANGE, ERR_NO_BODYPART, and game-loop timing · Static code review: Passed — original action and movement results are kept separate; range-1/range-3 and no-MOVE boundaries reviewed · Console test pending: Not run in this editorial pass

VERIFICATION

Evidence and test status

Official documentation
Checked August 12, 2026 — Creep action ranges, Creep.moveTo(), ERR_NOT_IN_RANGE, ERR_NO_BODYPART, and game-loop timing
Static code review
Passed — original action and movement results are kept separate; range-1/range-3 and no-MOVE boundaries reviewed
Console test pending
Not run in this editorial pass
Live multi-tick verification pending
No live shard trace was collected showing movement followed by a successful later-tick action retry
Last verified
August 12, 2026
Evidence level
Static official-documentation review and code review; live server evidence remains Pending
Screeps Console test
Pending — the action-to-movement retry sequence was not run in the Screeps Console for this revision

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.

ActionRequired rangeTypical 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() resultMeaning for this branch
OKThe movement request was accepted; verify position progress on later ticks.
ERR_NO_PATHNo path was found under the current movement options.
ERR_BUSYThe Creep cannot perform the movement request in its current state, such as while spawning.
ERR_INVALID_TARGETThe target is not a valid movement target.
ERR_TIREDFatigue prevents movement this tick.
ERR_NO_BODYPARTNo 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:

  1. Call the intended action and keep its return code.
  2. If and only if it returns ERR_NOT_IN_RANGE, request movement with the action's real range.
  3. 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.pos to have changed.
  • Treating moveTo() === OK as proof that the action succeeded.
  • Ignoring ERR_NO_BODYPART after all active MOVE parts 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

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.
}

SOURCE AND SCOPE

Review the source, evidence, or next system

This English guide keeps the technical scope and verification boundaries of its Chinese source. The verification record shows which checks were completed and which runtime observations are still pending.