DrKLO/Telegram · critical · IllegalArgumentException
op should be remove or update.{}
Error message
op should be remove or update.{} What it means
Thrown in the inner loop of dispatchAndUpdateViewHolders where each position of a multi-position op is walked. The switch only handles UPDATE (positionMultiplier 1) and REMOVE (positionMultiplier 0); any other command value is rejected. This is a defense-in-depth guard: by the time code reaches here the op.cmd has already been filtered at the method entry (error 0), so reaching the default means the UpdateOp object was mutated after submission or a code path built an op with an unexpected cmd constant. It signals corrupted internal state rather than a direct caller mistake.
Source
Thrown at TMessagesProj/src/main/java/androidx/recyclerview/widget/AdapterHelper.java:294
// handle each pos 1 by 1 to ensure continuity. If it breaks, dispatch partial
// TODO Since move ops are pushed to end, we should not need this anymore
int tmpStart = updatePositionWithPostponed(op.positionStart, op.cmd);
if (DEBUG) {
Log.d(TAG, "pos:" + op.positionStart + ",updatedPos:" + tmpStart);
}
int tmpCnt = 1;
int offsetPositionForPartial = op.positionStart;
final int positionMultiplier;
switch (op.cmd) {
case UpdateOp.UPDATE:
positionMultiplier = 1;
break;
case UpdateOp.REMOVE:
positionMultiplier = 0;
break;
default:
throw new IllegalArgumentException("op should be remove or update." + op);
}
for (int p = 1; p < op.itemCount; p++) {
final int pos = op.positionStart + (positionMultiplier * p);
int updatedPos = updatePositionWithPostponed(pos, op.cmd);
if (DEBUG) {
Log.d(TAG, "pos:" + pos + ",updatedPos:" + updatedPos);
}
boolean continuous = false;
switch (op.cmd) {
case UpdateOp.UPDATE:
continuous = updatedPos == tmpStart + 1;
break;
case UpdateOp.REMOVE:
continuous = updatedPos == tmpStart;
break;
}
if (continuous) {
tmpCnt++;View on GitHub (pinned to 45ab8f4308)
Solutions
- Audit any custom AdapterHelper.Callback or UpdateOp pool reuse to ensure op.cmd is never mutated post-submission.
- Verify ProGuard/R8 keep rules preserve androidx.recyclerview.widget.UpdateOp field values (do not optimize/inline them).
- Reproduce with RecyclerView updates serialized on the main thread; if it still occurs, the UpdateOp pool is being corrupted by a custom component.
- Align the fork's AdapterHelper source with the matching AndroidX recyclerview version so constants and dispatch logic are consistent.
Defensive patterns
Strategy: validation
Validate before calling
// Validate op.cmd before dispatch (internal fork guard)
static void assertKnownCmd(int cmd) {
if (cmd != UpdateOp.ADD && cmd != UpdateOp.MOVE
&& cmd != UpdateOp.REMOVE && cmd != UpdateOp.UPDATE) {
throw new IllegalStateException("unknown cmd " + cmd);
}
} Prevention
- Keep UpdateOp and AdapterHelper from the same AndroidX release in any fork.
- Add ProGuard/R8 keep rules to prevent constant inlining in the recyclerview widget package.
- Never mutate an UpdateOp after it is submitted to the helper.
When it happens
Trigger: A UpdateOp is recycled/reused and its cmd field is overwritten between obtainUpdateOp and consumption; a custom Callback or pre-processor that alters op.cmd; concurrent modification of the pending queue on multiple threads; an obfuscated/modified build (this is a Telegram fork) that changed UpdateOp constants.
Common situations: ProGuard/R8 stripping or reordering of UpdateOp static final constants such that ADD/MOVE/UPDATE/REMOVE no longer match the values the helper expects; a fork that patched AdapterHelper but not UpdateOp; thread-safety bugs in a custom adapter that re-enters the recycler during prefetch.
Related errors
- Unknown update op type for {}
- should not dispatch add or move for pre layout
- only remove and update ops can be dispatched in first pass
- Cannot call this method unless RecyclerView is computing a l
- {message}
AI-assisted analysis of DrKLO/Telegram@45ab8f4308 (2026-08-14).
Data as JSON: /api/errors/681ea328cdf0a60a.
Report an issue: GitHub.