DrKLO/Telegram · critical · IllegalArgumentException
Unknown update op type for {}
Error message
Unknown update op type for {} What it means
Thrown in consumeUpdatesInOneDeferredPass, the fallback path that applies ALL queued updates in a single pass when no pre-layout is needed. The switch handles ADD, MOVE, REMOVE, UPDATE and rejects anything else. Reaching the default means an UpdateOp in mPendingUpdates has a cmd value outside the four known constants — effectively corrupted state. Like errors 1 and 2, this is an internal invariant guard, not something a normal caller triggers directly.
Source
Thrown at TMessagesProj/src/main/java/androidx/recyclerview/widget/AdapterHelper.java:489
Log.d(TAG, "postponing " + op);
}
mPostponedList.add(op);
switch (op.cmd) {
case UpdateOp.ADD:
mCallback.offsetPositionsForAdd(op.positionStart, op.itemCount);
break;
case UpdateOp.MOVE:
mCallback.offsetPositionsForMove(op.positionStart, op.itemCount);
break;
case UpdateOp.REMOVE:
mCallback.offsetPositionsForRemovingLaidOutOrNewView(op.positionStart,
op.itemCount);
break;
case UpdateOp.UPDATE:
mCallback.markViewHoldersUpdated(op.positionStart, op.itemCount, op.payload);
break;
default:
throw new IllegalArgumentException("Unknown update op type for " + op);
}
}
boolean hasPendingUpdates() {
return mPendingUpdates.size() > 0;
}
boolean hasAnyUpdateTypes(int updateTypes) {
return (mExistingUpdateTypes & updateTypes) != 0;
}
int findPositionOffset(int position) {
return findPositionOffset(position, 0);
}
int findPositionOffset(int position, int firstPostponedItem) {
int count = mPostponedList.size();
for (int i = firstPostponedItem; i < count; ++i) {View on GitHub (pinned to 45ab8f4308)
Solutions
- Keep UpdateOp and AdapterHelper source from the same AndroidX version in the fork.
- Add keep rules for the recyclerview widget package to prevent constant inlining issues under R8.
- Ensure all notify* calls originate from the main thread.
- If the error is reproducible, log the op.toString() at enqueue time to find which component built the bad op.
Defensive patterns
Strategy: validation
Validate before calling
// Reject unknown op before enqueue
static int requireKnownCmd(int cmd) {
switch (cmd) {
case UpdateOp.ADD:
case UpdateOp.MOVE:
case UpdateOp.REMOVE:
case UpdateOp.UPDATE: return cmd;
default: throw new IllegalArgumentException("bad cmd " + cmd);
}
} Prevention
- Validate op.cmd at enqueue time in a fork to fail closer to the source.
- Prevent R8 from merging/inlining UpdateOp constant fields.
- Serialize all adapter updates on the main thread.
When it happens
Trigger: UpdateOp object reuse where cmd was set to an invalid value; a fork that added a new UpdateOp command constant without updating this switch; R8/ProGuard obfuscation collapsing distinct int constants; a custom AdapterHelper.Callback synthesizing malformed ops.
Common situations: Fork drift between UpdateOp.java and AdapterHelper.java; an obfuscation bug in a stripped release build; memory corruption from a native side or Unsafe usage touching the op array; concurrent enqueue of ops from multiple threads without synchronization.
Related errors
- op should be remove or update.{}
- only remove and update ops can be dispatched in first pass
- should not dispatch add or move for pre layout
- Moving more than 1 item is not supported yet
- {} is not within 0 and {}
AI-assisted analysis of DrKLO/Telegram@45ab8f4308 (2026-08-14).
Data as JSON: /api/errors/8beeb954540bdfca.
Report an issue: GitHub.