{"record":{"id":"c90af05a23333728","repo":"hcengineering/platform","slug":"reaction-not-found","errorCode":null,"errorMessage":"Reaction not found.","messagePattern":"Reaction not found\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server-plugins/rating/src/index.ts","lineNumber":108,"sourceCode":"      )\n    ) {\n      throw new Error('Duplicate emoji reaction is not allowed.')\n    }\n  }\n\n  private async validateReactionUpdate (ctx: MeasureContext, upd: TxUpdateDoc<DocReaction>): Promise<void> {\n    if (upd.operations.reactionType !== undefined) {\n      throw new Error('Modifications of reaction type are not allowed.')\n    }\n    // Find current reaction tried to be modified\n    const current = (\n      await this.provideFindAll(ctx, rating.class.DocReaction, {\n        _id: upd.objectId,\n        _class: upd.objectClass\n      })\n    ).shift()\n    if (current === undefined) {\n      throw new Error('Reaction not found.')\n    }\n    if (upd.operations.value === undefined || upd.operations.value < 0 || upd.operations.value > 10) {\n      throw new Error('Reaction value modification is required.')\n    }\n  }\n\n  private async validateReactionRemove (ctx: MeasureContext, upd: TxRemoveDoc<DocReaction>): Promise<void> {\n    // Find current reaction tried to be modified\n    // const current = (\n    //   await this.provideFindAll(ctx, rating.class.DocReaction, {\n    //     _id: upd.objectId,\n    //     _class: upd.objectClass\n    //   })\n    // ).shift()\n    // if (current === undefined) {\n    // No error, already removed\n    // }\n    // if (current.reactionType === ReactionKind.Star) {","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/server-plugins/rating/src/index.ts#L90-L126","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Check the reaction still exists (findAll with _id and _class) before sending the update tx","Refresh client state after a removal so stale reaction ids are not updated","Make the update idempotent: catch this error and treat it as a no-op if the reaction was already removed","Verify _id and _class in the update payload match the original DocReaction"],"exampleFix":"// before\nawait tx.updateDoc(DocReaction.class, { _id: staleId }, { value: 5 })\n// after\nconst current = await findAll(DocReaction.class, { _id: staleId })\nif (current.length > 0) {\n  await tx.updateDoc(DocReaction.class, { _id: staleId }, { value: 5 })\n}","handlingStrategy":"try-catch","validationCode":"const exists = (await findAll(ctx, rating.class.DocReaction, { _id: reactionId, _class: reactionClass })).length > 0\nif (!exists) throw new SkipTx()","typeGuard":"const isReactionUpdatable = async (ctx: any, id: string, cls: string) =>\n  (await findAll(ctx, rating.class.DocReaction, { _id: id, _class: cls })).length > 0","tryCatchPattern":"try {\n  await tx.updateDoc(reactionClass, { _id: id }, { value })\n} catch (err) {\n  if ((err as Error).message === 'Reaction not found.') return // treat as idempotent no-op\n  throw err\n}","preventionTips":["Re-fetch the reaction immediately before updating instead of using cached ids","Treat reaction updates as idempotent and swallow not-found on replay","Use cascading deletes so clients are notified when reactions are removed","Log objectId/_class when this error occurs to spot stale-class bugs"],"tags":["tx-validation","stale-data","race-condition"],"backgroundTag":"document-not-found","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}