Skip to content

Release Hub development updates for Microbot 2.6.34 - #654

Merged
chsami merged 39 commits into
mainfrom
claude/release-hub-2026-10-18
Oct 18, 2026
Merged

chsami merged 39 commits into
mainfrom
claude/release-hub-2026-10-18

Conversation

@chsami

@chsami chsami commented Oct 18, 2026 •

Copy link
Copy Markdown
Owner

Summary

Releases the reviewed development plugin updates to the stable Hub, alongside Microbot 2.6.34 (chsami/Microbot#2002). Every plugin also builds against the published 2.6.33 client.

All 38 changes are crash fixes found in the client error telemetry. Each one bumps its plugin's version, so users are offered the update.

Included

The open development PRs are not part of this release: #596, #597, #648, #646, #618 and #593.

Release commit

c418633 merges development into main without conflicts. The tree is identical to development (d7f5501).

Test plan

  • ci.yml dispatched on this branch, ./gradlew clean build against the published 2.6.33 client: run 39113405575 passed
  • All plugins and their tests compile against the Microbot 2.6.34 shaded jar (-PmicrobotClientPath), and the Hub tests pass: 926 tests, 0 failures, 2 skipped
  • ci.yml re-dispatched against the published 2.6.34 client: run 39118056320 passed

🤖 Generated with Claude Code

https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

chsami and others added 30 commits October 14, 2026 17:08
…ot NPEs (#613)

isPkerAround compared the raw interacting Actor by name, which calls
Actor.getName on the script thread and throws "must be called on client
thread". Compare the interacting actor against the client's local player
by identity instead; the previous equals check against the Rs2PlayerModel
wrapper could never match.

The Bankfortrip sleepUntil predicates dereferenced Rs2Equipment.get for
the ring and ammo slots, which returns null until the equip lands, so
each poll threw an NPE. Null-check them via small helpers.

Bump revKillerPlugin to 2.0.12.


Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ity is null (1.9.9) (#614)

Rs2Antiban.getActivity() is null when overwriteScriptSettings makes
applyMiningSetup() return early or after another script resets antiban
settings, so every MINING tick threw an NPE and aborted the workflow.


Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…l (2.4.3) (#615)

PrayerPotionScript.run(NmzConfig) divided boosted prayer by the real prayer
level with no logged-in check, throwing ArithmeticException every 600 ms
while logged out or loading. Route both loops through a helper that skips
drinking when the real level is not yet known, and add the missing login
guard.


Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
… needed (2.2.4) (#616)

Rs2Slayer.getSlayerTaskProtectiveEquipment() returns null when there is no
slayer task, which reached isBypassedBySlayerHelmet's switch and threw an NPE
on every BankerScript loop in slayer mode.


Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
… throwing (1.0.1) (#617)

BankInventoryStrategy parsed the free-text "Always Drop Item ids" config
with Integer::parseInt, so any item name in the list threw
NumberFormatException on every loop tick with a full inventory and the
script never banked or dropped. Trim each entry and keep only numeric ids.


Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…619)

determineState called Rs2Player.getInteracting() twice, once for the
null check and again for isDead(). The target can clear between the two
calls, so the second read returned null and threw an NPE. The NPC query
filters in handleFighting had the same double read on
n.getInteracting() before equals().

Move each check into a small helper that reads the interacting actor
once and checks and uses that value.

Bump SulphurNaguaPlugin to 2.0.3.


Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The Jad and healer query filters called Rs2NpcModel.getName() twice,
once for the null check and again for toLowerCase(). Each call is a
separate client-thread read, so the second one can return null after
the first passed and throw an NPE. handleHealerInteraction also called
getName() on the interacting NPC with no null check.

Add a nameContains helper that reads the name once, and null-check the
interacting NPC's name before using it.

Rs2NpcModel.matches(false, ...) is not used because it only lowercases
the NPC name in exact mode, so it would not match "TzTok-Jad".

Bump JadPlugin to 1.0.11.


Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…t (1.8.18) (#621)

Bank, furnace and anvil replay called Objects.requireNonNull on the
recorded entry's widget, which is null for clicks not made on a widget.
The NPE escaped before executeBankActions was cleared, so it repeated
every loop. Use the widget bounds when present and a 1x1 rectangle
otherwise, matching the workbench replay.


Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The attackable NPC sort read npc.getInteracting() and the live NPC path
distance on every comparison. NPCs that moved or retargeted mid-sort
gave inconsistent keys and TimSort threw "Comparison method violates its
general contract!". Compute both keys once per NPC, then sort.


Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…is closed (1.0.1) (#623)

BankSnapshotReader threw IllegalStateException inside the
runOnClientThreadOptional callable when the bank was closed, so ClientThread
logged every BankActuator poll at ERROR. The callable now returns an empty
Optional and read() raises the same "Open your bank" error on the caller's
thread, which ends the organizer run without client-thread error spam.


Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…6) (#624)

* fix(cluesolver): keep requirement banking off the client thread (1.0.6)

RequirementHandlerTask ran the whole missing-item flow inside
ClientThread.invokeLater, so Rs2Bank.getNearestBank planned the bank
route on the client thread and threw "synchronous route planning must
not run on the client thread". The onGameTick handler also opened the
bank, withdrew and closed it on the client thread.

Only the inventory/equipment read and item names now hop to the client
thread; bank lookup, walking, opening and withdrawing run on the task
executor behind a single in-flight guard. A null nearest bank fails the
task instead of throwing an NPE, and the tick handler reuses the chosen
bank instead of re-planning a route every tick.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

* fix(cluesolver): fail requirement task instead of hanging when walking to the bank fails

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…6.2) (#625)

recoverFromPit re-queried ladder 17385 inside the 2s throttle and clicked
the result unconditionally, throwing an NPE when the cache returned no
ladder. Keep the ladder from the first lookup when the re-query is empty,
and only start the throttle after a click actually succeeds.


Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
….19) (#626)

* fix(qol): parse Wintertodt energy text defensively (1.8.19)

WintertodtScript read the energy widget with split("\\D+")[1], which threw
ArrayIndexOutOfBoundsException when the text had no digits. Take the first
run of digits instead and fall back to -1 for null or digit-less text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

* fix(qol): read Wintertodt energy from ENERGY_TITLE widget

The script read widget 25952276 (WintStatus.WARMTH_TITLE) for the boss
energy, so wintertodtHp tracked the player's warmth. Read
InterfaceID.WintStatus.ENERGY_TITLE instead, matching the other
Wintertodt plugins, and share one compiled digit pattern.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
SafeSpot called AIOFighterPlugin.getState().equals(...) on every tick,
but getState() is a raw ConfigManager read that returns null before the
state is first set (startup, after reset), so the loop threw an NPE.
Compare the state by identity through a small helper instead, matching
LootScript and SafetyScript.


Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
SandCrabScript built Rs2InventorySetup from config.inventorySetup().getName()
on every loop, but inventorySetup() defaults to null, so with no setup
selected (or the setup disabled) the loop threw an NPE before reaching its
own "Please setup your Inventory Setup" check. Validate the configured setup
first and only construct Rs2InventorySetup when inventory setups are enabled,
checking DisableInventorySetup before the equipment match.


Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(nmz): guard null widgets and local player in lobby handling (2.4.4)

isOutside now reads the cached Rs2Player world location instead of
dereferencing getLocalPlayer() on the client thread. consumeEmptyVial
skips the dream confirm when widget 129:6 is not loaded, and handleStore
stops buying when the reward shop potion child is missing instead of
passing null to Rs2Widget.clickWidgetFast.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

* fix(nmz): guard null local player when walking to power-up orbs

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
….8.7) (#630)

* fix(woodcutting): guard null return point in overlay and walk-back (1.8.7)

getReturnPoint falls back to Rs2Player.getWorldLocation(), which is null
while the local player is not loaded. The overlay passed it straight into
LocalPoint.fromWorld and threw an NPE every frame; walkBack dereferenced it
the same way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

* test(woodcutting): isolate overlay test from leaked static return points

Other plugin tests in the full suite leave Script.initialPlayerLocation
set, so getReturnPoint returned a stale non-null point in CI. Clear and
restore AutoWoodcuttingScript's static return points around the render.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…4) (#631)

* fix(mahoganyhomes): skip contract lookup while player location is unknown (0.0.14)

getClosestContractLocation compared each office with
Rs2Player.getWorldLocation(), which is null during loading or a world hop,
so WorldPoint.distanceTo2D threw an NPE and failed the loop iteration.
Read the player location once, return null from the helper when it is
missing, and skip the tick in getNewContract.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

* fix(mahoganyhomes): skip the whole tick while player location is unknown

bank() and walkToHome() pass the player location to distanceBetween and
fix()/finish() read its plane, so they also threw during loading or a world
hop. Return early from the loop when the location is null; the
getClosestContractLocation null check stays as a second guard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ailable (2.2.7) (#632)

* fix(aiofighter): skip attack style lookup when enum or struct is unavailable (2.2.7)

getWeaponTypeStyles dereferenced Microbot.getEnum and getStructComposition
results without null checks; both return null when the client-thread lookup
fails, throwing NPEs from the AttackStyleScript task. Return an empty style
array instead so updateAttackStyle leaves the current style untouched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

* test(aiofighter): cover null StructComposition before a named style

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…e (2.2.8) (#633)

handleItemOnNpcToKill called npc.getName().equalsIgnoreCase(...) on a dead
NPC interacting with the player. getName() returns null once the NPC's
composition is gone during death/despawn, throwing an NPE on the
AttackNpcScript thread. Move the item choice into a pure helper that
returns early on a null name, and cover it with unit tests.


Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
AnagramClueTask.handleDialogue passed clue.getAnswer() straight to
Rs2Keyboard.typeString, which NPEs for anagrams without a challenge
answer. Only type and submit when the answer is non-blank; otherwise
just click through the dialogue.


Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…cation (1.0.3) (#635)

* fix(scurrius): tolerate inverted eat/prayer ranges and null player location (1.0.3)

Order the configured min/max eat and prayer percents before picking a random
threshold so min > max no longer throws IllegalArgumentException, and treat a
null player location as outside the fight room during load or hop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

* fix(scurrius): skip the tick while the player location is unknown

Return early at the top of the loop when Rs2Player.getWorldLocation() is null
during load or hop, so the state machine does not treat an unknown location as
outside the fight room and the dodge loop does not dereference it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
getAvailableAltars cast every altar renderable to DynamicObject, throwing
ClassCastException when the renderable is a plain Model. Check the type
first and read the animation once.


Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
#637)

saveMouseClicks wrote to ~/.runelite/PlayerMonitor/click_count.log without
creating the directory, which is only created by loadMouseClicks and is
skipped in lite mode. Stopping the plugin then threw FileNotFoundException
and aborted shutDown before the listener and script were cleaned up.

Create the parent directory before writing, close the writer with
try-with-resources, and catch the IOException in shutDown so cleanup runs.


Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…yer gear (2.2.9) (#638)

addInventorySetup("cache") registers the setup on the client thread, so the
immediate lookup could miss it and pass null to setCurrentSlayerInventorySetup,
which threw an NPE on getName(). Wait for the cache setup to register before
looking it up, and make the setter ignore a null setup.


Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…2.3) (#639)

bank() iterated the shared items field while the stop path cleared it from
the plugin-stop thread, throwing ConcurrentModificationException. The
withdrawal list is now a local variable passed to the travel-item helpers,
the merge is a static helper, and stopping the script no longer touches it.


Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…widget (1.1.9) (#640)

Rs2Widget.getWidget(197, 11) returns null when the reward shop child is not
built yet or the shop has just closed, which threw an NPE inside
waitForInventoryChanges. Return early when the container is null and skip the
click and confirm when the reward item is not in the list.


Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…0.4) (#641)

The giant-rat query predicate called getName() twice; when the NPC
despawned between the null check and toLowerCase() the second call
returned null and the NPE aborted the Scurrius tick.


Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ad (1.1.7) (#642)

Read the player location through Rs2Player.getWorldLocation() and skip the
check when it is null, instead of dereferencing getLocalPlayer() inside
client-thread lambdas in preStateChecks, scanAmmoniteCrabLocation, walkBack,
resetAggro and otherPlayerDetected. Use Rs2Player.isInteracting() in the
FIGHT case.


Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ender (2.1.6) (#643)

ThievingOverlay.render calls xpGained() every frame, which called
setStartXp() and dereferenced Rs2Player.getLocalPlayer() without a null
check. getLocalPlayer() returns null while the local player is missing
from the player cache (login, world hop, loading), so the overlay threw an
NPE on every frame. Skip the start-xp update when the local player is null
and leave startXp untouched.


Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
chsami and others added 9 commits October 16, 2026 16:42
…k (1.3.17) (#644)

The melee rat step only opened gate 9719 when the player already stood on
the x=3111 gate strip, and otherwise gated the attack on Rs2Walker.canReach,
which routes through the gate transport and tolerates a 2-tile endpoint
offset. Rats were clicked from outside the closed pen (spam-clicking) or the
step idled. Now the attack is gated on a strict collision reachability check;
when the rat is not reachable the player walks to the gate and opens it. The
ranged step attacks the rat over the fence without a reachability check.


Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…(1.0.5) (#645)

* fix(PVirewatchKiller): skip script loop until fight area is set (1.0.5)

The script loop starts with no initial delay and dereferenced
plugin.fightArea before the first GameTick assigned it, and again after
the plugin stop hook cleared it. Read the player location once with a
null guard, snapshot fightArea and startingLocation, and return early
while any of them is missing. Compare WorldPoints with equals instead of
identity so the return-to-start branch is no longer always taken.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

* fix(PVirewatchKiller): skip NPC outline in overlay until fight area is set

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

* fix(PVirewatchKiller): read fight area once per frame for the tile loop

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
#647)

The rev239 cache update removed PMOON_FISHING_SPOT_MULTI (51367) and
moved the Moons of Peril bream spot to ObjectID.RIVER_FISHING_SPOT; the
old id now belongs to a sailing gangplank, so the Fish interaction
matched nothing and the resupply loop idled beside the stream forever.

Target RIVER_FISHING_SPOT (the id the quest helper uses at the same
stream) and stop fishing with a log line when no bream is caught for
60 seconds.


Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
… and bank (0.0.15) (#649)

walkToHome and bank null-checked a local copy of the current home but then
re-read plugin.getCurrentHome() through isMissingItems, planksNeeded and
steelBarsNeeded and for the walk target, so a contract cleared on the client
thread mid-loop threw an NPE. Rs2Player.getWorldLocation() was also passed to
distanceBetween unchecked. Snapshot both once, guard them in isAwayFromHome,
and pass the Home through to the material helpers.


Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…bleObjects (0.0.16) (#650)

getFixableObjects indexed HotspotObjects.objects by Hotspot ordinal without a
bounds check, so a broken hotspot 8 varbit at a 7-object home (or 7/8 at
SARAH) threw ArrayIndexOutOfBoundsException and aborted the script loop.


Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…andlePatch (1.2.5) (#651)

* fix(FarmTreeRun): treat null object composition as empty patch in isPatchEmpty (1.2.4)

Rs2GameObject.getObjectComposition is nullable and returns null on a
client-thread read timeout or a null impostor while the patch object
transforms after planting, which threw an NPE from handlePlantingTree.
A null composition now reads as an empty patch so the post-plant wait
keeps polling and planting is retried instead of the patch being skipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

* fix(FarmTreeRun): guard null tree composition in handlePatch and skip composition read when protection is off (1.2.5)

handlePatch dereferenced Rs2GameObject.findObjectComposition(...).getActions()
without a null check; the composition is null on the same client-thread
read failure or null impostor that caused the isPatchEmpty NPE. Return
false so the loop retries on the next tick.

handlePayment's protection opt-out checks no longer read the patch
composition, so a transient null can no longer fall through to paying
the gardener when protection is disabled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…11) (#652)

* fix(ShootingStar): close stop race on null panel and null state (1.4.11)

On "Oh dear, you are dead!" the plugin called its own shutDown()
directly, which removed and nulled the panel while the plugin stayed
registered, so tick, event and config handlers kept queueing
updatePanelList() runnables that dereferenced the null panel on the EDT.
Even a runnable queued just before a normal stop read the field after
removePanel() had cleared it.

The script loop null-checked the static state field and then switched
on it again after several client calls, so a concurrent stop that set
state to null threw on ordinal().

- stop through Microbot.stopPlugin(this) so the plugin is unregistered
- skip updatePanelList when there is no panel, and re-read the panel on
  the EDT so queued updates skip a removed panel
- read state into a local once per loop iteration and switch on it
- treat a missing antiban category as not busy

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

* fix(ShootingStar): reset loop state on start instead of on stop

ShootingStarScript.shutdown() nulled state, currentStar and pickaxe
while mainScheduledFuture.cancel(true) does not wait for a running
iteration, so a loop that had already passed its null check went on to
dereference the cleared fields (switch on state, currentStar in the
WALKING, MINING and BANKING cases). The previous local snapshot of state
only moved the NPE to the currentStar reads.

- stop clearing state, currentStar and pickaxe in shutdown()
- clear them at the top of run() with the other per-run resets
- drop the now unneeded state snapshot in the loop
- read the static state once in the overlay before rendering it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The summoned-creature lookup filtered NPCs on Actor.getInteracting(),
which asserts the client thread, but the query was evaluated by
nearest() on the script executor inside sleepUntil, throwing
IllegalStateException "must be called on client thread" and never
detecting the summoned creature. Evaluate it with nearestOnClientThread().


Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@chsami
chsami merged commit ce25d10 into main Oct 18, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant