sickn33/agentic-awesome-skills · warning · Error

Already liked

Error message

Already liked

What it means

This is example code in the Firebase skill: a Firestore transaction that 'likes' a post reads the like document and throws new Error('Already liked') if it exists, aborting the transaction. The throw acts as a concurrency-safe duplicate check — the transaction rejects rather than double-incrementing likeCount.

Source

Thrown at skills/firebase/SKILL.md:471

  await batch.commit();
  return postRef.id;
}

// Transaction - read and write atomically
async function likePost(postId, userId) {
  return runTransaction(db, async (transaction) => {
    const postRef = doc(db, 'posts', postId);
    const likeRef = doc(db, 'posts', postId, 'likes', userId);

    const postSnap = await transaction.get(postRef);
    if (!postSnap.exists()) {
      throw new Error('Post not found');
    }

    const likeSnap = await transaction.get(likeRef);
    if (likeSnap.exists()) {
      throw new Error('Already liked');
    }

    // Increment like count and add like document
    transaction.update(postRef, {
      likeCount: increment(1)
    });

    transaction.set(likeRef, {
      userId,
      createdAt: serverTimestamp()
    });

    return postSnap.data().likeCount + 1;
  });
}

### Social Login (Google, GitHub, etc.)

View on GitHub (pinned to 58d857988f)

Solutions

  1. Treat 'Already liked' as an idempotency signal: catch it and set the UI to the liked state instead of showing an error
  2. Disable the like button while the transaction is in flight
  3. Branch the catch: e.message === 'Already liked' separately from 'Post not found'
  4. Optionally pre-check the like document's existence, but keep the transaction check as the race-safe source of truth

Example fix

// before
await like(postId, userId);

// after
try {
  await like(postId, userId);
  setLiked(true);
} catch (e) {
  if (e.message === 'Already liked') setLiked(true);
  else throw e;
}
// plus: <button disabled={liked || pending} onClick={like}>Like</button>
Defensive patterns

Strategy: try-catch

Validate before calling

const likeSnap = await getDoc(doc(db, 'posts', postId, 'likes', userId));
if (likeSnap.exists()) { setLiked(true); return; }
await like(postId, userId);

Try / catch

try { await like(postId, userId); }
catch (e) {
  if (e.message === 'Already liked') { setLiked(true); return; }
  if (e.message === 'Post not found') { showError('Post removed'); return; }
  throw e;
}

Prevention

When it happens

Trigger: Calling the like() transaction twice for the same user and post: on the second run likeSnap.exists() is true and it throws. Double-taps firing two concurrent calls — one commits, the other re-runs and sees the like.

Common situations: Double-click/double-tap before the UI disables the button; client retry after a network hiccup where the first transaction actually committed; the same handler wired from multiple components.


AI-assisted analysis of sickn33/agentic-awesome-skills@58d857988f (2026-08-26). Data as JSON: /api/errors/a064ffac113743b3. Report an issue: GitHub.