Skip to content

fix: _Installation update fails when clearing deviceToken - #10454

Open
AdrianCurtin wants to merge 2 commits into
parse-community:alphafrom
AdrianCurtin:fix_installation_device_token_clear
Open

fix: _Installation update fails when clearing deviceToken#10454
AdrianCurtin wants to merge 2 commits into
parse-community:alphafrom
AdrianCurtin:fix_installation_device_token_clear

Conversation

@AdrianCurtin

@AdrianCurtin AdrianCurtin commented May 4, 2026

Copy link
Copy Markdown

Issue

Updating an existing _Installation to clear deviceToken from the client (iOS/Android SDKs, Dashboard, anything sending { deviceToken: { __op: 'Delete' } } or { deviceToken: null }) fails with:

You cannot use [object Object] as a query parameter. {"code":107}
    at transformQueryKeyValue (Adapters/Storage/Mongo/MongoTransform.js:389:11)
    at transformWhere (Adapters/Storage/Mongo/MongoTransform.js:399:17)
    at MongoStorageAdapter.find (Adapters/Storage/Mongo/MongoStorageAdapter.js:580:59)

RestWrite.handleInstallation runs at step 109 of RestWrite.execute, before the field-write loop processes __op operators. The Delete operator object is truthy, so it was being pushed into the $or lookup query as { deviceToken: { __op: 'Delete' } }, which transformQueryKeyValue cannot transform into a Mongo predicate.

Approach

Detect the two clearing shapes (null and { __op: 'Delete' }) at the top of handleInstallation and derive a deviceTokenForLookup value (undefined when clearing). Identification paths use deviceTokenForLookup:

  • the "must specify ID" guard (only matters for create — clearing alone with no other identifier still throws 135)
  • the 64-char iOS lowercase normalization
  • the "no critical change" early-return for queries
  • the $or lookup orQueries push
  • the result.deviceToken match loop
  • the "deviceToken may not be changed" 136 throw
  • both deviceToken-conflict cleanup delQuery blocks

this.data.deviceToken is left untouched, so the actual write step still applies the Delete op and the field is cleared on the row.

Tasks

  • Add tests
  • Add changes to documentation (guides, repository pages, code comments)
  • Add security check
  • Add new Parse Error codes to Parse JS SDK

Summary by CodeRabbit

  • Bug Fixes

    • Fixed clearing iOS device tokens using either deletion or a null value.
    • Cleared tokens are no longer included in push-recipient lookups, while other installation details remain intact.
  • Tests

    • Added coverage for device token removal during installation updates, including updates alongside other field changes.

handleInstallation runs before Parse __op operators are processed, so
when a client cleared deviceToken via { __op: 'Delete' } or null, the
operator object was being pushed into the Mongo $or lookup query and
rejected by transformQueryKeyValue with "You cannot use [object Object]
as a query parameter" (Parse error 107).

Detect the clearing intent and route the identification/lookup paths
through deviceTokenForLookup (undefined when clearing) while leaving
this.data.deviceToken untouched so the field is still cleared on write.

Adds regression specs covering both clearing shapes and the case where
deviceToken is cleared alongside another field update.
Copilot AI review requested due to automatic review settings May 4, 2026 17:35
@parse-github-assistant

Copy link
Copy Markdown

🚀 Thanks for opening this pull request! We appreciate your effort in improving the project. Please let us know once your pull request is ready for review.

Tip

  • Keep pull requests small. Large PRs will be rejected. Break complex features into smaller, incremental PRs.
  • Use Test Driven Development. Write failing tests before implementing functionality. Ensure tests pass.
  • Group code into logical blocks. Add a short comment before each block to explain its purpose.
  • We offer conceptual guidance. Coding is up to you. PRs must be merge-ready for human review.
  • Our review focuses on concept, not quality. PRs with code issues will be rejected. Use an AI agent.
  • Human review time is precious. Avoid review ping-pong. Inspect and test your AI-generated code.

Note

Please respond to review comments from AI agents just like you would to comments from a human reviewer. Let the reviewer resolve their own comments, unless they have reviewed and accepted your commit, or agreed with your explanation for why the feedback was incorrect.

Caution

Pull requests must be written using an AI agent with human supervision. Pull requests written entirely by a human will likely be rejected, because of lower code quality, higher review effort and the higher risk of introducing bugs. Please note that AI review comments on this pull request alone do not satisfy this requirement. Our CI and AI review are safeguards, not development tools. If many issues are flagged, rethink your development approach. Invest more effort in planning and design rather than using review cycles to fix low-quality code.

@coderabbitai

coderabbitai Bot commented May 4, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9a811bf2-7ac5-4929-a3db-791e88c73534

📥 Commits

Reviewing files that changed from the base of the PR and between 7c386e6 and 9042b7e.

📒 Files selected for processing (2)
  • spec/ParseInstallation.spec.js
  • src/RestWrite.js

📝 Walkthrough

Walkthrough

This PR updates _Installation writes so deviceToken: null and { __op: 'Delete' } clear the token without breaking installation identity. Cleared tokens are excluded from lookup, duplicate cleanup, and push-recipient queries. Tests cover clearing and concurrent field updates.

Changes

Installation device-token clearing

Layer / File(s) Summary
Token clearing and lookup integration
src/RestWrite.js
handleInstallation converts null to { __op: 'Delete' }. It uses a lookup-only token for normalization, identification, matching, validation, and duplicate cleanup. Cleared tokens are excluded from device-token queries.
Token clearing test coverage
spec/ParseInstallation.spec.js
Tests cover Delete and null clearing, installation identifier preservation, exclusion from push-recipient queries, and simultaneous deviceType and appVersion updates.

Estimated code review effort: 3 (Moderate) | ~28 minutes

Merge Risk: ⚪ Minimal · up to 9042b

The change narrowly fixes clearing an installation device token while preserving the existing write behavior. No actionable merge-blocking risk remains beyond normal checks and review.


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (1 error)

Check name Status Explanation Resolution
Title check ❌ Error The title uses the required fix: prefix and describes the change, but the subject starts with an underscore instead of a capitalized letter. Change the title to start the subject with a capitalized letter, for example: "fix: Installation update fails when clearing deviceToken".
✅ Passed checks (6 passed)
Check name Status Explanation
Description check ✅ Passed The description includes the required Issue, Approach, and Tasks sections and clearly explains the fix and test coverage.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Security Check ✅ Passed The diff only normalizes null/Delete tokens and excludes the operator from lookup; final updates still enforce ACL/CLP, and no published advisory pattern is introduced.
Engage In Review Feedback ✅ Passed GitHub shows Adrian replied to both Copilot comments, acknowledged the issues, and commit 9042b7e implemented normalization plus push-recipient test coverage.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates _Installation handling so clearing deviceToken during an update no longer feeds a delete/null payload into the installation lookup path, which was causing Mongo query transformation failures.

Changes:

  • Adds a deviceTokenForLookup path in RestWrite.handleInstallation to treat null and { __op: 'Delete' } as non-identifying values during installation matching.
  • Updates installation matching, validation, and dedup lookup branches to use the derived lookup token instead of the raw request payload.
  • Adds regression tests covering deviceToken clearing via delete op, via null, and while updating another field.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/RestWrite.js Adjusts _Installation lookup logic to avoid querying with delete-op/null deviceToken values.
spec/ParseInstallation.spec.js Adds regression tests for clearing deviceToken during installation updates.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/RestWrite.js
Comment on lines +1299 to +1305
// Treat that as "no deviceToken to identify/match by" so we do not feed the
// operator object into the lookup query (which would fail Mongo transform).
const clearingDeviceToken =
this.data.deviceToken === null ||
(typeof this.data.deviceToken === 'object' &&
this.data.deviceToken !== null &&
this.data.deviceToken.__op === 'Delete');

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, this was real. Fixed in 9042b7e.

Confirmed the behavior first: tightening the assertion to expect(results[0].deviceToken).toBeUndefined() failed with Expected null to be undefined, so the null was being persisted. Since $exists: true matches a field that is present and null, the row stayed in the push recipient set.

handleInstallation now collapses the null form onto { __op: 'Delete' } before any of the early returns, so both client forms take the delete path. That covers creates too: on create the Delete operator flattens to undefined in transformUpdateOperator, so the field is simply never set rather than stored as null.

Comment thread spec/ParseInstallation.spec.js Outdated
Comment on lines +605 to +606
expect(results[0].deviceToken == null).toBeTrue();
expect(results[0].installationId).toEqual(installId);

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, the assertion was too loose. deviceToken == null passed for both the stored null and the absent field, which is exactly the distinction that mattered.

Tightened to toBeUndefined(), and added removes an installation cleared via null from push recipients, which builds its query with the real applyDeviceTokenExists helper from src/Push/utils.js rather than restating the filter, asserts the installation is selected while it has a token, clears it with null, and asserts it is no longer selected.

coderabbitai[bot]
coderabbitai Bot previously approved these changes May 4, 2026
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.

2 participants