Vai al contenuto

Basi della sicurezza

"Edge Function returned a non-2xx status code": l'errore vero

"Edge Function returned a non-2xx status code" vuol dire che la tua funzione Supabase ha risposto con un errore. Ecco dove leggere quello vero.

Vlad Tkachenko12 min di lettura
Una busta rispedita con un timbro giallo di traverso sul fronte e il lembo aperto, con la lettera piegata ancora dentro.

In breve

  • "Edge Function returned a non-2xx status code" è ciò che supabase-js dice di qualsiasi risposta della tua funzione fuori dai 200. Il motivo vero è tornato nella stessa risposta, e i log della funzione contengono il resto.
  • Leggi prima il numero di stato. 401 vuol dire che la richiesta è stata respinta prima che il tuo codice girasse, 404 che non c'è una funzione con quel nome pubblicata, 500 che il tuo codice è fallito, 546 o 504 che sono finite le risorse o il tempo.
  • Due soluzioni rapide zittiscono il messaggio e aprono un buco: chiamare l'API dal tuo frontend con la chiave dentro, e spegnere la verifica del JWT senza mettere un controllo tuo al suo posto.

Premi nella tua app il pulsante che chiama una Edge Function e, al posto di un risultato, la tua app mostra questo:

Edge Function returned a non-2xx status code

È la stessa frase qualunque cosa sia andata storta. Una chiave mancante, una funzione andata in crash, una richiesta respinta prima che il tuo codice girasse: tutte dicono "Edge Function returned a non-2xx status code", quindi il messaggio da solo non ti dice quasi niente su cosa riparare.

Quello che non ti dice è che il motivo è già arrivato. L'errore è tornato nella stessa risposta di quella frase, e supabase-js non te lo legge. Cercare la frase ti dà le ipotesi degli altri. Leggere la risposta ti dà la tua.

Che cosa significa "Edge Function returned a non-2xx status code"?

La tua funzione ha risposto, e la risposta era un errore. È tutto quello che dice la frase.

Ogni risposta sul web porta un codice di stato, un numero di tre cifre che dice com'è andata la richiesta. Qualsiasi cosa nei 200 vuol dire che ha funzionato. Quando la tua app chiama una funzione con supabase.functions.invoke, supabase-js guarda quel numero e, se è fuori dai 200, consegna alla tua app una frase fissa al posto dei dati. La frase è scritta parola per parola dentro supabase-js stesso, e l'errore che la porta si chiama FunctionsHttpError.

Pensa alla chiamata come a una lettera che spedisci a un ufficio sul retro. L'ufficio risponde a quello che hai chiesto oppure ti rimanda la lettera. Quando torna indietro, supabase-js legge il timbro sulla busta e ti dice che è stata rispedita. La busta non la apre. Il biglietto dentro dice perché, e l'ufficio ha tenuto un registro di tutto quello che è successo mentre aveva la tua lettera.

Due parenti stretti si somigliano e vogliono dire altro. Failed to send a request to the Edge Function vuol dire che non è tornata nessuna risposta leggibile, e in un browser spesso si tratta di un'intestazione CORS mancante, di cui si occupa l'articolo su quel messaggio. E se più parti scollegate della tua app si sono rotte nello stesso momento, controlla se è Supabase o la tua app prima di aprire qualsiasi codice.

La risposta porta lo stato e il motivo. supabase-js passa alla tua app una frase, e il motivo resta attaccato all'errore e nel log della funzione.

Dov'è l'errore vero?

In due posti, e puoi leggerli entrambi senza cambiare una riga di codice.

La risposta stessa. Apri la tua app pubblicata in Chrome, Edge o Firefox, premi F12, scegli la scheda Network e ripeti quello che è fallito. Clicca sulla richiesta il cui indirizzo contiene /functions/v1/ seguito dal nome della tua funzione. La colonna Status ha il numero, e la scheda Response ha quello che la funzione ha mandato insieme: un messaggio scritto dal tuo codice, un breve contenuto JSON dalla piattaforma di Supabase, oppure le parole Internal Server Error.

Se tu, o lo strumento che scrive il tuo codice, potete cambiare la chiamata, la guida alla gestione degli errori di Supabase mostra come leggere la stessa risposta dentro l'app, perché la risposta originale viaggia con l'errore come error.context:

const { data, error } = await supabase.functions.invoke('your-function', { body })
if (error) console.log(error.context.status, await error.context.text())

Il registro della funzione. Nella dashboard di Supabase apri Edge Functions, scegli la funzione e leggi due schede. Invocations elenca ogni richiesta con il suo codice di stato e la sua durata. Logs contiene quello che la guida ai log di Supabase chiama eventi della piattaforma, eccezioni non gestite e messaggi di log personalizzati, cioè tutto quello che il tuo codice ha stampato con console.log o console.error. Riproduci il guasto una volta, annota l'ora e leggi le due schede a quel minuto.

Leggi prima il codice di stato

Il numero ti dice fin dove è arrivata la tua richiesta prima di essere rimandata indietro, e questo decide dove è scritto il motivo.

StatoChe cosa è successoDove sta il motivo
401Respinta prima che il tuo codice girasse, o l'ha respinta il tuo codiceLa risposta. Invalid JWT o Missing authorization header è la piattaforma
404In questo progetto non c'è una funzione pubblicata con quel nomeLa risposta dice Requested function was not found
500Il tuo codice ha girato e ha lanciato un errore, o ha restituito 500 appostaLa scheda Logs
503La funzione non è nemmeno partitaLa scheda Logs: un import, un errore di sintassi o la configurazione della funzione
504Nessuna risposta entro 150 secondiLa scheda Invocations, poi quello che il tuo codice stava aspettando
546Ha esaurito il tempo CPU o la memoriaLa scheda Logs dice quale
Qualsiasi altro 4xxL'ha scelto il tuo codice, per esempio passando la risposta di un servizioLa risposta

I significati vengono dal riferimento dei codici di stato di Supabase e dalla sua pagina dei limiti. Per un 404 basta questa tabella: confronta il nome della funzione nella tua app con l'elenco nella dashboard, e verifica che sia pubblicata nel progetto con cui parla la tua app. Le altre righe hanno una sezione ciascuna qui sotto.

500: la tua funzione ha girato ed è fallita

Qualcosa nel tuo codice ha lanciato un errore, e la scheda Logs ha la riga.

La pagina di Supabase su questo errore lo divide in due. Se la risposta dice esattamente Internal Server Error, il tuo codice è andato in crash e il log mostra il tipo di errore e la riga in cui è successo, per esempio TypeError: Cannot read properties of undefined. Se la risposta dice altro, il tuo codice ha scritto quel messaggio da sé e ha restituito il 500 apposta, quindi cerca nella funzione lo stato che ha mandato.

C'è una causa da escludere prima di tutte le altre in una funzione che chiama OpenAI, Stripe o qualsiasi API a pagamento: un secret che esiste sulla tua macchina e non nel progetto pubblicato.

Dentro la funzione, un secret si legge con Deno.env.get('OPENAI_API_KEY'). Quando non c'è nessun secret con quel nome, la documentazione di Deno dice che questo restituisce undefined invece di fallire. La funzione va quindi avanti, costruisce una richiesta con la parola undefined al posto della chiave e la spedisce. OpenAI risponde con un suo 401, "Incorrect API key provided", e la tua funzione lo passa così com'è oppure crolla leggendo una risposta che non si aspettava. Nell'immagine della busta: l'ufficio ha aperto la tua lettera, è andato al cassetto dove doveva esserci la chiave, l'ha trovato vuoto e ha rimandato un biglietto su una chiave sbagliata.

La riparazione sta nella pagina Edge Function Secrets del tuo progetto Supabase. La guida ai secret di Supabase dice che una funzione legge subito un nuovo secret, quindi non serve un nuovo deploy, e che nessun nome di secret può cominciare con SUPABASE_, perché quel prefisso è riservato ai valori che Supabase imposta per te. Un secret impostato per lo sviluppo locale resta sulla tua macchina; il progetto pubblicato ha il suo elenco.

401: respinta prima che il tuo codice girasse

Se la risposta dice Invalid JWT o Missing authorization header, la tua funzione non ha mai visto la richiesta. La piattaforma di Supabase l'ha respinta alla porta.

Quella porta è una verifica chiamata verify_jwt, attiva per impostazione predefinita, che legge l'intestazione Authorization di ogni richiesta prima che il tuo codice giri. La pagina di Supabase sul 401 elenca tre modi di non superarla:

  • La chiave non corrisponde a quelle del tuo progetto. Per esempio una chiave copiata da un altro progetto, o una vecchia rimasta impostata da qualche parte dopo una rotazione.
  • Non c'è alcuna intestazione Authorization. supabase-js ne aggiunge una per te. Un semplice fetch all'indirizzo della funzione, o un webhook da un altro servizio, no.
  • Il tuo progetto usa le nuove chiavi di firma di Supabase. La verifica integrata capisce solo il formato vecchio, e la soluzione di Supabase è spegnere la verifica per quella funzione.

Quest'ultimo punto va oltre il singolo caso. Supabase oggi consiglia di spegnere la verifica integrata e di gestire l'autenticazione direttamente nel codice della tua funzione. Il consiglio ha due metà, e la seconda è quella che si perde per strada.

Se il 401 è arrivato con un messaggio scritto dal tuo codice, la piattaforma ha lasciato passare la richiesta e la tua funzione l'ha respinta, o ha passato un 401 del servizio che chiamava, che spesso è il secret vuoto della sezione sul 500. È anche una verifica diversa da quella dietro PGRST301 sulle tue query al database, che ha una pagina tutta sua. Se il log della funzione mostra invalid claim: missing sub claim, ha chiesto a Supabase Auth chi stava chiamando e ha ricevuto la chiave del tuo progetto invece di un utente che ha fatto l'accesso, cosa che l'articolo su quel messaggio ricostruisce.

Lo stato dice fin dove è arrivata la richiesta. Un 401 al cancello non ha mai raggiunto il tuo codice; un 401 rimandato dal servizio chiamato sì.

546 e 504: senza risorse o senza tempo

Supabase ferma una funzione che consuma troppo, e i due numeri dicono quale limite ha toccato.

546 è il calcolo. La pagina dei limiti dà a una funzione 256 MB di memoria e a ogni richiesta 2 secondi di tempo CPU. Superane uno e la risposta dice Function failed due to not having enough compute resources (please check logs), con il codice WORKER_RESOURCE_LIMIT (i log più vecchi dicono WORKER_LIMIT). La scheda Logs dice CPU Time exceeded o Memory limit exceeded. La pagina di Supabase sul 546 porta come esempi l'elaborazione di immagini, gli embedding di IA e il web scraping, e la sua risposta è fare meno per ogni richiesta o affidare la parte pesante a un altro servizio.

504 è il tempo. Una funzione che non manda nessuna risposta entro 150 secondi riceve un 504. Il limite CPU conta solo il tempo in cui il tuo codice sta davvero calcolando: la pagina dei limiti dice che non include l'I/O asincrono, ed è proprio quello aspettare la rete. Una funzione che aspetta un modello di IA lento quindi non consuma CPU. Sta consumando l'orologio da 150 secondi, e quando finisce ricevi un 504 e non un 546.

Due soluzioni che peggiorano le cose

Due modifiche fanno sparire questo messaggio in pochi minuti, ed entrambe ti lasciano messo peggio dell'errore.

Chiamare l'API dalla pagina. Il modo più rapido perché una funzione che fallisce smetta di fallire è smettere di usarla: mettere la chiave in una variabile VITE_ e chiamare OpenAI direttamente dal tuo frontend. Funziona al primo colpo. Mette anche la chiave nel file che ogni visitatore scarica, perché quel prefisso è un'istruzione per compilare il valore dentro il tuo bundle, e lì chiunque può leggerla. Tra le app pubblicate che abbiamo scansionato, una chiave OpenAI è comparsa nel JavaScript di 33, e che cosa ci può fare un estraneo ha un articolo tutto suo.

Spegnere la verifica e fermarsi lì. Togliersi un 401 spegnendo verify_jwt è la prima metà del consiglio stesso di Supabase. La guida alla configurazione di Supabase dice chiaro che cosa fa da sola: a quel punto chiunque può invocare la tua Edge Function senza un JWT valido. L'indirizzo della tua funzione sta nel tuo frontend, leggibile da chiunque. Se la funzione custodisce una chiave OpenAI, un estraneo ora può spenderla attraverso di te. Se usa la chiave segreta del progetto, che Supabase mette nell'ambiente di ogni funzione, la richiesta di un estraneo gira con una chiave che scavalca tutte le regole di row level security che hai scritto.

La seconda metà, un controllo dentro la funzione su chi sta chiamando, sono poche righe. Spostare un secret in una Edge Function le contiene, e spiega perché persino la verifica predefinita fa passare più chiamanti di quanto il nome lasci pensare.

Il primo errore si vede da fuori, ed è così che l'abbiamo contato. La nostra scansione gratuita legge il JavaScript della tua app pubblicata come farebbe un estraneo e nomina ogni chiave che trova, dicendo se è una di quelle che devono stare lì. Ci vogliono circa 20 secondi e non serve un account: scansiona la tua app.

Perché la chiave resti dove l'hai messa

La soluzione di oggi può essere disfatta dal prompt della settimana prossima. Un builder a cui chiedi di far funzionare di nuovo qualcosa è libero di prendere la scorciatoia qui sopra, e la tua app continua a funzionare mentre lo fa.

Reeve Monitor controlla ogni ora al posto tuo:

  • tutti e nove i controlli ogni ora, su un massimo di tre app
  • una email il giorno in cui una nuova scansione valuta la tua app peggio della precedente; una chiave segreta nel tuo JavaScript limita da sola il voto a D
  • se l'app risponde, ogni 60 secondi
  • un rapporto mensile di quello che ha visto

Una funzione che gira con la tua chiave segreta scavalca tutte le regole di row level security. Reeve Care tiene una copia del tuo database Supabase dove niente nel tuo progetto può arrivare.

  • una copia cifrata secondo una pianificazione, conservata sullo storage di Reeve nell'UE, fuori dal tuo account Supabase
  • ogni copia verificata prima di contare, contando le righe di ogni tabella
  • un ripristino in un clic che salva quello che c'è adesso prima di sostituirlo
  • anche i tuoi file caricati, appena colleghi una credenziale di Storage
  • tutto quello che fa Monitor

Che cosa fare adesso

Cosa fare

  • Riproduci il guasto una volta e annota l'ora.
  • Apri la richiesta nella scheda Network del browser e leggi stato e risposta prima di cambiare qualsiasi cosa.
  • Leggi le schede Invocations e Logs della funzione nella dashboard di Supabase a quel minuto.
  • Con un 500 da una funzione che chiama una API a pagamento, controlla che il secret sia impostato nella pagina Edge Function Secrets del progetto pubblicato. Dopo non serve un nuovo deploy.
  • Con un 401 dalla piattaforma, correggi la chiave o spegni la verifica integrata, e aggiungi nella stessa modifica un controllo dentro la funzione su chi sta chiamando.
  • Non togliere mai questo errore spostando la chiave in una variabile VITE_.

Il controllo su chi sta chiamando è il passo più facile da saltare, e la guida ai secret nelle Edge Functions lo tratta per intero. La checklist di sicurezza in 10 minuti copre quello che di solito resta aperto in un'app appena lanciata.

FAQ

Che cosa significa "Edge Function returned a non-2xx status code"?

Significa che la tua Edge Function di Supabase ha risposto con uno stato di errore, un numero qualsiasi fuori dai 200, e supabase-js ha trasformato quella risposta in una frase fissa. La frase è la stessa per un crash, una chiave mancante, una richiesta respinta prima che il tuo codice girasse e un timeout. La risposta vera e propria, che dice quale di questi è stato, resta attaccata all'errore, e la funzione tiene un suo log.

Come vedo l'errore vero che c'è dietro?

Apri la tua app pubblicata, premi F12, scegli la scheda Network e ripeti quello che è fallito. Clicca sulla richiesta il cui indirizzo contiene /functions/v1/ e leggi stato e risposta: è quello che la funzione ha davvero mandato. Poi apri Edge Functions nella dashboard di Supabase, scegli la funzione e leggi le schede Invocations e Logs alla stessa ora. Nel codice, la guida di Supabase legge la stessa risposta con await error.context.json().

Perché la mia Edge Function funziona in locale e fallisce una volta pubblicata?

Controlla prima se un secret esiste sulla tua macchina e non nel progetto pubblicato. L'ambiente locale legge il suo file di ambiente, la funzione pubblicata legge i secret impostati sul progetto. Un secret mancante torna come undefined invece che come errore, quindi la funzione va avanti e fallisce più tardi, di solito quando il servizio chiamato rifiuta una chiave vuota. Impostalo nella pagina Edge Function Secrets; Supabase dice che un nuovo secret viene letto subito, senza un nuovo deploy.

Devo spegnere la verifica del JWT per risolvere un 401?

Supabase oggi consiglia di spegnere la verifica integrata e di controllare chi chiama dentro la tua funzione. Contano entrambe le metà. Spenta da sola, la verifica non ferma più niente, e la documentazione di Supabase avverte che così chiunque può invocare la tua funzione senza un JWT valido. Se la funzione custodisce una chiave OpenAI o la chiave segreta del progetto, vuol dire chiunque su internet che consuma la tua quota o scavalca le tue regole di row level security.

Che cosa significa l'errore 546 su una Edge Function di Supabase?

La funzione ha esaurito il calcolo: più di 2 secondi di tempo CPU, o più della sua memoria, in una richiesta. La risposta dice "Function failed due to not having enough compute resources (please check logs)", e la scheda Logs dice quale limite è stato, CPU Time exceeded o Memory limit exceeded. Aspettare una API lenta non conta come tempo CPU; una funzione che aspetta troppo riceve invece un 504.

Scritto da

Vlad Tkachenko

Fondatore di Reeve

Passo le giornate a guardare app costruite con Lovable, Bolt, v0, Cursor e Replit, e la breve lista di errori che vi ricompaiono di continuo.

Altro sull'autore

Da leggere dopo

Tutti gli articoli

Fai il backup del database Supabase prima della prossima modifica

Reeve ne tiene una copia cifrata a intervalli programmati e la ripristina per te se qualcosa va storto.

Vedi i backup Supabase

Controllo esterno automatizzato, non un audit completo. L'assenza di risultati non è una garanzia di sicurezza.