hcengineering/platform · error

Reaction not found.

Error message

Reaction not found.

What it means

The rating plugin validates every TxUpdateDoc for a DocReaction before applying it. validateReactionUpdate looks up the existing reaction by _id and _class via provideFindAll; if no document matches, the reaction being updated does not exist (or was already removed), so it throws 'Reaction not found.' to prevent updating a phantom document.

Source

Thrown at server-plugins/rating/src/index.ts:108

      )
    ) {
      throw new Error('Duplicate emoji reaction is not allowed.')
    }
  }

  private async validateReactionUpdate (ctx: MeasureContext, upd: TxUpdateDoc<DocReaction>): Promise<void> {
    if (upd.operations.reactionType !== undefined) {
      throw new Error('Modifications of reaction type are not allowed.')
    }
    // Find current reaction tried to be modified
    const current = (
      await this.provideFindAll(ctx, rating.class.DocReaction, {
        _id: upd.objectId,
        _class: upd.objectClass
      })
    ).shift()
    if (current === undefined) {
      throw new Error('Reaction not found.')
    }
    if (upd.operations.value === undefined || upd.operations.value < 0 || upd.operations.value > 10) {
      throw new Error('Reaction value modification is required.')
    }
  }

  private async validateReactionRemove (ctx: MeasureContext, upd: TxRemoveDoc<DocReaction>): Promise<void> {
    // Find current reaction tried to be modified
    // const current = (
    //   await this.provideFindAll(ctx, rating.class.DocReaction, {
    //     _id: upd.objectId,
    //     _class: upd.objectClass
    //   })
    // ).shift()
    // if (current === undefined) {
    // No error, already removed
    // }
    // if (current.reactionType === ReactionKind.Star) {

View on GitHub (pinned to 63e28dc964)

Solutions

  1. Check the reaction still exists (findAll with _id and _class) before sending the update tx
  2. Refresh client state after a removal so stale reaction ids are not updated
  3. Make the update idempotent: catch this error and treat it as a no-op if the reaction was already removed
  4. Verify _id and _class in the update payload match the original DocReaction

Example fix

// before
await tx.updateDoc(DocReaction.class, { _id: staleId }, { value: 5 })
// after
const current = await findAll(DocReaction.class, { _id: staleId })
if (current.length > 0) {
  await tx.updateDoc(DocReaction.class, { _id: staleId }, { value: 5 })
}
Defensive patterns

Strategy: try-catch

Validate before calling

const exists = (await findAll(ctx, rating.class.DocReaction, { _id: reactionId, _class: reactionClass })).length > 0
if (!exists) throw new SkipTx()

Type guard

const isReactionUpdatable = async (ctx: any, id: string, cls: string) =>
  (await findAll(ctx, rating.class.DocReaction, { _id: id, _class: cls })).length > 0

Try / catch

try {
  await tx.updateDoc(reactionClass, { _id: id }, { value })
} catch (err) {
  if ((err as Error).message === 'Reaction not found.') return // treat as idempotent no-op
  throw err
}

Prevention

When it happens

Trigger: Applying a TxUpdateDoc<DocReaction> whose objectId does not match any existing DocReaction with the matching _class — e.g. the reaction was deleted concurrently, the _id/class are wrong, or the update tx is replayed after the reaction was removed.

Common situations: Race condition where two clients remove/update the same reaction; replaying or re-applying a transaction log on a fresh/restore database; client caching a stale reaction id; passing an objectClass that does not match the stored class.

Related errors


AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29). Data as JSON: /api/errors/c90af05a23333728. Report an issue: GitHub.