Pair Programming with an AI: Debugging Profile Picture Uploads with Claude-3.7
Posted March 2, 2025 by Gowri Shankar ‐ 9 min read
I’ve been stuck on a problem for a while now. You know that kind of bug—the one that refuses to budge no matter how many times you rewrite the code, tweak the request payload, or double-check the backend logs. Today, I decided to try something different. Instead of debugging alone, I brought in a peer programmer—except, this time, my partner wasn’t human. Enter Claude-3.7 Sonnet-Thinking—an AI that didn’t just spit out code snippets but actually worked through the problem like a real collaborator. And trust me, this thing wasn’t just suggesting fixes—it was thinking, iterating, making mistakes, correcting them, and even rewriting parts of my backend and frontend in an attempt to solve the issue. For the first time, I felt like I was debugging with an AI, not just using one.
Note: You can also listen to the podcast experience of this blog in Spotify.
The Problem and the Solution
React Native, FastAPI, and the Mysterious 422/400 Error
This should have been simple.
- Our React web app uploads profile pictures just fine. 🚀
- Our React Native (Expo) app? Not so much. ❤️🩹
Every request from React Native hit a 422 validation error, and then, after some tweaks, a 400 error. FastAPI wasn’t happy with what it was receiving, but the same backend endpoint worked perfectly for web uploads.
This is where things got weird. Same API, different clients, different results. That’s the kind of bug that keeps you up at night.
Debugging with Claude-3.7: A Different Kind of Pair Programming
If I were debugging this with a human, we’d be throwing ideas at the wall, trying different things, adding print statements everywhere. Claude-3.7? It did exactly that—except it never got tired, never got frustrated, and never once suggested, “Bro, let’s take a break and grab coffee.”
It started by analyzing the request payloads, comparing what React Web was sending vs. what React Native was sending.
First hypothesis: Maybe React Native isn’t formatting the FormData properly.
It rewrote the frontend upload logic. Still failed.
Next attempt: Could be a CORS issue or a missing header?
Adjusted the headers. Still failed.
Then the “Aha!” moment:
Claude combed through the FastAPI endpoint definition and suddenly suggested:
“Maybe the backend is expecting a different parameter name?”
And there it was.
A Small Change, A Big Difference
1. Changing file to photo
The FastAPI backend expected the field name to be photo, but React Native was sending it as file.
A one-word change in the frontend, and suddenly… Boom! The 400 error disappeared.
2. Handling File Uploads Properly in React Native
Unlike React web, where file uploads are straightforward, React Native needed special handling:
- Constructing FormData the right way
- Implementing fallback methods in case uploads failed
- Adding blob-based handling as a last-resort
3. iOS-Specific Fixes
iOS stores assets using ph:// URIs (which cannot be directly uploaded), so we had to:
- Copy the file to a temporary location before upload
- Use platform-specific headers
4. Backend Enhancements
- Improved logging to track failures better
- Added file content validation before processing requests
- Handled HTTP exceptions more gracefully
5. Debugging Like a Pro
To ensure this never happens again, we:
- Created a debugging endpoint to verify uploaded files
- Logged detailed request payloads
- Implemented pre-upload validation to catch errors early
🚀 Final result? File uploads now work smoothly on both React Web and React Native.
The Experience
Déjà Vu: A Flashback to Debugging with Aman
At one point, I paused. This whole experience felt strangely familiar.
It took me back to a late evening, when we were drowning in Sentry logs from a backend issue. jaR was panicked(as usual), and we had no clear leads. Just as I was about to log off, Aman pinged me:
“Want to peer debug? Let’s go deep on Backend via Sentry Logs and Application Logger in GCP.”
And so we did. Hours of debugging, testing random theories, going down rabbit holes—until finally, we cracked the issue.
What’s crazy? This session with Claude felt exactly the same.
With Aman, we bounced ideas, shared frustrations, b****ed about everything under the sun and finally celebrated when we figured it out. With Claude? No celebrations, no high-fives—just a quiet resolution to the problem. But the experience was eerily similar.
AI as a Debugging Partner: What I Learned
This wasn’t just about fixing a file upload issue. It was about how AI is evolving from being a tool to becoming a teammate.
Here’s what I learned:
- Claude doesn’t just generate code—it iterates, tests, and refines solutions like a human would.
- It makes mistakes, learns from them, and even rewrites parts of the code dynamically.
- It doesn’t just give one answer—it explores different approaches until something works.
But AI isn’t perfect. It lacks intuition, creative problem-solving, and the ability to step back and rethink the whole approach. It’s a powerful assistant, but it still needs a human in the loop.
Conclusion
The Future: AI + Humans = The Ultimate Debugging Team
Pair programming with AI feels like a glimpse into the future of software development. Instead of just asking for help, we can now collaborate with AI in real-time—debugging, problem-solving, and even optimizing code together.
What’s next?
- AI that remembers past debugging sessions and learns from experience.
- AI that understands business logic, not just code syntax.
- AI that seamlessly integrates into CI/CD pipelines, automatically debugging issues before they hit production.
For now, Claude-3.7 Sonnet-Thinking gave me one of the best debugging sessions I’ve had in a long time.
Would I pair-program with AI again?
Absolutely.
What’s Your Take?
Have you ever pair-programmed with an AI? Did it feel like a real collaborator, or just another debugging tool? Let’s discuss in the comments! 🚀
Annex
Array of Upload Methods
It created an array of upload methods to attempt, each addressing a different approach to uploading the image. Approximately 500 lines of code. The array was defined as follows:
const uploadMethods = [
{
name: 'Primary FormData method',
method: () => primaryUploadMethod(imageAsset),
},
{
name: 'Alternative FormData method',
method: () => alternativeUploadMethod(imageAsset),
},
{
name: 'Blob upload method',
method: () => blobUploadMethod(imageAsset),
},
]
Primary Upload Method vs. Alternative Upload Method
File Extension Handling
- Primary method extracts the file extension and defaults to .jpg if invalid or too long.
- Alternative method always assumes .jpg.
iOS File Handling
- Primary method only converts ph:// URIs to temporary files.
- Alternative method converts both ph:// and file:// URIs for iOS.
File Accessibility Check
- Alternative method explicitly checks file accessibility using FileSystem.getInfoAsync() and attempts to read a sample (Base64) for validation.
FormData Metadata
- Alternative method includes additional metadata (device, uriType, timestamp) in FormData for debugging.
Content-Type Handling
- Primary method always sets “Content-Type”: “multipart/form-data”.
- Alternative method omits “Content-Type” for Android but explicitly sets it for iOS.
Error Handling
- Alternative method wraps errors in try-catch and returns null instead of throwing exceptions.
Primary Upload Method vs. Blob Upload Method
File Content Handling
- Primary method directly appends the file to FormData.
- Blob method reads the file as a Base64 string and constructs a multipart request manually.
Multipart FormData Construction
- Primary method relies on FormData.append().
- Blob method manually builds the request body with a custom boundary.
File Accessibility for iOS
- Blob method always copies the file to a temporary location for iOS.
Explicit File Content Validation
- Blob method throws an error if fileContent is empty.
Header Differences
- Blob method manually constructs Content-Type with a dynamic boundary.
Manual Body Construction
- Blob method manually structures the multipart request with boundary markers instead of relying on FormData.
Spot File Access Issues
It wrote a method to diagnose file access issues by verifying the existence, readability, and uploadability of an image file before using it in an actual upload process.
Key Functions:
- File Existence & Size Check: Uses FileSystem.getInfoAsync() to verify if the file exists and retrieve its size.
- Readability Test (iOS-Specific): Attempts to read the first 100 bytes of the file to detect permission issues.
- Debug Upload Attempt: Sends the file to a dedicated debug endpoint to validate its accessibility and server-side handling.
// Debug method to check file access issues
const debugFileAccess = async (imageAsset: ImagePicker.ImagePickerAsset) => {
try {
console.log('Debugging file access for:', imageAsset.uri)
// Check if file exists and is readable
const fileInfo = await FileSystem.getInfoAsync(imageAsset.uri, {
size: true,
})
console.log(
'File exists:',
fileInfo.exists,
fileInfo.exists && 'size' in fileInfo
? `File size: ${fileInfo.size}`
: 'Size not available',
)
if (!fileInfo.exists) {
console.error('File does not exist at path:', imageAsset.uri)
throw new Error('File does not exist')
}
// On iOS, try to read file content to check permissions
if (Platform.OS === 'ios') {
try {
const firstBytes = await FileSystem.readAsStringAsync(
imageAsset.uri,
{
encoding: FileSystem.EncodingType.Base64,
length: 100, // Just read first 100 bytes to verify access
position: 0,
},
)
console.log(
'Successfully read first bytes of file, length:',
firstBytes.length,
)
} catch (readError: any) {
console.error('Error reading file content:', readError)
throw new Error(
`File exists but cannot be read: ${readError.message || 'Unknown error'}`,
)
}
}
// Continue with debug endpoint call
try {
const formData = new FormData()
// Create file object with proper name and type
const extension = (imageAsset.uri.split('.').pop() || '').toLowerCase()
const fileName = `debug_photo_${Date.now()}.${extension || 'jpg'}`
const mimeType = extension === 'png' ? 'image/png' : 'image/jpeg'
console.log('Debug upload details:', {
fileName,
mimeType,
uri: imageAsset.uri,
})
// @ts-ignore - React Native's FormData implementation is compatible with this usage
formData.append('photo', {
uri: imageAsset.uri,
type: mimeType,
name: fileName,
})
// Send the file to the debug endpoint
const response = await fetch(
`${OpenAPI.BASE}/api/v1/users/${user.id}/photo/debug`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${OpenAPI.TOKEN}`,
Accept: 'application/json',
},
body: formData,
},
)
const debugResult = await response.json()
console.log('Debug endpoint result:', debugResult)
return debugResult
} catch (apiError) {
console.error('Error calling debug endpoint:', apiError)
throw apiError
}
} catch (error: any) {
console.error('Debug access error:', error)
Alert.alert(
'Debug Error',
`Failed to access file: ${error.message || 'Unknown error'}`,
)
return null
}
}
Debug Logs
- Image Selection & File Properties: The image picker successfully selected an image with a size of ~1.5MB and confirmed file existence before upload.
- Initial Upload Attempts Failed: Multiple attempts using FormData (primary and alternative methods) failed with a 400 error due to “error parsing the body”.
- Alternative File Handling Tried: The app copied the image to a temporary location and verified file integrity but still failed using alternative FormData upload.
- Blob Upload Method Succeeded: The file was read successfully and manually sent as a blob, which resulted in a successful 200 response.
- Final Upload Success: The uploaded profile picture URL was received, confirming the image was stored in Google Cloud Storage, and user profile updates were reflected.
LOG Image properties from picker: {"fileName": "IMG_0005.JPG", "fileSize": 1544221, "height": 2002, "platform": "ios", "type": "image", "uri": "file:///Users/shankar/Library/Developer/CoreSimulator/Devices/CAA2A3F4-5A23-4255-8F89-9C3B3DB16DA2/data/Containers/Data/Application/EFA48B5A-BD0C-4E6E-9963-2FD29A051DDA/Library/Caches/ImagePicker/73A07125-782A-4847-8205-B6524BCD4E9B.jpg", "width": 3000}
LOG Attempting to debug file access before upload
LOG Debugging file access for: file:///Users/shankar/Library/Developer/CoreSimulator/Devices/CAA2A3F4-5A23-4255-8F89-9C3B3DB16DA2/data/Containers/Data/Application/EFA48B5A-BD0C-4E6E-9963-2FD29A051DDA/Library/Caches/ImagePicker/73A07125-782A-4847-8205-B6524BCD4E9B.jpg
LOG File exists: true File size: 1544221
LOG Successfully read first bytes of file, length: 136
LOG Debug upload details: {"fileName": "debug_photo_1740929135140.jpg", "mimeType": "image/jpeg", "uri": "file:///Users/shankar/Library/Developer/CoreSimulator/Devices/CAA2A3F4-5A23-4255-8F89-9C3B3DB16DA2/data/Containers/Data/Application/EFA48B5A-BD0C-4E6E-9963-2FD29A051DDA/Library/Caches/ImagePicker/73A07125-782A-4847-8205-B6524BCD4E9B.jpg"}
LOG Debug endpoint result: {"detail": "There was an error parsing the body"}
LOG Debug result before upload: {"detail": "There was an error parsing the body"}
LOG Attempting upload using: Primary FormData method
LOG Trying primary upload method
LOG Upload details: {"fileName": "mobile_photo_1740929135179.jpg", "formDataEntries": "photo", "mimeType": "image/jpeg", "uri": "file:///Users/shankar/Library/Developer/CoreSimulator/Devices/CAA2A3F4-5A23-4255-8F89-9C3B3DB16DA2/data/Containers/Data/Application/EFA48B5A-BD0C-4E6E-9963-2FD29A051DDA/Library/Caches/ImagePicker/73A07125-782A-4847-8205-B6524BCD4E9B.jpg"}
LOG Upload response status: 400
ERROR Upload failed with status: 400 {"detail":"There was an error parsing the body"}
ERROR Error with Primary FormData method: [Error: Upload failed: 400 {"detail":"There was an error parsing the body"}]
LOG Attempting upload using: Alternative FormData method
LOG Trying alternative upload method
LOG iOS photo library asset detected, copying to temporary file
LOG File copied to temporary location: file:///Users/shankar/Library/Developer/CoreSimulator/Devices/CAA2A3F4-5A23-4255-8F89-9C3B3DB16DA2/data/Containers/Data/Application/EFA48B5A-BD0C-4E6E-9963-2FD29A051DDA/Documents/temp_1740929135198.jpg
LOG Alternative method file info: {"exists": true, "isDirectory": false, "md5": "2fc420d9e6e8229b460a05d334432050", "modificationTime": 1740929135.098648, "size": 1544221, "uri": "file:///Users/shankar/Library/Developer/CoreSimulator/Devices/CAA2A3F4-5A23-4255-8F89-9C3B3DB16DA2/data/Containers/Data/Application/EFA48B5A-BD0C-4E6E-9963-2FD29A051DDA/Documents/temp_1740929135198.jpg"}
LOG Successfully read sample of file, length: 1368
LOG Sending alternative upload request
LOG Alternative upload response status: 400
ERROR Alternative upload error: [Error: Alternative upload failed: 400 {"detail":"There was an error parsing the body"}]
LOG Alternative FormData method method returned no result
LOG Attempting upload using: Blob upload method
LOG Trying blob-based upload method
LOG File copied to temporary location for blob method: file:///Users/shankar/Library/Developer/CoreSimulator/Devices/CAA2A3F4-5A23-4255-8F89-9C3B3DB16DA2/data/Containers/Data/Application/EFA48B5A-BD0C-4E6E-9963-2FD29A051DDA/Documents/temp_1740929135231.jpg
LOG File content read successfully, length: 2058964
LOG Sending manual blob upload request with content length: 2059157
LOG Blob upload response status: 200
LOG Blob upload successful: {...}
LOG Upload successful using: Blob upload method