@@ -40,49 +40,31 @@ export function useCommitTriage(
4040 const [ error , setError ] = useState < string | null > ( null ) ;
4141 const [ summary , setSummary ] = useState < string | null > ( null ) ;
4242 const [ unavailable , setUnavailable ] = useState ( false ) ;
43+ const hasAttemptedRef = useRef ( false ) ;
4344
44- // Track whether the mutation exists at the module level (survives re-renders)
45- let requestTriageMutation : ReturnType < typeof useMutation > | null = null ;
46- let mutationExists = true ;
47- try {
48- // eslint-disable-next-line react-hooks/rules-of-hooks
49- requestTriageMutation = useMutation ( "testSuites:requestTriage" as any ) ;
50- } catch {
51- // Mutation not registered yet — backend not deployed
52- mutationExists = false ;
53- }
45+ // Always call useMutation (React hooks rules) — if the function doesn't
46+ // exist on the backend, the call itself will fail, which we handle below.
47+ const requestTriageMutation = useMutation ( "testSuites:requestTriage" as any ) ;
5448
55- // Once we know the mutation doesn't exist, mark unavailable permanently
56- // (no state update needed on subsequent renders since unavailable is already true)
57- const mutationExistsRef = useRef ( mutationExists ) ;
58- mutationExistsRef . current = mutationExists ;
59-
60- useEffect ( ( ) => {
61- if ( ! mutationExistsRef . current ) {
62- setUnavailable ( true ) ;
63- }
64- } , [ ] ) ;
65-
66- // Reset state when the run IDs change (navigating to a different commit),
67- // but preserve unavailable if the mutation doesn't exist
49+ // Reset state when the run IDs actually change (navigating to a different commit)
6850 const runKey = failedRunIds . join ( "," ) ;
51+ const prevRunKeyRef = useRef ( runKey ) ;
6952 useEffect ( ( ) => {
70- setSummary ( null ) ;
71- setError ( null ) ;
72- setLoading ( false ) ;
73- // Only reset unavailable if the mutation actually exists
74- if ( mutationExistsRef . current ) {
75- setUnavailable ( false ) ;
53+ if ( prevRunKeyRef . current !== runKey ) {
54+ prevRunKeyRef . current = runKey ;
55+ setSummary ( null ) ;
56+ setError ( null ) ;
57+ setLoading ( false ) ;
58+ hasAttemptedRef . current = false ;
59+ // Keep unavailable sticky — if the mutation doesn't exist, it won't
60+ // magically appear when switching commits
7661 }
7762 } , [ runKey ] ) ;
7863
7964 const requestTriage = useCallback ( ( ) => {
80- if ( failedRunIds . length === 0 || unavailable ) return ;
81- if ( ! requestTriageMutation ) {
82- setUnavailable ( true ) ;
83- return ;
84- }
65+ if ( failedRunIds . length === 0 || unavailable || hasAttemptedRef . current ) return ;
8566
67+ hasAttemptedRef . current = true ;
8668 setLoading ( true ) ;
8769 setError ( null ) ;
8870
@@ -96,16 +78,21 @@ export function useCommitTriage(
9678 setLoading ( false ) ;
9779 } else {
9880 // Backend will generate async — for now show as pending
99- // In future, a reactive query subscription will update this
10081 setLoading ( false ) ;
101- setError ( "Triage requested — results will appear when backend processing completes." ) ;
82+ setSummary ( "Triage requested — results will appear when backend processing completes." ) ;
10283 }
10384 } )
10485 . catch ( ( err : unknown ) => {
10586 const message = err instanceof Error ? err . message : String ( err ) ;
106- // Detect "function not found" errors from Convex
107- if ( message . includes ( "Could not find" ) || message . includes ( "not found" ) ) {
87+ // Detect backend errors that mean triage isn't available — mark permanently unavailable
88+ if (
89+ message . includes ( "Could not find" ) ||
90+ message . includes ( "not found" ) ||
91+ message . includes ( "is not a function" ) ||
92+ message . includes ( "Server Error" )
93+ ) {
10894 setUnavailable ( true ) ;
95+ setError ( null ) ;
10996 } else {
11097 setError ( message ) ;
11198 }
0 commit comments