toeverything/AFFiNE · error · ReplyNotFound

reply_not_found

reply_not_found

Error message

Reply not found.

What it means

GraphQL mutation updateReply (resolver.ts:202) throws ReplyNotFound when service.getReply(input.id) returns null. The reply (comment sub-item) does not exist; the check runs before 'Doc.Comments.Update' permission and before service.updateReply.

Solutions

  1. Refetch the thread and only edit replies still present.
  2. Treat a vanished parent comment as removing all its replies; drop local reply state with it.
  3. Keep the reply editor bound to a live subscription so deleted replies disable editing immediately.
Defensive patterns

Strategy: try-catch

Validate before calling

const replyAlive = (await comments(docId)).some(c => c.replies?.some(r => r.id === input.id));
if (!replyAlive) { /* disable edit action */ }

Type guard

function isReplyNotFound(e: unknown): boolean {
  return (
    e instanceof GraphQLError &&
    (e.extensions?.code === 'reply_not_found' ||
      e.message === 'Reply not found.')
  );
}

Try / catch

try {
  await updateReply({ id, content });
} catch (e) {
  if (isReplyNotFound(e)) {
    // reply (or its parent) is gone: refetch thread, drop stale editor
  } else throw e;
}

Prevention

When it happens

Trigger: Calling updateReply with a reply id that is deleted or unknown - e.g. the parent comment (and its replies) was deleted while the reply editor was open.

Common situations: Editing a reply in a thread that a collaborator deleted (deleting a comment removes its replies); stale reply id from an outdated subscription snapshot.

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18). Data as JSON: /api/errors/88c778163e995138. Report an issue: GitHub.

Appendix: source

Thrown at packages/backend/server/src/core/comment/resolver.ts:202

      ...reply,
      user: {
        id: me.id,
        name: me.name,
        avatarUrl: me.avatarUrl,
      },
    };
  }

  @Mutation(() => Boolean, {
    description: 'Update a reply content',
  })
  async updateReply(
    @CurrentUser() me: UserType,
    @Args('input') input: ReplyUpdateInput
  ) {
    const reply = await this.service.getReply(input.id);
    if (!reply) {
      throw new ReplyNotFound();
    }

    await this.assertPermission(me, reply, 'Doc.Comments.Update');

    await this.service.updateReply(input);
    publishCommentChanged(this.realtime, reply.workspaceId, reply.docId);
    return true;
  }

  @Mutation(() => Boolean, {
    description: 'Delete a reply',
  })
  async deleteReply(@CurrentUser() me: UserType, @Args('id') id: string) {
    const reply = await this.service.getReply(id);
    if (!reply) {
      throw new ReplyNotFound();
    }

View on GitHub (pinned to b4c8548c09)