fix: prevent interview crash from incomplete data extraction#498
Conversation
Add validation to interview creation endpoint to prevent crashes when candidateDetails or jobDescription are null during extraction. Update types to reflect nullable fields and add null checks in session context.
Implement conditional staleTime in useJob and new useInterview hook to automatically refetch when candidateDetails or jobDescription are missing. Update UI to show loading states during data extraction.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Claude finished @bhekanik's task —— View job Todo List
Quick Wins
Good Patterns
Recommendation🚢 SHIP IT - Solid fix that works for 1000+ users, no security issues |
There was a problem hiding this comment.
Pull Request Overview
This PR fixes a critical race condition where users could start interviews before AI extraction of candidate details and job descriptions completed, causing the app to crash with null reference errors. The fix implements a multi-layered defense strategy using conditional caching, API validation, and UI feedback.
Key changes:
- Implemented conditional
staleTimein React Query hooks to prevent caching incomplete data - Added backend validation to block interview creation when data extraction is incomplete
- Updated UI to show loading states and disable the start button during extraction
Reviewed Changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/hooks/useJob.ts | Added dynamic staleTime logic to prevent caching incomplete job data |
| src/hooks/useInterview.ts | New hook with similar conditional caching for interview data |
| src/hooks/useCustomisedSystemPrompt.tsx | Refactored to use the new useInterview hook |
| src/components/interview-placeholder.tsx | Added UI feedback and disabled state during data extraction |
| src/components/interview-container/voice-provider-config.ts | Updated types to handle null values and added validation guards |
| src/app/api/interviews/route.ts | Added backend validation to prevent interview creation with incomplete data |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| // Dynamic staleTime: if data is incomplete, mark as stale immediately | ||
| // This forces refetch on every useJob call until extraction completes | ||
| staleTime: (query) => { | ||
| const job = query.state.data; | ||
|
|
||
| // If no data yet, consider immediately stale | ||
| if (!job?.data) return 0; | ||
|
|
||
| // Check if candidate details and job description are extracted | ||
| const isDataComplete = job.data.candidateDetails && job.data.jobDescription; | ||
|
|
||
| // If incomplete: staleTime = 0 (refetch on every mount) | ||
| // If complete: staleTime = 30s (normal caching) | ||
| return isDataComplete ? 30000 : 0; | ||
| }, |
There was a problem hiding this comment.
The comment on line 22 is misleading. Setting staleTime to 0 doesn't force refetch 'on every useJob call' but rather marks the data as immediately stale, causing refetch when the component mounts or remounts. Consider clarifying this behavior.
| }, | ||
| enabled: !!interviewId, | ||
| // Dynamic staleTime: if job data is incomplete, mark as stale immediately | ||
| // This forces refetch on every useInterview call until extraction completes |
There was a problem hiding this comment.
Same misleading comment as in useJob.ts. The staleTime setting doesn't force refetch 'on every useInterview call' but marks data as stale for refetch on mount/remount.
| // This forces refetch on every useInterview call until extraction completes | |
| // This marks data as stale for refetch on mount/remount until extraction completes |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
| const job = await jobsRepo.getById(jobId); | ||
| return job; | ||
| }, | ||
| staleTime: 30000, // Cache valid for 30s, allows instant load from cache while background refetch happens | ||
| // Dynamic staleTime: if data is incomplete, mark as stale immediately | ||
| // This forces refetch on every useJob call until extraction completes | ||
| staleTime: (query) => { | ||
| const job = query.state.data; | ||
|
|
||
| // If no data yet, consider immediately stale | ||
| if (!job?.data) return 0; | ||
|
|
||
| // Check if candidate details and job description are extracted | ||
| const isDataComplete = job.data.candidateDetails && job.data.jobDescription; | ||
|
|
||
| // If incomplete: staleTime = 0 (refetch on every mount) | ||
| // If complete: staleTime = 30s (normal caching) | ||
| return isDataComplete ? 30000 : 0; | ||
| }, |
There was a problem hiding this comment.
Conditional staleTime never triggers refetch
The new logic in useJob (and similarly useInterview) tries to fix stale job data by setting staleTime to 0 whenever candidate details or the job description are missing. In TanStack Query a staleTime of 0 only marks the cached data as stale; it does not automatically refetch while the component remains mounted. When a user opens the interview placeholder before extraction completes, the first fetch returns null values and isDataBeingExtracted in InterviewPlaceholder stays true, leaving the “Start Interview” button permanently disabled even after the extraction finishes on the server unless the user manually refocuses the window or reloads. The original issue (stale cached data) is replaced with an indefinite loading state. A polling interval or explicit invalidation is still needed to refresh the data once the extraction completes.
Useful? React with 👍 / 👎.
Auto-formatting cleanup from Biome linter run during build. No logic changes - just code style consistency.
|
Claude finished @bhekanik's task —— View job Todo List
Quick Wins
Good Patterns
Recommendation🚢 SHIP IT - Solid fix that works for 1000+ users, no security issues |
staleTime alone doesn't trigger refetches while component is mounted. Need refetchInterval to actively poll for extraction completion. Why both are needed: - staleTime: Prevents long-term caching of incomplete data - refetchInterval: Detects when server extraction finishes Without polling, button stays disabled until manual reload/refocus.
|
Claude finished @bhekanik's task —— View job Todo List
Quick Wins
Good Patterns
Recommendation🚢 SHIP IT - Solid fix that works for 1000+ users, no security issues |
Update comments to accurately explain: - staleTime only triggers refetch on mount/remount/refocus - refetchInterval needed for active polling while mounted - Without polling, button stays disabled until manual reload Fixes misleading comment about staleTime forcing refetch on every call.
Match useJob comment improvements for consistency: - Clarify staleTime only refetches on mount/remount/refocus - Explain refetchInterval needed for mounted component polling - Consistent documentation across both hooks
|
Claude finished @bhekanik's task —— View job Todo List
Quick Wins
Good Patterns
Recommendation🚢 SHIP IT - Solid fix that works for 1000+ users, no security issues |
Summary
Fixes a critical bug where the interview page would crash with "Cannot read properties of null (reading 'candidateDetails')" error when users tried to start an interview before the AI extraction of candidate details and job description was complete.
The Problem
When a job is created, two async AI extraction processes start:
These can take 5-10 seconds. However, React Query was caching the job data with
staleTime: 30000ms(30 seconds), meaning:candidateDetails: nulljob.candidateDetails.nameThe Solution
Defense in Depth - Fixed at 4 layers:
1. API Validation (Backend Safety)
src/app/api/interviews/route.tscandidateDetailsorjobDescriptionare null2. Smart Caching (Cache Layer)
src/hooks/useJob.tsandsrc/hooks/useInterview.tsstaleTime = 0(never cache)staleTime = 30000(normal 30s caching)3. UI Feedback (UX Layer)
src/components/interview-placeholder.tsx4. Type Safety (Code Safety)
src/components/interview-container/voice-provider-config.tsnullcreateSessionContextTechnical Details
Key Insight from Code Review
Initially implemented polling (
refetchInterval), but realized a cleaner approach:The conditional
staleTimeapproach is:Files Changed
Backend:
src/app/api/interviews/route.ts- Added validationHooks:
src/hooks/useJob.ts- Conditional staleTimesrc/hooks/useInterview.ts- New hook with same logicsrc/hooks/useCustomisedSystemPrompt.tsx- Use new hookUI:
src/components/interview-placeholder.tsx- Loading statessrc/components/interview-container/voice-provider-config.ts- Type safetyTesting
Type of Change
Impact
Checklist