Technical note / State handling

Clearing stale presenter state after expected errors

A localized CyberChef fix that prevents a previously successful operation from changing how a later operation’s error is presented.

Project
CyberChef
Contribution
PR #2589
Related issue
Issue #2583
Role
PR author
Outcome
Merged 24 June 2026

Presentation state outlived the operation that created it.

CyberChef operations can provide custom presenters for successful output. The recipe stores the most recent successful operation in lastRunOp, and Recipe.present() can later use that operation’s presenter.

Issue #2583 described a sequence where an operation with a presenter succeeded, then a following operation returned an expected OperationError or DishError. The error string was produced correctly, but the earlier operation remained in lastRunOp. As a result, the stale presenter could transform the later error output—for example, wrapping a QR-generation error in a prior PDF presenter.

The success path updated state; expected error paths did not clear it.

Recipe.execute() updated lastRunOp when an operation completed normally. Expected operation errors took early-return paths after writing the error message, but those paths did not reset the stored presenter source.

Presenter operation succeedslastRunOp retainedNext operation errorsOld presenter applied

The bug was therefore not in the error string or the presenter itself. It was an ownership problem: state associated with a successful result remained eligible after the result had been replaced by an error.

Invalidate presenter eligibility when execution returns an error.

PR #2589 sets lastRunOp to null in both expected error branches before returning. This keeps presentation state aligned with the output currently held by the recipe.

// Simplified expected-error path
output = error.message;
lastRunOp = null;
return output;

The change is intentionally small. Successful operations continue to register their presenter, while expected error output is guaranteed to remain plain output.

Reproduce the cross-operation sequence, not one operation in isolation.

The regression test combines an operation with a custom presenter and a following operation that emits the expected error. It verifies the error text and confirms the result does not contain the earlier presenter’s iframe markup.

That sequence matters because neither operation alone demonstrates the failure. The regression exists at the state transition between them.

Cached strategy objects need explicit invalidation rules.

  • When output changes from success to error, clear any formatter or presenter selected by the earlier result.
  • Early-return error paths must restore the same state invariants as the normal path.
  • Regression tests for stale state should exercise a sequence of operations, because isolated unit behavior may remain correct.

Authoritative upstream record.