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
- Treat 'Already liked' as an idempotency signal: catch it and set the UI to the liked state instead of showing an error
- Disable the like button while the transaction is in flight
- Branch the catch: e.message === 'Already liked' separately from 'Post not found'
- 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
- Disable the like button while the transaction is pending
- Treat duplicate-like as success, not failure
- Derive initial liked state from the like document
- Debounce rapid taps on engagement buttons
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.