Skip to content

fix(ClueSolverPlugin): resolve cipher clue location on client thread (1.0.8) - #658

Merged
chsami merged 1 commit into
developmentfrom
claude/err-cluesolver-cipher-getlocation-off-client-thread
Oct 19, 2026
Merged

chsami merged 1 commit into
developmentfrom
claude/err-cluesolver-cipher-getlocation-off-client-thread

Conversation

@chsami

@chsami chsami commented Oct 18, 2026

Copy link
Copy Markdown
Owner

Problem

Error telemetry group 2647 reported 2 occurrences in 1 report, on ClueSolverPlugin 1.0.7 with Microbot 2.6.34. Logger: ClueTask. Thread: pool-12-thread-1.

java.lang.IllegalStateException: must be called on client thread
  net.runelite.api.Quest.getState:257
  net.runelite.client.plugins.cluescrolls.clues.CipherClue.isElunedInPrifddinas:356
  net.runelite.client.plugins.cluescrolls.clues.CipherClue.lambda$static$0:104
  net.runelite.client.plugins.cluescrolls.clues.CipherClue.getLocation:241
  ...cluesolver.cluetask.CipherClueTask.walkToLocation:55
  ...cluesolver.cluetask.CipherClueTask.executeTask:50
  ...cluesolver.cluetask.ClueTask.run:36
  ...cluesolver.ClueSolverScript.lambda$startClueTask$14:169

Root cause

ClueSolverScript.startClueTask runs task.run() on the script executor, not on the client thread. CipherClueTask.walkToLocation called clue.getLocation(clueScrollPlugin) directly. For the Eluned (Prifddinas) cipher clue, RuneLite's CipherClue gets the location from a provider that calls Quest.getState. That method only works on the client thread, so the call threw and the task failed before it started walking.

Change

  • CipherClueTask.walkToLocation now reads the location with Microbot.getClientThread().runOnClientThreadOptional(() -> clue.getLocation(clueScrollPlugin)).orElse(null). This is the same pattern ClueTask.getPlayerLocationSafe uses. If the read throws or times out, the existing null check fails the task cleanly. The walker call still runs off the client thread.
  • onGameTick is unchanged, because it already runs on the client thread.
  • ClueSolverPlugin version goes from 1.0.7 to 1.0.8.

Validation

  • New CipherClueTaskTest:
    • locationIsResolvedOnClientThreadBeforeWalking: the mocked getLocation throws "must be called on client thread" unless it is called inside runOnClientThreadOptional. The test checks that the walker receives the location. With the fix stashed, this test fails, so it reproduces the bug.
    • missingLocationFailsTaskWithoutWalking: a null location completes the task with false and never calls the walker.
  • ./gradlew test --tests 'net.runelite.client.plugins.microbot.cluesolver.*': all cluesolver tests pass (Cipher 2/2, Anagram 3/3, RequirementHandler 5/5).
  • ./gradlew build -PpluginList=ClueSolverPlugin: BUILD SUCCESSFUL, and PluginManifestVersionTest passes.

Known gaps

  • Other cluetasks also call getLocation from the executor (Cryptic, Emote, FairyRing, HotCold, and the Anagram, Coordinate, Map and Music constructors). Their RuneLite location providers only use static data or getVarbitValue, so they don't hit this assertion today. They were left alone to keep this PR small.
  • Review found the same kind of bug at a different call: AnagramClueTask.handleDialogue calls clue.getAnswer(...) from a background executor. For the 'EEK ZERO OP' anagram, the answer provider calls Quest.EAGLES_PEAK.getState. This hasn't shown up in telemetry yet and is tracked as a follow-up.
  • Not tested live in the game client.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

…(1.0.8)

CipherClueTask.walkToLocation runs on the script executor and called
CipherClue.getLocation directly. For the Eluned cipher the location provider
calls Quest.getState, which asserts the client thread and threw
IllegalStateException, failing the task. Resolve the location through
runOnClientThreadOptional and keep the null check for a clean failure.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV
@chsami
chsami merged commit c0ad2fb into development Oct 19, 2026
1 check 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