garrytan / garrytan/gstack

Browse tool cannot authenticate with Rails + Devise + Turbo apps

Open
#984 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
133k
Forks
19.9k
Avg merge
18h 46m
Merged PRs (30d)
26

Description

### Summary

The gstack browse tool (headless Playwright) cannot complete a login flow on Rails 8 + Devise + Turbo applications, making `/qa`, `/design-review`, and other browser-based skills unable to test any authenticated pages.

### Environment

- gstack browse (headless Playwright, launched mode)
- Rails 8.1 + Turbo 8 + Devise 5.0.3
- macOS Darwin 25.3.0
- App running on localhost:3000

### Problem

There are two distinct issues that compound to make authentication impossible:

**Issue 1: Devise + Turbo status code mismatch (app-side, fixable)**

Devise returns HTTP 200 on failed authentication. Turbo expects either a redirect (303) or an error status (422) for form re-renders. When `$B click` submits the login form via Turbo, a failed login produces:

```
Error: Form responses must redirect to another location
at ie.requestSucceededWithResponse (turbo.min.js:5:15107)
```

**Fix:** Set `config.responder.error_status = :unprocessable_entity` and `config.responder.redirect_status = :see_other` in `config/initializers/devise.rb`. After this fix, curl confirms correct behavior: 422 on bad password, 303 redirect on good password.

**Issue 2: Browse tool loses page context after form-based navigation (browse-side, not fixable by app)**

Even after fixing the Devise status codes, the browse tool cannot complete the login flow because:

1. `$B fill @e2 "email"` + `$B fill @e3 "pass"` + `$B click @e5` — The click triggers a 303 redirect. After the redirect completes, subsequent `$B` commands show `[browse] Starting server...` and `about:blank`. The page context is lost.

2. `$B js "form.submit()"` with `data-turbo="false"` — The standard form POST triggers a full page navigation. Same result: subsequent commands get `about:blank`.

3. `$B chain 'snapshot -i' 'fill @e2 ...' 'click @e5' 'wait 3000' 'url'` — The `fill` fails because refs from snapshot don't propagate into chain commands.

4. `$B js "fetch('/users/sign_in', {...})"` — The async fetch fires but subsequent `$B` commands restart the server connection before the result is available.

5. `$B fill "#user_email" "email"` (CSS selectors instead of refs) — Times out: `Operation timed out: fill: Timeout 5000ms exceeded.`

6. `$B focus "#user_email"` — Returns `focus requires headed mode. Run $B connect first.`

### What I observed

Each `$B` command appears to reconnect to the Playwright daemon. While the browser process (PID visible via `$B status`) stays alive, page context doesn't survive across commands when a navigation event occurs between them. The `[browse] Starting server...` message appears on most commands, suggesting the connection is re-established each time.

Cookies DO persist in the daemon (verified with `$B cookies`), but only the `__profilin` cookie — the session cookie set by the redirect response is never captured because the page context is lost before the redirect completes.

### Reproduction

Any Rails + Devise app with Turbo enabled:

```bash
B=~/.claude/skills/gstack/browse/dist/browse
$B goto "http://localhost:3000/users/sign_in"
$B snapshot -i # shows @e2 email, @e3 password, @e5 submit
$B fill @e2 "user@example.com"
$B fill @e3 "password"
$B click @e5 # triggers 303 redirect
# After this point:
$B url # returns about:blank
$B snapshot -i # returns "(no accessible elements found)"
```

### Workaround

Created a development-only bypass route that skips the login form entirely:

```ruby
# GET /dev_login?email=admin@example.com&redirect=/target/page
get "dev_login", to: "dev_login#create" if Rails.env.development?
```

The browse tool can then authenticate with a single `$B goto` that sets the session cookie and redirects — no form, no Turbo, no multi-command state to lose.

### Expected behavior

The browse tool should be able to:
1. Fill a login form across multiple commands
2. Submit the form (click or press Enter)
3. Follow the redirect
4. Continue browsing on the authenticated page with the session cookie intact

### Suggested fixes

1. **Page context persistence after navigation:** When a `click` or `press Enter` triggers a page navigation (including redirects), the browse daemon should follow the navigation and present the final page to subsequent commands, not reset to `about:blank`.

2. **Chain ref persistence:** Refs captured by `snapshot -i` inside a `chain` should be available to subsequent commands in the same chain.

3. **`fill` with CSS selectors:** `$B fill "#user_email" "value"` should work the same as `$B fill @e2 "value"`. Currently times out on Rails apps.

4. **Login helper command:** Consider a built-in `$B login ` that handles the entire flow atomically in one Playwright context, including following redirects.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.