Google 400 at consent stops a real user signing in to Terra
A real user was stopped at Google's consent step on 2026-08-30 at 13:44 ART. Google returned its generic 400, "The server cannot process the request because it is malformed. It should not be retried." Screenshot forwarded by Elazar, iOS Safari opened from a WhatsApp link. This is a genuine terra sign-in failure, not a shared stale link. Terra's own event rail recorded signInInitiated at 13:44:12 ART, flowId 99f3e188, canonical served host, intended redirect to terra's auth callback, with no superseded prior flow. So the person did start at terra's login and did click sign in. No signInCompleted follows it anywhere in the 24h window, and no gotrueError of any kind, meaning Google never sent a code back and terra's callback endpoint was never reached. The token exchange never ran, so nothing here is evidence for or against the client secret. The configuration is not the cause, established by a working control rather than by inspection. A different sign-in completed successfully the same day at 11:47 ART, flowId bdfa79b0, outcome authorized, on the same client, the same secret and the same registration. I also probed the entry point live: the authorize leg returns a well-formed redirect carrying the dedicated enamel/terra client id, the correct supabase callback as redirect_uri, response_type code, scope email and profile, and a fresh state. Terra's login page answers 200. Replaying the exact consent URL from a cookieless context also returns 400, and that reproduction is worthless as evidence: the consent URL carries a single-use token bound to the session that created it, so it fails for everyone who did not create it. It was checked and set aside rather than cited. What remains unexplained is why Google rejected a freshly issued, well-formed request for this one person two hours after an identical flow succeeded for someone else. The URL shows authuser=unknown, which means Google could not resolve an account on that device. Arriving at the consent step in that state, rather than at an account chooser, is the anomaly worth chasing. Next discriminator, cheap and not yet run: have the same person retry starting at terra's login in a fresh private Safari tab, ideally after signing in to Google first, and report whether it recurs. A success narrows this to that device's Google session state; a repeat makes it reproducible and worth escalating with a har capture. Nobody has changed any configuration in response to this, and nobody should until the retry result exists.
Sub-tickets — 1/1 done
Activity
-
Status 2026-08-31 05:00 ART, roughly fifteen hours after the failure: no new evidence, and the absence is itself the finding. The rail holds exactly one auth row since the incident - the original signInInitiated at 13:44:12.813, flowId 99f3e188-931d-43ea-8432-0e8e3cc58dd5. That flow never produced a signInCompleted. No later signInInitiated exists either, so the retry this item asked for has not been attempted, or was attempted somewhere that never reached terra's login page. Read that carefully rather than as quiet: nobody has signed in to Terra at all in fifteen hours, successful or otherwise. The last completed sign-in remains the 11:47 ART one from the day before. So a second failure would look identical to nobody trying, and this item cannot distinguish them from the data. Nothing has been changed and nothing should be. The blocker is not diagnostic, it is human: the person who hit the 400 has to attempt a sign-in again, starting at Terra's own login page, so the rail records either a completion or a second initiated-without-completion. Either outcome moves this; another day of silence does not.
-
Cause narrowed 2026-08-31 with two new pieces of evidence, both read off artifacts already in hand rather than obtained by asking anyone. FIRST, the request was theirs and it was fresh. The consent URL carries an as parameter whose second half, 1788108254571764, is a microsecond epoch stamp. It decodes to 2026-08-30 13:44:14 ART, one and a half seconds after Terra's own signInInitiated at 13:44:12.813. So Google minted that consent URL in response to this person's own click, in real time. The forwarded-stale-link explanation is dead, and with it the idea that they were sent a link by someone else. SECOND, the browser that loaded the consent page had no Google session. Their URL contains authuser=unknown. Google fills that parameter with an account index when it can resolve a signed-in account from its own cookies, and leaves it unknown when it cannot. That is a direct reading of their request, not an inference from ours. Fetching that same URL from a context holding no Google cookies returns the identical page: not merely status 400, but the same sentence, the server cannot process the request because it is malformed, it should not be retried. So the failure state is reproducible and matches. THE LIMIT ON THAT REPRODUCTION, stated because it changes how much it is worth. The replay happened long after the token was minted, so it varies two things at once, absent cookies and an expired token, and cannot separate them. A clean separation would need a freshly minted consent URL loaded without cookies, and a consent URL cannot be minted without a Google session in the first place, so that experiment is not available. The authuser=unknown reading does not depend on the replay and carries the weight on its own. WHAT IS ESTABLISHED: the consent page was loaded by a browser that Google could not associate with any signed-in account, and Google answers that condition with this exact error rather than by offering a sign-in. WHAT IS NOT: how the device got into that state. Two candidates remain and the artifacts cannot separate them. Either the flow crossed browser contexts - begun inside WhatsApp's in-app webview and handed off to Safari, which holds a different cookie jar, an interpretation the return-to-WhatsApp banner in the screenshot is consistent with but does not prove - or Safari on that phone is configured to block cookies outright. Two device-side questions settle it and no further log reading will. Nothing in Terra caused this and nothing in Terra can prevent it: the failure happens on Google's domain, in a browser state Terra never sees, after Terra has already handed the flow off correctly.
-
ROOT CAUSE CONFIRMED, AND THE USER WAS NEVER PUIA. The WhatsApp export pasted into the returned consultas document carries the whole exchange, and it names the person: Tomy Cardozo, not Sebastian Puia. Every earlier note on this item that says "the user" meant an unidentified person; it is this one. THE SEQUENCE, from the thread itself. 11:35 - Puia posts the link into a WhatsApp group addressed to two people: "@Lauti Jordi @Tomy Cardozo entrene a este link para verla q esta quedando un golazo https://terra-enamel.pensanta.com/". 13:44 - Tomy: "Me sale esto amigos" with the screenshot. 13:52/13:53 - Elazar asks for the URL. 14:04 - Tomy pastes the consent URL, the same one already analysed on this item. 14:20 - Elazar: "Copia el url y abri Chrome y pegalo e intenta, no click desde WhatsApp, por favor." SO THE LINK WAS TAPPED FROM INSIDE WHATSAPP. That is the fact the earlier analysis inferred from the screenshot's back-to-WhatsApp banner and from authuser=unknown, and it is now established from the thread rather than from the picture. The in-app-browser class is confirmed for this instance. Elazar's own remedy at 14:20 - open it in Chrome, do not click from WhatsApp - is exactly what TERRA-138 now shows automatically on /login before the click. He had to type it by hand for this user; the next one gets it from the page. WHAT IS STILL NOT RECORDED, and it is why this does not close on the diagnosis: the thread ends at 14:20. Whether Tomy retried in Chrome and got in is nowhere in the export. Nobody confirmed the fix worked for the actual person it happened to. That is a question for Elazar to ask in the chat, not something more log-reading can answer - and the auth rail cannot answer it either, since a successful sign-in by Tomy would need his identity to correlate against and he may not be in the roster at all. The coverage bound recorded on TERRA-138 is unaffected and still stands: the detector may not fire on an iOS SFSafariViewController handoff, which is a real Safari instance with no token to match. This thread does not resolve that either way.
-
Blocked on a person, not on diagnosis, and the row was sitting in backlog as though there were still analysis to do. There is not. The cause is established: the link was tapped from inside WhatsApp, confirmed from the exported thread rather than inferred from the screenshot, and Google answered a consent request from a browser context it could not associate with any signed-in account. Nothing in terra caused it and nothing in terra can prevent it - the failure happens on Google's domain, in a browser state terra never sees, after terra has correctly handed the flow off. The remedy Elazar typed by hand at 14:20 is now shown automatically on /login by TERRA-138, which is done. What is missing is a single fact that only a human can supply: whether Tomy Cardozo retried in Chrome and got in. The WhatsApp export ends at 14:20 and does not say. The auth rail cannot answer it either - correlating a successful sign-in to him needs an identity to correlate against, and he may not be in the roster at all. More log reading produces nothing; fifteen hours of rail silence already read as indistinguishable from nobody trying. Unblocks when Elazar asks in the chat and reports the answer. A confirmed success closes this row. A repeat makes it reproducible and worth a har capture, which is a different and larger piece of work.
-
EL BLOQUEO DECIA QUE SOLO UNA PERSONA PODIA CONTESTAR ESTO. MEDIDO 2026-09-01 08:47, PARTE SE PUEDE CONTESTAR SIN NADIE, y la respuesta no es la que el bloqueo esperaba. LO QUE DECIA LA FILA: falta un solo dato que solo un humano puede dar - si la persona reintento en Chrome y entro. El export de WhatsApp termina a las 14:20 y no lo dice, y el riel de auth tampoco puede contestarlo porque correlacionar un login exitoso con el necesita una identidad contra la cual correlacionar, y puede no estar en el padron. MEDI EXACTAMENTE ESO EN VEZ DE DARLO POR IMPOSIBLE, que es lo que el bloqueo hacia. Y la parte que la fila planteo como hipotesis resulto ser el hecho: NO EXISTE NINGUNA FILA EN users con ese apellido. Cero. O sea que no es que no se pueda correlacionar por falta de datos del riel - no hay a quien correlacionar. La persona nunca fue dada de alta. EL RIEL DESDE EL INCIDENTE, ventana desde 2026-08-30 14:20 ART hasta hoy: cuatro signInInitiated y cuatro signInCompleted, de DOS actores distintos. Cero fallas de cualquier tipo. Y cero usuarios nuevos creados en users en toda esa ventana. Asi que en los dos dias siguientes el login funciono las cuatro veces que se uso, y nadie nuevo entro al padron. QUE SE PUEDE CONCLUIR Y QUE NO, y lo separo porque la tentacion es cerrar la fila con esto. SE PUEDE: que no hay ninguna evidencia de que la persona haya vuelto a entrar, y que si volvio a intentar no dejo rastro ni en el riel ni en el padron. NO SE PUEDE: afirmar que reintento y fallo. Un reintento que muere en el consentimiento de Google se ve EXACTAMENTE IGUAL que nadie intentando - que es lo que la fila ya decia sobre las quince horas de silencio, y sigue siendo cierto. La ausencia de evidencia aca no distingue las dos cosas. LO QUE SI CAMBIA: la pregunta que hay que hacerle al humano ya no es "reintento y entro?". Es OTRA, y es anterior: esta persona tiene que estar en el padron y no esta. Si nunca fue dada de alta, entonces incluso un consentimiento exitoso de Google la habria dejado afuera del lado de terra, y el 400 de Google seria el segundo problema, no el primero. NO AFIRMO que el gate la habria rechazado - no medi que hace terra con un login de alguien sin fila en users, y decirlo sin medirlo seria inventar el desenlace. Lo que afirmo es que la fila no puede cerrarse como un incidente de Google mientras esa segunda posibilidad no se descarte. EL BLOQUEO SE REESCRIBE, no se levanta: sigue bloqueada en una persona, pero la pregunta cambio. Y una parte del bloqueo era falsa - "el riel no puede contestarlo" era una afirmacion sobre el riel que no se habia probado contra el riel. NADIE TOCO NINGUNA CONFIGURACION Y SIGUE SIN HABER QUE TOCAR.
-
Sigue bloqueada en una persona pero LA PREGUNTA CAMBIO, y una parte del bloqueo anterior era falsa. Decia que el riel de auth no podia contestar si la persona reintento; eso era una afirmacion sobre el riel que nadie habia probado contra el riel. Medido 2026-09-01: NO EXISTE ninguna fila en users con ese apellido, o sea que no hay a quien correlacionar porque la persona nunca fue dada de alta. Desde el incidente hubo 4 signInInitiated y 4 signInCompleted de dos actores, cero fallas, y cero usuarios nuevos. La pregunta al humano ya no es si reintento y entro: es anterior a eso, esta persona tiene que estar en el padron y no esta. No afirmo que el gate la habria rechazado - no medi que hace terra con un login sin fila en users. Un reintento muerto en el consentimiento sigue siendo indistinguible de nadie intentando.
-
ME FALTABA UNA COLUMNA Y ESO HABRIA INVALIDADO LO QUE ESCRIBI HACE DIEZ MINUTOS. Lo reviso porque audit-terra-ca contesto la pregunta de fuente y su respuesta apuntaba directo al agujero de mi consulta. 2026-09-01 08:50. LO QUE YO HABIA MEDIDO: cuatro signInCompleted desde el incidente y "cero fallas". Conte por ACTION solamente. Y el camino sin fila en users NO produce una accion distinta - produce el MISMO signInCompleted, con un failureCode. O sea que un rebote por no tener perfil habria entrado en mis cuatro exitos y yo lo habria contado como un login bueno. La afirmacion era mas fuerte de lo que la consulta podia sostener. LO QUE audit ENCONTRO EN FUENTE, y es lo que me hizo volver a medir: un callback de Google exitoso NO crea fila en users. Busca findAppUserByAuthId (auth/callback/route.ts:404-407) y si no hay match llama a bindSupabaseAuthId, que es de enamel y cuyo cuerpo no leyo. Si no aparece fila, appUser queda null y el callback toma route.ts:480-484: redirige a /login?error=no-profile con codigo noProfile. NO va a /sin-acceso ni a /identidad-en-revision, y la prosa vieja de la cabecera del archivo que menciona /solicitar-acceso YA NO ES la ruta literal - lineas 37-41 contra 480-484. Otro comentario que sobrevivio a su codigo. Y LO IMPORTANTE PARA MI PREGUNTA: NO es invisible. Todo finish llama antes a logEvent con categoria auth y accion signInCompleted, llevando el outcome en el detail y el failureCode noProfile. authUserId se toma del usuario de GoTrue ANTES de resolver identidad, y actorUserId queda null - correctamente, porque no hay fila. O sea que el caso deja rastro y se puede contar, si uno mira la columna correcta. REMEDIDO CON failureCode Y detail->>'outcome', que es lo que faltaba. Serie completa de signInCompleted de terra: 22 authorized; 2 con failureCode noProfile (2026-07-31 al 08-14); 2 noProfile sin failureCode (07-30, antes de que existiera la columna poblada); 2 providerFailed; 2 exchangeFailed con FLOW_ID_ABSENT; 1 exchangeFailed; 1 lookupFailed. DESDE EL INCIDENTE (30-08 14:20 ART en adelante): los cuatro son authorized con failureCode NULL. CERO noProfile. El ultimo noProfile de toda la serie es del 14 de agosto, dos semanas ANTES del incidente. ASI QUE LA CONCLUSION SE SOSTIENE Y AHORA ESTA VERIFICADA EN VEZ DE ASUMIDA, y ademas dice mas: si la persona hubiera reintentado y hubiera pasado el consentimiento de Google, habria llegado al callback y - sin fila en users - habria dejado un noProfile. No hay ninguno. Entonces o no reintento, o reintento y volvio a morir del lado de Google, antes de tocar terra. LO QUE SIGUE SIN PODERSE SEPARAR, igual que antes: esas dos posibilidades. Un reintento que muere en el consentimiento no deja rastro en ningun lado que terra pueda leer. LIMITE QUE audit MARCO Y QUE MANTENGO: leyo fuente y su test, no el cuerpo de la funcion de enamel, ni el logger desplegado, ni que hace el cliente con /login?error=no-profile. O sea que prueba el camino ESCRITO y la llamada de evento INTENCIONADA. Lo que yo agrego por el otro lado es que filas noProfile REALES existen en la tabla - dos, fechadas - asi que ese camino no solo esta escrito: ya persistio eventos. Son dos mediciones distintas que se apoyan, no una sola.
-
ME FALTABA UNA COLUMNA Y ESO HABRIA INVALIDADO LO QUE ESCRIBI HACE DIEZ MINUTOS. Lo reviso porque audit-terra-ca contesto la pregunta de fuente y su respuesta apuntaba directo al agujero de mi consulta. 2026-09-01 08:50. LO QUE YO HABIA MEDIDO: cuatro signInCompleted desde el incidente y "cero fallas". Conte por ACTION solamente. Y el camino sin fila en users NO produce una accion distinta - produce el MISMO signInCompleted, con un failureCode. O sea que un rebote por no tener perfil habria entrado en mis cuatro exitos y yo lo habria contado como un login bueno. La afirmacion era mas fuerte de lo que la consulta podia sostener. LO QUE audit ENCONTRO EN FUENTE, y es lo que me hizo volver a medir: un callback de Google exitoso NO crea fila en users. Busca findAppUserByAuthId (auth/callback/route.ts:404-407) y si no hay match llama a bindSupabaseAuthId, que es de enamel y cuyo cuerpo no leyo. Si no aparece fila, appUser queda null y el callback toma route.ts:480-484: redirige a /login?error=no-profile con codigo noProfile. NO va a /sin-acceso ni a /identidad-en-revision, y la prosa vieja de la cabecera del archivo que menciona /solicitar-acceso YA NO ES la ruta literal - lineas 37-41 contra 480-484. Otro comentario que sobrevivio a su codigo. Y LO IMPORTANTE PARA MI PREGUNTA: NO es invisible. Todo finish llama antes a logEvent con categoria auth y accion signInCompleted, llevando el outcome en el detail y el failureCode noProfile. authUserId se toma del usuario de GoTrue ANTES de resolver identidad, y actorUserId queda null - correctamente, porque no hay fila. O sea que el caso deja rastro y se puede contar, si uno mira la columna correcta. REMEDIDO CON failureCode Y detail->>'outcome', que es lo que faltaba. Serie completa de signInCompleted de terra: 22 authorized; 2 con failureCode noProfile (2026-07-31 al 08-14); 2 noProfile sin failureCode (07-30, antes de que existiera la columna poblada); 2 providerFailed; 2 exchangeFailed con FLOW_ID_ABSENT; 1 exchangeFailed; 1 lookupFailed. DESDE EL INCIDENTE (30-08 14:20 ART en adelante): los cuatro son authorized con failureCode NULL. CERO noProfile. El ultimo noProfile de toda la serie es del 14 de agosto, dos semanas ANTES del incidente. ASI QUE LA CONCLUSION SE SOSTIENE Y AHORA ESTA VERIFICADA EN VEZ DE ASUMIDA, y ademas dice mas: si la persona hubiera reintentado y hubiera pasado el consentimiento de Google, habria llegado al callback y - sin fila en users - habria dejado un noProfile. No hay ninguno. Entonces o no reintento, o reintento y volvio a morir del lado de Google, antes de tocar terra. LO QUE SIGUE SIN PODERSE SEPARAR, igual que antes: esas dos posibilidades. Un reintento que muere en el consentimiento no deja rastro en ningun lado que terra pueda leer. LIMITE QUE audit MARCO Y QUE MANTENGO: leyo fuente y su test, no el cuerpo de la funcion de enamel, ni el logger desplegado, ni que hace el cliente con /login?error=no-profile. O sea que prueba el camino ESCRITO y la llamada de evento INTENCIONADA. Lo que yo agrego por el otro lado es que filas noProfile REALES existen en la tabla - dos, fechadas - asi que ese camino no solo esta escrito: ya persistio eventos. Son dos mediciones distintas que se apoyan, no una sola.
-
Cerrado un cabo suelto de la nota de auditoria, sin mover el bloqueo. La nota acusaba a la cabecera del archivo de callback de nombrar /solicitar-acceso; la cabecera ya estaba correcta. El comentario viejo era otro, el que esta pegado al return del camino sin perfil, y ese si nombraba /solicitar-acceso mientras el codigo redirige a /login?error=no-profile desde hace tiempo. Corregido. Verificado ademas lo que la auditoria habia marcado como limite propio: que hace el cliente con /login?error=no-profile. Esta atendido. LoginCard ramifica por ese codigo, muestra el cartel de error, echa la direccion con la que se intento ingresar y ofrece el camino de pedido de acceso. No es una pantalla muda, asi que una persona sin fila en el padron ve por que no entro. El bloqueo NO cambia: sigue siendo una sola pregunta a una persona, y sigue sin poder separarse un reintento que murio en el consentimiento de Google de nadie reintentando. Ninguna de las dos cosas deja rastro que la aplicacion pueda leer.
-
coder02-terra-ca
-
coder-terra-cc
-
MARKER-t137-noprofile-mulcn: the open measurement on this row is now made, from source, and it DISPROVES the standing hypothesis. The blockedReason says 'no medi que hace terra con un login sin fila en users'. Measured at src/app/auth/callback/route.ts:480-488: a successful Google sign-in whose address has no row in the shared users table is NOT rejected at Google and does NOT error. The token exchange runs, the session is kept - login is not access - and the callback returns finish('/login?error=no-profile', 'noProfile', null), writing a signInCompleted row with outcome noProfile and a null actorUserId, because there is no public.users.id to reference (appEvents_actorUserId_fkey requires one). LoginCard renders the banner, echoes the attempted address and offers the request-access path. WHY THAT SETTLES SOMETHING. The absence of a padron row cannot explain the 2026-08-30 13:44 ART failure. If that had been the cause, terra would have recorded signInCompleted{outcome:noProfile} and the person would have seen an explained refusal. The measured evidence is the opposite: signInInitiated at 13:44:12, flowId 99f3e188, and NO signInCompleted of any outcome and no gotrueError in the 24h window. Google never sent a code back and terra's callback was never reached. So the failure is upstream of terra entirely, and the missing users row is a SECOND, separate problem with this person rather than the explanation of the first. Terra behaves correctly for a person absent from the padron. That path is not the defect. WHAT REMAINS ON THIS ROW is unchanged and still needs the human: what the person saw and whether they retried. A retry that died at consent is still indistinguishable from nobody trying. The 400 itself remains unexplained by anything terra emitted. FOR TERRA-207: a docente absent from the padron on 1/10 gets /login?error=no-profile with their address echoed and a request-access link - an explained refusal, not a crash or a blank page. That is worth knowing before the jornada.