@@ -615,6 +615,9 @@ <h2>📋 Builds</h2>
615615 // =========================================================================
616616 const STORAGE_KEY = 'lambda-layer-builder' ;
617617 const POLL_INTERVAL = 10000 ; // 10 seconds
618+ // Slightly longer than the EC2 watchdog (MAX_BUILD_MINUTES=30) so the API
619+ // has a chance to write FAILED before we give up on the client side.
620+ const BUILD_TIMEOUT_SECONDS = 35 * 60 ;
618621
619622 const API_URL = 'https://9nij0o9osa.execute-api.eu-central-1.amazonaws.com' ;
620623
@@ -742,6 +745,16 @@ <h2>📋 Builds</h2>
742745 stopPolling ( buildId ) ;
743746 }
744747
748+ // Stop polling if the build has been running too long (timed out).
749+ // The EC2 watchdog marks it FAILED after 30 min, but if that update
750+ // hasn't propagated yet we stop client-side polling anyway.
751+ const age = Math . floor ( Date . now ( ) / 1000 ) - ( build . created_at || 0 ) ;
752+ if ( ! [ 'COMPLETED' , 'FAILED' ] . includes ( build . status ) && age > BUILD_TIMEOUT_SECONDS ) {
753+ stopPolling ( buildId ) ;
754+ build . status = 'FAILED' ;
755+ build . error = build . error || 'Build timed out (no response after 35 minutes).' ;
756+ }
757+
745758 saveState ( ) ;
746759 renderBuilds ( ) ;
747760 }
@@ -755,6 +768,13 @@ <h2>📋 Builds</h2>
755768 // =========================================================================
756769 function startPolling ( buildId ) {
757770 if ( state . polling [ buildId ] ) return ;
771+ // Don't resume polling for builds that are already past the timeout
772+ // (e.g., stale entries restored from localStorage after a page reload).
773+ const build = state . builds . find ( b => b . id === buildId ) ;
774+ if ( build && build . created_at &&
775+ Math . floor ( Date . now ( ) / 1000 ) - build . created_at > BUILD_TIMEOUT_SECONDS ) {
776+ return ;
777+ }
758778 checkStatus ( buildId ) ; // immediate check
759779 state . polling [ buildId ] = setInterval ( ( ) => checkStatus ( buildId ) , POLL_INTERVAL ) ;
760780 }
0 commit comments