codeconsole opened a new issue, #16217:
URL: https://github.com/apache/grails-core/issues/16217
`grails-test-examples/scaffolding` fails intermittently in CI, on four
occasions between 24 and 25 August 2026, on four unrelated branches. The root
cause is not yet known; this issue records what is established so far.
## The test
`com.example.UserControllerSpec > User list` —
`grails-test-examples/scaffolding/src/integrationTest/groovy/com/example/UserControllerSpec.groovy`
```groovy
void "User list"() {
when: 'an unauthenticated user requests the user list and signs in when
prompted'
via(UserListPage)
at(LoginPage).login()
then: 'the saved request redirects to the user list'
at(UserListPage)
scaffoldTable
}
```
It fails inside `LoginPage.login()`, on the wait that follows the click:
```groovy
void login(String username = '[email protected]', String password = 'letmein')
{
this.username = username
this.password = password
loginButton.click()
waitFor { title != pageTitle && $('input', name: 'username').empty }
// times out here
}
```
```
geb.waiting.WaitTimeoutException: condition did not pass in 30 seconds
(failed with exception)
at com.example.pages.LoginPage.login(LoginPage.groovy:39)
at com.example.UserControllerSpec.User list(UserControllerSpec.groovy:48)
Caused by: Assertion failed:
title != pageTitle && $('input', name: 'username').empty
| | | |
| | | false
| | 'Please sign in'
| false
'Please sign in'
```
## Occurrences
| Date (UTC) | PR | Job | Duration |
|---|---|---|---|
| 2026-08-24 05:34 | #16150 | [Build Grails with Groovy snapshot (shard
2)](https://github.com/apache/grails-core/actions/runs/32693786704/job/97332626174)
| 55m |
| 2026-08-24 05:34 | #16184 | [Functional Tests (Java 25,
indy=false)](https://github.com/apache/grails-core/actions/runs/32693980319/job/97332597354)
| 59m |
| 2026-08-24 21:35 | #16209 | [Functional Tests (Java 25,
indy=false)](https://github.com/apache/grails-core/actions/runs/32780264043/job/97600561629)
| 51m |
| 2026-08-24 23:29 | #16215 | [Functional Tests (Java 21,
indy=true)](https://github.com/apache/grails-core/actions/runs/32789589183/job/97628349227)
| 43m |
None of those four branches touches this application, Spring Security, or
Geb. It has been seen on Java 21 and Java 25, with indy on and off, and in two
different workflows. In each run the other two specifications of the same
application - `BookControllerSpec` and `UserCommunityControllerSpec`, which
sign in the same way - passed.
## What the browser was looking at
`@ContainerGebConfiguration(reporting = true)` means Geb captures the page
at the failure. The capture is the same in all four, to the byte:
- 1089 bytes, `<title>Please sign in</title>` - Spring Security's generated
login page
- both fields **empty**, showing their placeholders
- the focus ring still on the username field, where `autofocus` puts it on
load
- **no error**: not `/login?error`, no "Bad credentials" banner
- a `_csrf` hidden field present
Two things follow. The page was loaded *after* the credentials were typed,
because a page that had been typed into would hold the values and would have
the focus on the second field. And the request that produced it was not a
rejected authentication attempt, because that renders `?error` with a banner.
## What the failure is not
Determined by injecting faults locally into the same test and comparing the
captured page:
| Injected fault | Page produced | Matches CI |
|---|---|---|
| Session cookie deleted between filling and submitting | 1089 bytes, login
page, no error, autofocus | **yes, byte for byte** |
| Valid session, `_csrf` field overwritten with a stale value | 283 bytes,
`Whitelabel Error Page` (403) | no |
So the form reaches the server **without a session cookie**, rather than
with a stale or mismatched CSRF token. Missing session and wrong token are
answered differently: with no session Spring Security treats the caller as
anonymous and returns the login page, while a wrong token on a live session is
denied with a 403.
It is also not the credentials or the user data:
`UserService.loadUserByUsername` looks the user up by email, and a lookup
failure would produce `?error`, which is absent.
## What is still unknown
Why the browser has no session cookie when the form is submitted. The
specification clears cookies in `setup()`:
```groovy
void setup() {
clearCookiesQuietly()
}
```
and `ContainerGebSpec` holds `@Shared static GebTestManager testManager`, so
the browser is shared between specifications in a worker. Whether the cookie is
cleared, never set, scoped to a different host than the one the form posts to,
or lost some other way is not established.
A consequence worth noting for anyone attempting a fix: if the session is
genuinely gone, Spring Security's saved request goes with it, so simply
submitting the form again is not enough - the login then succeeds but lands on
`/` rather than the user list, and the specification fails on
`at(UserListPage)` instead. Confirmed locally.
## Reproducing
Not reproduced on demand. Four consecutive local runs of
`:grails-test-examples-scaffolding:integrationTest` passed. The failure
signature can be produced deliberately by deleting the browser's cookies
between filling the form and clicking, which is how the comparison above was
made.
## Suggested next step
Capture the browser's cookies and `currentUrl` into the Geb report when that
wait fails. One further occurrence would then distinguish "no cookie at all"
from "a cookie scoped to another host", which is what the fix depends on.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]