Awesome Reviewers

Use fail-safe error handling everywhere in long-running / retrying flows: cleanup must be guaranteed, state/cursors must follow the intended retry semantics, and errors must remain diagnosable.

Apply these rules: 1) Cleanup in finally, not best-effort.

2) Decide per error kind: retry, terminal-skip, or bubble.

3) Never “silently succeed” after a failed dispatch.

4) Preserve diagnostics.

5) Classify timeouts/cancellations/quotas correctly.

Example pattern (cursor + fail-safe dispatch):

async function processBatch(items: Item[], cursor: Cursor) {
  let firstError: unknown;
  for (const item of items) {
    try {
      const ok = await dispatch(item);
      if (ok) cursor.markHandled(item.id);
      // advance cursor for success (or terminal skip)
    } catch (err) {
      if (isTerminal(err)) {
        // skip permanently but advance cursor so you don’t wedge
        cursor.markHandled(item.id);
        continue;
      }
      // retryable: do NOT advance cursor/dedup keys
      firstError ??= err;
    }
  }
  if (firstError) throw firstError;
  cursor.advanceToLatest();
  await cursor.save();
}

function isTerminal(err: unknown): boolean {
  // 404/410 patterns; also treat auth/permission without retry-after as terminal
  return err instanceof HttpError && (err.status === 404 || err.status === 410);
}

This standard prevents the common failure modes seen across the discussions: orphaned state from missing cleanup, lost messages from swallowing/“marking handled” on failed dispatch, infinite poll livelocks from not advancing cursor, and operator-blind failures from lost error context.