A cautionary tale about trusting Cursor's AI too much and the importance of understanding your tools A cautionary tale about trusting Cursor's AI too much and the importance of understanding your tools The Setup Recently, I was working on a Web3 authentication flow using React, wagmi, and a Node.js backend. The goal was simple: connect wallet → sign message → verify signature → get JWT token. Sounds straightforward, right? The Problem The authentication flow wasn't working correctly. When users clicked "Sign Message & Login", the backend verification API was being called immediately, before the user had actually signed anything in MetaMask. This was clearly wrong. The AI's "Solution" I asked Cursor's AI assistant to fix this issue. Here's what happened: First Attempt: The Async/Await Mistake // AI's initial "fix" const signature = await signMessage({ message }); // AI's initial "fix" const signature = await signMessage({ message }); The AI assumed signMessage was an async function and tried to await it. This was completely wrong. signMessage Second Attempt: Still Not Getting It // AI's second attempt - still wrong const { signMessage, isPending } = useSignMessage(); // ... later const signature = await signMessage({ message }); // Still trying to await! // AI's second attempt - still wrong const { signMessage, isPending } = useSignMessage(); // ... later const signature = await signMessage({ message }); // Still trying to await! The AI was still treating signMessage as if it returned a Promise, even after I pointed out it wasn't async. signMessage Third Attempt: Finally Understanding the Hook Pattern Only after I explicitly explained that signMessage is a function from a React hook (not an async function) did the AI implement the correct pattern: signMessage // Correct implementation const { signMessage, isPending, data: signature, error: signError } = useSignMessage(); // Use useEffect to listen for signature completion useEffect(() => { if (signature && pendingNonce && address) { handleSignatureComplete(signature, pendingNonce, address); } }, [signature, pendingNonce, address]); // Trigger signing (non-blocking) const handleSignAndLogin = async () => { // ... get nonce signMessage({ message }); // This triggers MetaMask popup // Don't await this - it's not async! }; // Correct implementation const { signMessage, isPending, data: signature, error: signError } = useSignMessage(); // Use useEffect to listen for signature completion useEffect(() => { if (signature && pendingNonce && address) { handleSignatureComplete(signature, pendingNonce, address); } }, [signature, pendingNonce, address]); // Trigger signing (non-blocking) const handleSignAndLogin = async () => { // ... get nonce signMessage({ message }); // This triggers MetaMask popup // Don't await this - it's not async! }; Why This Happened 1. Pattern Recognition vs. Understanding Pattern Recognition vs. Understanding The AI recognized common patterns (async/await for API calls) but didn't understand the specific React hook pattern for useSignMessage. It applied the wrong mental model. useSignMessage 2. Lack of Context Awareness Lack of Context Awareness Even when I mentioned "wagmi hook", the AI didn't connect this to the specific behavior of React hooks that trigger side effects rather than return promises. 3. Overconfidence in Initial Solutions Overconfidence in Initial Solutions The AI presented its first solution with confidence, making it seem like the correct approach. This can lead developers to trust the solution without questioning it. The Correct Solution Here's how the authentication flow should actually work: const { signMessage, isPending, data: signature, error: signError } = useSignMessage(); // Listen for signature completion useEffect(() => { if (signature && pendingNonce && address) { handleSignatureComplete(signature, pendingNonce, address); } }, [signature, pendingNonce, address]); const handleSignAndLogin = async () => { setLoading(true); try { // Get nonce from backend const { data } = await axios.get('/auth/nonce'); const { nonce } = data; // Store nonce for later use setPendingNonce(nonce); // Create message to sign const message = `Sign this message to authenticate: ${nonce}`; // Trigger signing (shows MetaMask popup) signMessage({ message }); } catch (error) { setLoading(false); // Handle error } }; const handleSignatureComplete = async (signature, nonce, address) => { try { // Verify signature with backend const { data: authData } = await axios.post('/auth/verify', { address, signature, nonce }); if (authData.success) { // Store JWT and update UI localStorage.setItem('authToken', authData.token); setUser(authData.user); setIsAuthenticated(true); } } catch (error) { // Handle verification error } finally { setLoading(false); setPendingNonce(null); } }; const { signMessage, isPending, data: signature, error: signError } = useSignMessage(); // Listen for signature completion useEffect(() => { if (signature && pendingNonce && address) { handleSignatureComplete(signature, pendingNonce, address); } }, [signature, pendingNonce, address]); const handleSignAndLogin = async () => { setLoading(true); try { // Get nonce from backend const { data } = await axios.get('/auth/nonce'); const { nonce } = data; // Store nonce for later use setPendingNonce(nonce); // Create message to sign const message = `Sign this message to authenticate: ${nonce}`; // Trigger signing (shows MetaMask popup) signMessage({ message }); } catch (error) { setLoading(false); // Handle error } }; const handleSignatureComplete = async (signature, nonce, address) => { try { // Verify signature with backend const { data: authData } = await axios.post('/auth/verify', { address, signature, nonce }); if (authData.success) { // Store JWT and update UI localStorage.setItem('authToken', authData.token); setUser(authData.user); setIsAuthenticated(true); } } catch (error) { // Handle verification error } finally { setLoading(false); setPendingNonce(null); } }; Conclusion Cursor's AI assistant is a powerful tool, but it's not a senior developer. It can help with: ✅ Code generation ✅ Pattern suggestions ✅ Boilerplate reduction ✅ Documentation ✅ Code generation ✅ Pattern suggestions ✅ Boilerplate reduction ✅ Documentation But it struggles with: ❌ Complex architectural decisions ❌ Domain-specific patterns ❌ Understanding context deeply ❌ Making critical business logic decisions ❌ Complex architectural decisions ❌ Domain-specific patterns ❌ Understanding context deeply ❌ Making critical business logic decisions The key takeaway: Use Cursor's AI as a powerful junior developer that needs constant oversight, not as a replacement for understanding your code and your tools. The key takeaway Always question, always test, and always understand what you're building. The AI might write the code, but you're responsible for making sure it works correctly. Have you had similar experiences with Cursor or other AI coding assistants? Share your stories in the comments below! Have you had similar experiences with Cursor or other AI coding assistants? Share your stories in the comments below!