Skip to content

Commit 599b388

Browse files
joshwilhelmiclaude
andcommitted
fix: Prevent stale token-usage data from updating state on session switch
- Add AbortController to cancel in-flight token-usage requests when session/project changes - Capture session/project IDs before fetch and verify they match before updating state - Handle AbortError gracefully without logging as error - Prevents race condition where old session data overwrites current session's token budget 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 41558a9 commit 599b388

File tree

1 file changed

+29
-2
lines changed

1 file changed

+29
-2
lines changed

src/components/ChatInterface.jsx

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2735,12 +2735,32 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
27352735
return;
27362736
}
27372737

2738+
// Create AbortController to cancel in-flight requests when session/project changes
2739+
let abortController = new AbortController();
2740+
27382741
const fetchTokenUsage = async () => {
2742+
// Abort previous request if still in flight
2743+
if (abortController.signal.aborted) {
2744+
abortController = new AbortController();
2745+
}
2746+
2747+
// Capture current session/project to verify before updating state
2748+
const currentSessionId = selectedSession.id;
2749+
const currentProjectPath = selectedProject.path;
2750+
27392751
try {
2740-
const url = `/api/sessions/${selectedSession.id}/token-usage?projectPath=${encodeURIComponent(selectedProject.path)}`;
2752+
const url = `/api/sessions/${currentSessionId}/token-usage?projectPath=${encodeURIComponent(currentProjectPath)}`;
27412753
console.log('📊 Fetching token usage from:', url);
27422754

2743-
const response = await authenticatedFetch(url);
2755+
const response = await authenticatedFetch(url, {
2756+
signal: abortController.signal
2757+
});
2758+
2759+
// Only update state if session/project hasn't changed
2760+
if (currentSessionId !== selectedSession?.id || currentProjectPath !== selectedProject?.path) {
2761+
console.log('⚠️ Session/project changed during fetch, discarding stale data');
2762+
return;
2763+
}
27442764

27452765
if (response.ok) {
27462766
const data = await response.json();
@@ -2752,6 +2772,11 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
27522772
setTokenBudget({ used: 0, total: parseInt(import.meta.env.VITE_CONTEXT_WINDOW) || 160000, percentage: 0 });
27532773
}
27542774
} catch (error) {
2775+
// Don't log error if request was aborted (expected behavior)
2776+
if (error.name === 'AbortError') {
2777+
console.log('🚫 Token usage fetch aborted (session/project changed)');
2778+
return;
2779+
}
27552780
console.error('Failed to fetch token usage:', error);
27562781
// Reset to zero on error
27572782
setTokenBudget({ used: 0, total: parseInt(import.meta.env.VITE_CONTEXT_WINDOW) || 160000, percentage: 0 });
@@ -2773,6 +2798,8 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
27732798
document.addEventListener('visibilitychange', handleVisibilityChange);
27742799

27752800
return () => {
2801+
// Abort any in-flight requests when effect cleans up
2802+
abortController.abort();
27762803
clearInterval(interval);
27772804
document.removeEventListener('visibilitychange', handleVisibilityChange);
27782805
};

0 commit comments

Comments
 (0)